blob: dba7ea5e4439fc51ab15ce377059ce101ac9bc94 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.infrastructure.security.api;
import com.google.gson.Gson;
import com.sun.jersey.core.util.Base64;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Set;
import javax.ws.rs.Consumes;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer;
import org.apache.fineract.infrastructure.security.constants.TwoFactorConstants;
import org.apache.fineract.infrastructure.security.data.AuthenticatedUserData;
import org.apache.fineract.infrastructure.security.service.SpringSecurityPlatformSecurityContext;
import org.apache.fineract.infrastructure.security.service.TwoFactorUtils;
import org.apache.fineract.portfolio.client.service.ClientReadPlatformService;
import org.apache.fineract.useradministration.data.RoleData;
import org.apache.fineract.useradministration.domain.AppUser;
import org.apache.fineract.useradministration.domain.Role;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Profile;
import org.springframework.context.annotation.Scope;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Component;
@Component
@Scope("singleton")
@Profile("basicauth")
@Path("/authentication")
@Tag(name = "Authentication HTTP Basic", description = "An API capability that allows client applications to verify authentication details using HTTP Basic Authentication.")
public class AuthenticationApiResource {
public static class AuthenticateRequest {
public String username;
public String password;
}
private final DaoAuthenticationProvider customAuthenticationProvider;
private final ToApiJsonSerializer<AuthenticatedUserData> apiJsonSerializerService;
private final SpringSecurityPlatformSecurityContext springSecurityPlatformSecurityContext;
private final TwoFactorUtils twoFactorUtils;
private final ClientReadPlatformService clientReadPlatformService;
@Autowired
public AuthenticationApiResource(
@Qualifier("customAuthenticationProvider") final DaoAuthenticationProvider customAuthenticationProvider,
final ToApiJsonSerializer<AuthenticatedUserData> apiJsonSerializerService,
final SpringSecurityPlatformSecurityContext springSecurityPlatformSecurityContext, TwoFactorUtils twoFactorUtils,
ClientReadPlatformService aClientReadPlatformService) {
this.customAuthenticationProvider = customAuthenticationProvider;
this.apiJsonSerializerService = apiJsonSerializerService;
this.springSecurityPlatformSecurityContext = springSecurityPlatformSecurityContext;
this.twoFactorUtils = twoFactorUtils;
clientReadPlatformService = aClientReadPlatformService;
}
@POST
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Verify authentication", description = "Authenticates the credentials provided and returns the set roles and permissions allowed.")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = AuthenticationApiResourceSwagger.PostAuthenticationResponse.class))),
@ApiResponse(responseCode = "400", description = "Unauthenticated. Please login") })
public String authenticate(final String apiRequestBodyAsJson,
@QueryParam("returnClientList") @DefaultValue("false") boolean returnClientList) {
// TODO FINERACT-819: sort out Jersey so JSON conversion does not have
// to be done explicitly via GSON here, but implicit by arg
AuthenticateRequest request = new Gson().fromJson(apiRequestBodyAsJson, AuthenticateRequest.class);
if (request == null) {
throw new IllegalArgumentException(
"Invalid JSON in BODY (no longer URL param; see FINERACT-726) of POST to /authentication: " + apiRequestBodyAsJson);
}
if (request.username == null || request.password == null) {
throw new IllegalArgumentException("Username or Password is null in JSON (see FINERACT-726) of POST to /authentication: "
+ apiRequestBodyAsJson + "; username=" + request.username + ", password=" + request.password);
}
final Authentication authentication = new UsernamePasswordAuthenticationToken(request.username, request.password);
final Authentication authenticationCheck = this.customAuthenticationProvider.authenticate(authentication);
final Collection<String> permissions = new ArrayList<>();
AuthenticatedUserData authenticatedUserData = new AuthenticatedUserData(request.username, permissions);
if (authenticationCheck.isAuthenticated()) {
final Collection<GrantedAuthority> authorities = new ArrayList<>(authenticationCheck.getAuthorities());
for (final GrantedAuthority grantedAuthority : authorities) {
permissions.add(grantedAuthority.getAuthority());
}
final byte[] base64EncodedAuthenticationKey = Base64.encode(request.username + ":" + request.password);
final AppUser principal = (AppUser) authenticationCheck.getPrincipal();
final Collection<RoleData> roles = new ArrayList<>();
final Set<Role> userRoles = principal.getRoles();
for (final Role role : userRoles) {
roles.add(role.toData());
}
final Long officeId = principal.getOffice().getId();
final String officeName = principal.getOffice().getName();
final Long staffId = principal.getStaffId();
final String staffDisplayName = principal.getStaffDisplayName();
final EnumOptionData organisationalRole = principal.organisationalRoleData();
boolean isTwoFactorRequired = twoFactorUtils.isTwoFactorAuthEnabled()
&& !principal.hasSpecificPermissionTo(TwoFactorConstants.BYPASS_TWO_FACTOR_PERMISSION);
Long userId = principal.getId();
if (this.springSecurityPlatformSecurityContext.doesPasswordHasToBeRenewed(principal)) {
authenticatedUserData = new AuthenticatedUserData(request.username, userId,
new String(base64EncodedAuthenticationKey, StandardCharsets.UTF_8), isTwoFactorRequired);
} else {
authenticatedUserData = new AuthenticatedUserData(request.username, officeId, officeName, staffId, staffDisplayName,
organisationalRole, roles, permissions, principal.getId(),
new String(base64EncodedAuthenticationKey, StandardCharsets.UTF_8), isTwoFactorRequired,
returnClientList ? clientReadPlatformService.retrieveUserClients(userId) : null);
}
}
return this.apiJsonSerializerService.serialize(authenticatedUserData);
}
}