FINERACT-1932: Fineract modularization - fineract-security
This commit is contained in:
committed by
Aleksandar Vidakovic
parent
f504281067
commit
356dd07ca0
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
description = 'Fineract Security'
|
||||
|
||||
apply plugin: 'java'
|
||||
apply plugin: 'eclipse'
|
||||
|
||||
configurations {
|
||||
providedRuntime // needed for Spring Boot executable WAR
|
||||
providedCompile
|
||||
compile() {
|
||||
exclude module: 'hibernate-entitymanager'
|
||||
exclude module: 'hibernate-validator'
|
||||
exclude module: 'activation'
|
||||
exclude module: 'bcmail-jdk14'
|
||||
exclude module: 'bcprov-jdk14'
|
||||
exclude module: 'bctsp-jdk14'
|
||||
exclude module: 'c3p0'
|
||||
exclude module: 'stax-api'
|
||||
exclude module: 'jaxb-api'
|
||||
exclude module: 'jaxb-impl'
|
||||
exclude module: 'jboss-logging'
|
||||
exclude module: 'itext-rtf'
|
||||
exclude module: 'classworlds'
|
||||
}
|
||||
runtime
|
||||
}
|
||||
|
||||
apply from: 'dependencies.gradle'
|
||||
|
||||
// Configuration for the modernizer plugin
|
||||
// https://github.com/andygoossens/gradle-modernizer-plugin
|
||||
modernizer {
|
||||
ignoreClassNamePatterns = [
|
||||
'.*AbstractPersistableCustom',
|
||||
'.*EntityTables',
|
||||
'.*domain.*'
|
||||
]
|
||||
}
|
||||
|
||||
// If we are running Gradle within Eclipse to enhance classes with OpenJPA,
|
||||
// set the classes directory to point to Eclipse's default build directory
|
||||
if (project.hasProperty('env') && project.getProperty('env') == 'eclipse') {
|
||||
sourceSets.main.java.outputDir = new File(rootProject.projectDir, "fineract-security/bin/main")
|
||||
}
|
||||
|
||||
eclipse {
|
||||
project {
|
||||
buildCommand([ LaunchConfigHandle: "<project>/.externalToolBuilders/OpenJPA Enhance Builder.launch" ], 'org.eclipse.ui.externaltools.ExternalToolBuilder')
|
||||
}
|
||||
}
|
||||
|
||||
if (!(project.hasProperty('env') && project.getProperty('env') == 'dev')) {
|
||||
sourceSets {
|
||||
test {
|
||||
java {
|
||||
exclude '**/core/boot/tests/**'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
dependencies {
|
||||
// Never use "compile" scope, but make all dependencies either 'implementation', 'runtimeOnly' or 'testCompile'.
|
||||
// Note that we never use 'api', because Fineract at least currently is a simple monolithic application ("WAR"), not a library.
|
||||
// We also (normally should have) no need to ever use 'compileOnly'.
|
||||
|
||||
// Core module dependency
|
||||
implementation(project(path: ':fineract-core'))
|
||||
|
||||
// PostgreSQL driver for SQL injection prevention utilities
|
||||
implementation 'org.postgresql:postgresql'
|
||||
|
||||
// Spring Boot Security dependencies
|
||||
implementation(
|
||||
'org.springframework.boot:spring-boot-starter-web',
|
||||
'org.springframework.boot:spring-boot-starter-security',
|
||||
'org.springframework.boot:spring-boot-starter-oauth2-authorization-server',
|
||||
'org.springframework.boot:spring-boot-starter-oauth2-resource-server',
|
||||
'org.springframework.boot:spring-boot-starter-oauth2-client',
|
||||
'org.springframework.boot:spring-boot-starter-cache',
|
||||
|
||||
'jakarta.ws.rs:jakarta.ws.rs-api',
|
||||
'org.glassfish.jersey.media:jersey-media-multipart',
|
||||
|
||||
'com.google.guava:guava',
|
||||
'com.google.code.gson:gson',
|
||||
|
||||
'org.apache.commons:commons-lang3',
|
||||
|
||||
'com.jayway.jsonpath:json-path',
|
||||
|
||||
'com.github.spotbugs:spotbugs-annotations',
|
||||
'io.swagger.core.v3:swagger-annotations-jakarta',
|
||||
|
||||
'org.springdoc:springdoc-openapi-starter-webmvc-ui',
|
||||
'org.mapstruct:mapstruct',
|
||||
)
|
||||
|
||||
// JPA for security entities
|
||||
implementation ('org.springframework.boot:spring-boot-starter-data-jpa') {
|
||||
exclude group: 'org.hibernate'
|
||||
}
|
||||
implementation('org.eclipse.persistence:org.eclipse.persistence.jpa') {
|
||||
exclude group: 'org.eclipse.persistence', module: 'jakarta.persistence'
|
||||
}
|
||||
|
||||
// OWASP ESAPI for security utilities
|
||||
implementation('org.owasp.esapi:esapi') {
|
||||
exclude group: 'org.slf4j'
|
||||
exclude group: 'log4j'
|
||||
exclude group: 'commons-logging'
|
||||
exclude group: 'commons-collections'
|
||||
exclude group: 'commons-lang'
|
||||
exclude group: 'spotbugs-annotations'
|
||||
exclude group: 'xml-apis'
|
||||
}
|
||||
|
||||
// Email support for OTP delivery
|
||||
implementation ('org.apache.commons:commons-email') {
|
||||
exclude group: 'com.sun.mail', module: 'javax.mail'
|
||||
exclude group: 'javax.activation', module: 'activation'
|
||||
}
|
||||
implementation 'com.sun.mail:jakarta.mail'
|
||||
|
||||
compileOnly 'org.projectlombok:lombok'
|
||||
annotationProcessor 'org.projectlombok:lombok'
|
||||
annotationProcessor 'org.mapstruct:mapstruct-processor'
|
||||
|
||||
// Test dependencies
|
||||
testImplementation( 'io.github.classgraph:classgraph' )
|
||||
testImplementation ('org.springframework.boot:spring-boot-starter-test') {
|
||||
exclude group: 'com.jayway.jsonpath', module: 'json-path'
|
||||
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
|
||||
exclude group: 'jakarta.activation'
|
||||
exclude group: 'javax.activation'
|
||||
exclude group: 'org.skyscreamer'
|
||||
}
|
||||
testImplementation ('org.mockito:mockito-inline')
|
||||
testImplementation 'org.springframework.security:spring-security-test'
|
||||
}
|
||||
+156
@@ -0,0 +1,156 @@
|
||||
/**
|
||||
* 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 io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.media.Content;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.v3.oas.annotations.parameters.RequestBody;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.ws.rs.Consumes;
|
||||
import jakarta.ws.rs.DefaultValue;
|
||||
import jakarta.ws.rs.POST;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.QueryParam;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.Collection;
|
||||
import java.util.Set;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
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.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.Qualifier;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
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
|
||||
@ConditionalOnProperty("fineract.security.basicauth.enabled")
|
||||
@Path("/v1/authentication")
|
||||
@Tag(name = "Authentication HTTP Basic", description = "An API capability that allows client applications to verify authentication details using HTTP Basic Authentication.")
|
||||
@RequiredArgsConstructor
|
||||
public class AuthenticationApiResource {
|
||||
|
||||
@Value("${fineract.security.2fa.enabled}")
|
||||
private boolean twoFactorEnabled;
|
||||
|
||||
public static class AuthenticateRequest {
|
||||
|
||||
public String username;
|
||||
public String password;
|
||||
}
|
||||
|
||||
@Qualifier("customAuthenticationProvider")
|
||||
private final DaoAuthenticationProvider customAuthenticationProvider;
|
||||
private final ToApiJsonSerializer<AuthenticatedUserData> apiJsonSerializerService;
|
||||
private final SpringSecurityPlatformSecurityContext springSecurityPlatformSecurityContext;
|
||||
private final ClientReadPlatformService clientReadPlatformService;
|
||||
|
||||
@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.")
|
||||
@RequestBody(required = true, content = @Content(schema = @Schema(implementation = AuthenticationApiResourceSwagger.PostAuthenticationRequest.class)))
|
||||
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = AuthenticationApiResourceSwagger.PostAuthenticationResponse.class)))
|
||||
@ApiResponse(responseCode = "400", description = "Unauthenticated. Please login")
|
||||
public String authenticate(@Parameter(hidden = true) 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().setUsername(request.username).setPermissions(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.getEncoder()
|
||||
.encode((request.username + ":" + request.password).getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
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 = this.twoFactorEnabled
|
||||
&& !principal.hasSpecificPermissionTo(TwoFactorConstants.BYPASS_TWO_FACTOR_PERMISSION);
|
||||
Long userId = principal.getId();
|
||||
if (this.springSecurityPlatformSecurityContext.doesPasswordHasToBeRenewed(principal)) {
|
||||
authenticatedUserData = new AuthenticatedUserData().setUsername(request.username).setUserId(userId)
|
||||
.setBase64EncodedAuthenticationKey(new String(base64EncodedAuthenticationKey, StandardCharsets.UTF_8))
|
||||
.setAuthenticated(true).setShouldRenewPassword(true).setTwoFactorAuthenticationRequired(isTwoFactorRequired);
|
||||
} else {
|
||||
|
||||
authenticatedUserData = new AuthenticatedUserData().setUsername(request.username).setOfficeId(officeId)
|
||||
.setOfficeName(officeName).setStaffId(staffId).setStaffDisplayName(staffDisplayName)
|
||||
.setOrganisationalRole(organisationalRole).setRoles(roles).setPermissions(permissions).setUserId(principal.getId())
|
||||
.setAuthenticated(true)
|
||||
.setBase64EncodedAuthenticationKey(new String(base64EncodedAuthenticationKey, StandardCharsets.UTF_8))
|
||||
.setTwoFactorAuthenticationRequired(isTwoFactorRequired)
|
||||
.setClients(returnClientList ? clientReadPlatformService.retrieveUserClients(userId) : null);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return this.apiJsonSerializerService.serialize(authenticatedUserData);
|
||||
}
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* 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 io.swagger.v3.oas.annotations.media.Schema;
|
||||
import java.util.Collection;
|
||||
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
|
||||
import org.apache.fineract.useradministration.data.RoleData;
|
||||
|
||||
/**
|
||||
* Created by sanyam on 13/8/17.
|
||||
*/
|
||||
public final class AuthenticationApiResourceSwagger {
|
||||
|
||||
private AuthenticationApiResourceSwagger() {
|
||||
|
||||
}
|
||||
|
||||
@Schema(description = "PostAuthenticationRequest")
|
||||
public static final class PostAuthenticationRequest {
|
||||
|
||||
private PostAuthenticationRequest() {
|
||||
|
||||
}
|
||||
|
||||
@Schema(required = true, example = "mifos")
|
||||
public String username;
|
||||
@Schema(required = true, example = "password")
|
||||
public String password;
|
||||
}
|
||||
|
||||
@Schema(description = "PostAuthenticationResponse")
|
||||
public static final class PostAuthenticationResponse {
|
||||
|
||||
private PostAuthenticationResponse() {
|
||||
|
||||
}
|
||||
|
||||
@Schema(example = "mifos")
|
||||
public String username;
|
||||
@Schema(example = "1")
|
||||
public Long userId;
|
||||
@Schema(example = "bWlmb3M6cGFzc3dvcmQ=")
|
||||
public String base64EncodedAuthenticationKey;
|
||||
@Schema(example = "true")
|
||||
public boolean authenticated;
|
||||
@Schema(example = "1")
|
||||
public Long officeId;
|
||||
@Schema(example = "Head Office")
|
||||
public String officeName;
|
||||
@Schema(example = "1")
|
||||
public Long staffId;
|
||||
@Schema(example = "Director, Program")
|
||||
public String staffDisplayName;
|
||||
public EnumOptionData organisationalRole;
|
||||
public Collection<RoleData> roles;
|
||||
@Schema(example = "ALL_FUNCTIONS")
|
||||
public Collection<String> permissions;
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* 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 org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
||||
@Controller
|
||||
public class LoginController {
|
||||
|
||||
@GetMapping("/login")
|
||||
public String login() {
|
||||
return "login"; // resolves to src/main/resources/templates/login.html
|
||||
}
|
||||
}
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
/**
|
||||
* 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 io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.ws.rs.DefaultValue;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.POST;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.QueryParam;
|
||||
import jakarta.ws.rs.core.Context;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.UriInfo;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.commands.domain.CommandWrapper;
|
||||
import org.apache.fineract.commands.service.CommandWrapperBuilder;
|
||||
import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
|
||||
import org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer;
|
||||
import org.apache.fineract.infrastructure.security.data.AccessTokenData;
|
||||
import org.apache.fineract.infrastructure.security.data.OTPDeliveryMethod;
|
||||
import org.apache.fineract.infrastructure.security.data.OTPMetadata;
|
||||
import org.apache.fineract.infrastructure.security.data.OTPRequest;
|
||||
import org.apache.fineract.infrastructure.security.domain.TFAccessToken;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.apache.fineract.infrastructure.security.service.TwoFactorService;
|
||||
import org.apache.fineract.useradministration.domain.AppUser;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Path("/v1/twofactor")
|
||||
@Component
|
||||
@ConditionalOnProperty("fineract.security.2fa.enabled")
|
||||
@Tag(name = "Two Factor", description = "")
|
||||
@RequiredArgsConstructor
|
||||
public class TwoFactorApiResource {
|
||||
|
||||
private final ToApiJsonSerializer<OTPMetadata> otpRequestSerializer;
|
||||
private final ToApiJsonSerializer<OTPDeliveryMethod> otpDeliveryMethodSerializer;
|
||||
private final ToApiJsonSerializer<AccessTokenData> accessTokenSerializer;
|
||||
private final DefaultToApiJsonSerializer<Map<String, Object>> toApiJsonSerializer;
|
||||
|
||||
private final PlatformSecurityContext context;
|
||||
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
|
||||
private final TwoFactorService twoFactorService;
|
||||
|
||||
@GET
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
public String getOTPDeliveryMethods(@Context final UriInfo uriInfo) {
|
||||
AppUser user = context.authenticatedUser();
|
||||
|
||||
List<OTPDeliveryMethod> otpDeliveryMethods = twoFactorService.getDeliveryMethodsForUser(user);
|
||||
return this.otpDeliveryMethodSerializer.serialize(otpDeliveryMethods);
|
||||
}
|
||||
|
||||
@POST
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
public String requestToken(@QueryParam("deliveryMethod") final String deliveryMethod,
|
||||
@QueryParam("extendedToken") @DefaultValue("false") boolean extendedAccessToken, @Context final UriInfo uriInfo) {
|
||||
final AppUser user = context.authenticatedUser();
|
||||
|
||||
final OTPRequest request = twoFactorService.createNewOTPToken(user, deliveryMethod, extendedAccessToken);
|
||||
return this.otpRequestSerializer.serialize(request.getMetadata());
|
||||
}
|
||||
|
||||
@Path("validate")
|
||||
@POST
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
public String validate(@QueryParam("token") final String token) {
|
||||
final AppUser user = context.authenticatedUser();
|
||||
|
||||
TFAccessToken accessToken = twoFactorService.createAccessTokenFromOTP(user, token);
|
||||
|
||||
return accessTokenSerializer.serialize(accessToken.toTokenData());
|
||||
}
|
||||
|
||||
@Path("invalidate")
|
||||
@POST
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
public String updateConfiguration(final String apiRequestBodyAsJson) {
|
||||
final CommandWrapper commandRequest = new CommandWrapperBuilder().invalidateTwoFactorAccessToken().withJson(apiRequestBodyAsJson)
|
||||
.build();
|
||||
final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
|
||||
return this.toApiJsonSerializer.serialize(result);
|
||||
}
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* 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 jakarta.ws.rs.Consumes;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.PUT;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import java.util.Map;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.commands.domain.CommandWrapper;
|
||||
import org.apache.fineract.commands.service.CommandWrapperBuilder;
|
||||
import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.apache.fineract.infrastructure.security.service.TwoFactorConfigurationService;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Path("/v1/twofactor/configure")
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Component
|
||||
@ConditionalOnProperty("fineract.security.2fa.enabled")
|
||||
@RequiredArgsConstructor
|
||||
public class TwoFactorConfigurationApiResource {
|
||||
|
||||
private static final String RESOURCE_NAME_FOR_PERMISSIONS = "TWOFACTOR_CONFIG";
|
||||
|
||||
private final PlatformSecurityContext context;
|
||||
private final TwoFactorConfigurationService configurationService;
|
||||
private final DefaultToApiJsonSerializer<Map<String, Object>> toApiJsonSerializer;
|
||||
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
|
||||
|
||||
@GET
|
||||
public String retrieveAll() {
|
||||
this.context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
|
||||
Map<String, Object> configurationMap = configurationService.retrieveAll();
|
||||
return toApiJsonSerializer.serialize(configurationMap);
|
||||
}
|
||||
|
||||
@PUT
|
||||
public String updateConfiguration(final String apiRequestBodyAsJson) {
|
||||
final CommandWrapper commandRequest = new CommandWrapperBuilder().updateTwoFactorConfiguration().withJson(apiRequestBodyAsJson)
|
||||
.build();
|
||||
final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
|
||||
return this.toApiJsonSerializer.serialize(result);
|
||||
}
|
||||
}
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
/**
|
||||
* 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 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.tags.Tag;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Set;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
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.AuthenticatedOauthUserData;
|
||||
import org.apache.fineract.infrastructure.security.data.FineractJwtAuthenticationToken;
|
||||
import org.apache.fineract.infrastructure.security.service.SpringSecurityPlatformSecurityContext;
|
||||
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.Value;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/*
|
||||
* Implementation of Oauth2 authentication APIs, loaded only when "oauth" profile is enabled.
|
||||
*/
|
||||
@Path("/v1/userdetails")
|
||||
@Component
|
||||
@ConditionalOnProperty("fineract.security.oauth2.enabled")
|
||||
@Tag(name = "Fetch authenticated user details", description = "")
|
||||
@RequiredArgsConstructor
|
||||
public class UserDetailsApiResource {
|
||||
|
||||
private final ToApiJsonSerializer<AuthenticatedOauthUserData> apiJsonSerializerService;
|
||||
private final SpringSecurityPlatformSecurityContext springSecurityPlatformSecurityContext;
|
||||
|
||||
@Value("${fineract.security.2fa.enabled}")
|
||||
private boolean twoFactorEnabled;
|
||||
|
||||
@GET
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "Fetch authenticated user details\n", description = "checks the Authentication and returns the set roles and permissions allowed.")
|
||||
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = UserDetailsApiResourceSwagger.GetUserDetailsResponse.class)))
|
||||
public String fetchAuthenticatedUserData() {
|
||||
|
||||
final SecurityContext context = SecurityContextHolder.getContext();
|
||||
if (context == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final FineractJwtAuthenticationToken authentication = (FineractJwtAuthenticationToken) context.getAuthentication();
|
||||
if (authentication == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final AppUser principal = (AppUser) authentication.getPrincipal();
|
||||
if (principal == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final Collection<String> permissions = new ArrayList<>();
|
||||
AuthenticatedOauthUserData authenticatedUserData;
|
||||
|
||||
final Collection<GrantedAuthority> authorities = new ArrayList<>(authentication.getAuthorities());
|
||||
for (final GrantedAuthority grantedAuthority : authorities) {
|
||||
permissions.add(grantedAuthority.getAuthority());
|
||||
}
|
||||
|
||||
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 = this.twoFactorEnabled
|
||||
&& !principal.hasSpecificPermissionTo(TwoFactorConstants.BYPASS_TWO_FACTOR_PERMISSION);
|
||||
if (this.springSecurityPlatformSecurityContext.doesPasswordHasToBeRenewed(principal)) {
|
||||
authenticatedUserData = new AuthenticatedOauthUserData().setUsername(principal.getUsername()).setUserId(principal.getId())
|
||||
.setAccessToken(authentication.getToken().getTokenValue()).setAuthenticated(true).setShouldRenewPassword(true)
|
||||
.setTwoFactorAuthenticationRequired(isTwoFactorRequired);
|
||||
} else {
|
||||
authenticatedUserData = new AuthenticatedOauthUserData().setUsername(principal.getUsername()).setOfficeId(officeId)
|
||||
.setOfficeName(officeName).setStaffId(staffId).setStaffDisplayName(staffDisplayName)
|
||||
.setOrganisationalRole(organisationalRole).setRoles(roles).setPermissions(permissions).setUserId(principal.getId())
|
||||
.setAccessToken(authentication.getToken().getTokenValue()).setAuthenticated(true)
|
||||
.setTwoFactorAuthenticationRequired(isTwoFactorRequired);
|
||||
}
|
||||
|
||||
return this.apiJsonSerializerService.serialize(authenticatedUserData);
|
||||
}
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* 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 io.swagger.v3.oas.annotations.media.Schema;
|
||||
import java.util.Collection;
|
||||
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
|
||||
import org.apache.fineract.useradministration.data.RoleData;
|
||||
|
||||
/**
|
||||
* Created by sanyam on 13/8/17.
|
||||
*/
|
||||
final class UserDetailsApiResourceSwagger {
|
||||
|
||||
private UserDetailsApiResourceSwagger() {
|
||||
|
||||
}
|
||||
|
||||
@Schema(description = "GetUserDetailsResponse")
|
||||
public static final class GetUserDetailsResponse {
|
||||
|
||||
private GetUserDetailsResponse() {
|
||||
|
||||
}
|
||||
|
||||
@Schema(example = "mifos")
|
||||
public String username;
|
||||
@Schema(example = "1")
|
||||
public Long userId;
|
||||
@Schema(example = "bWlmb3M6cGFzc3dvcmQ=")
|
||||
public String accessToken;
|
||||
@Schema(example = "true")
|
||||
public boolean authenticated;
|
||||
@Schema(example = "1")
|
||||
public Long officeId;
|
||||
@Schema(example = "Head Office")
|
||||
public String officeName;
|
||||
@Schema(example = "1")
|
||||
public Long staffId;
|
||||
@Schema(example = "mifosStaffDisplayName")
|
||||
public String staffDisplayName;
|
||||
public EnumOptionData organisationalRole;
|
||||
public Collection<RoleData> roles;
|
||||
@Schema(example = "ALL_FUNCTIONS")
|
||||
public Collection<String> permissions;
|
||||
}
|
||||
}
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
/**
|
||||
* 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.command;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.fineract.commands.annotation.CommandType;
|
||||
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
|
||||
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
|
||||
import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
||||
import org.apache.fineract.infrastructure.security.constants.TwoFactorConstants;
|
||||
import org.apache.fineract.infrastructure.security.domain.TFAccessToken;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.apache.fineract.infrastructure.security.service.TwoFactorService;
|
||||
import org.apache.fineract.useradministration.domain.AppUser;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
@CommandType(entity = "TWOFACTOR_ACCESSTOKEN", action = "INVALIDATE")
|
||||
@ConditionalOnProperty("fineract.security.2fa.enabled")
|
||||
public class InvalidateTFAccessTokenCommandHandler implements NewCommandSourceHandler {
|
||||
|
||||
private final TwoFactorService twoFactorService;
|
||||
private final PlatformSecurityContext securityContext;
|
||||
private final FromJsonHelper fromJsonHelper;
|
||||
|
||||
@Autowired
|
||||
public InvalidateTFAccessTokenCommandHandler(TwoFactorService twoFactorService, PlatformSecurityContext securityContext,
|
||||
FromJsonHelper fromJsonHelper) {
|
||||
this.twoFactorService = twoFactorService;
|
||||
this.securityContext = securityContext;
|
||||
this.fromJsonHelper = fromJsonHelper;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult processCommand(JsonCommand command) {
|
||||
validateJson(command.json());
|
||||
|
||||
final AppUser user = securityContext.authenticatedUser();
|
||||
|
||||
final TFAccessToken accessToken = twoFactorService.invalidateAccessToken(user, command);
|
||||
|
||||
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withResourceIdAsString(accessToken.getToken())
|
||||
.build();
|
||||
}
|
||||
|
||||
private void validateJson(String json) {
|
||||
if (StringUtils.isBlank(json)) {
|
||||
throw new InvalidJsonException();
|
||||
}
|
||||
|
||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
|
||||
this.fromJsonHelper.checkForUnsupportedParameters(typeOfMap, json, new HashSet<>(Collections.singletonList("token")));
|
||||
final JsonElement element = this.fromJsonHelper.parse(json);
|
||||
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
|
||||
.resource(TwoFactorConstants.ACCESSTOKEN_RESOURCE_NAME);
|
||||
|
||||
final String token = this.fromJsonHelper.extractStringNamed("token", element);
|
||||
baseDataValidator.reset().parameter("token").value(token).notNull().notBlank();
|
||||
|
||||
if (!dataValidationErrors.isEmpty()) {
|
||||
throw new PlatformApiDataValidationException(dataValidationErrors);
|
||||
}
|
||||
}
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* 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.command;
|
||||
|
||||
import java.util.Map;
|
||||
import org.apache.fineract.commands.annotation.CommandType;
|
||||
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
|
||||
import org.apache.fineract.infrastructure.security.data.TwoFactorConfigurationValidator;
|
||||
import org.apache.fineract.infrastructure.security.service.TwoFactorConfigurationService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
@CommandType(entity = "TWOFACTOR_CONFIGURATION", action = "UPDATE")
|
||||
@ConditionalOnProperty("fineract.security.2fa.enabled")
|
||||
public class UpdateTwoFactorConfigCommandHandler implements NewCommandSourceHandler {
|
||||
|
||||
private final TwoFactorConfigurationService configurationService;
|
||||
private final TwoFactorConfigurationValidator dataValidator;
|
||||
|
||||
@Autowired
|
||||
public UpdateTwoFactorConfigCommandHandler(TwoFactorConfigurationService configurationService,
|
||||
TwoFactorConfigurationValidator dataValidator) {
|
||||
this.configurationService = configurationService;
|
||||
this.dataValidator = dataValidator;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult processCommand(final JsonCommand command) {
|
||||
this.dataValidator.validateForUpdate(command.json());
|
||||
final Map<String, Object> changes = configurationService.update(command);
|
||||
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).with(changes).build();
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* 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.constants;
|
||||
|
||||
public final class TenantConstants {
|
||||
|
||||
private TenantConstants() {
|
||||
|
||||
}
|
||||
|
||||
public static final String PROPERTY_RO_SCHEMA_SERVER_NAME = "FINERACT_RO_SCHEMA_SERVER_NAME";
|
||||
public static final String PROPERTY_RO_SCHEMA_SERVER_PORT = "FINERACT_RO_SCHEMA_SERVER_PORT";
|
||||
public static final String PROPERTY_RO_SCHEMA_SCHEMA_NAME = "FINERACT_RO_SCHEMA_SCHEMA_NAME";
|
||||
public static final String PROPERTY_RO_SCHEMA_USERNAME = "FINERACT_RO_SCHEMA_USERNAME";
|
||||
public static final String PROPERTY_RO_SCHEMA_PASSWORD = "FINERACT_RO_SCHEMA_PASSWORD";
|
||||
public static final String PROPERTY_RO_SCHEMA_CONNECTION_PARAMETERS = "FINERACT_RO_SCHEMA_CONNECTION_PARAMETERS";
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* 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.constants;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public final class TwoFactorConfigurationConstants {
|
||||
|
||||
private TwoFactorConfigurationConstants() {
|
||||
|
||||
}
|
||||
|
||||
public static final String RESOURCE_NAME = "TWOFACTOR_CONFIGURATION";
|
||||
|
||||
public static final String ENABLE_EMAIL_DELIVERY = "otp-delivery-email-enable";
|
||||
public static final String EMAIL_SUBJECT = "otp-delivery-email-subject";
|
||||
public static final String EMAIL_BODY = "otp-delivery-email-body";
|
||||
|
||||
public static final String ENABLE_SMS_DELIVERY = "otp-delivery-sms-enable";
|
||||
public static final String SMS_PROVIDER_ID = "otp-delivery-sms-provider";
|
||||
public static final String SMS_MESSAGE_TEXT = "otp-delivery-sms-text";
|
||||
|
||||
public static final String OTP_TOKEN_LIVE_TIME = "otp-token-live-time";
|
||||
public static final String OTP_TOKEN_LENGTH = "otp-token-length";
|
||||
|
||||
public static final String ACCESS_TOKEN_LIVE_TIME = "access-token-live-time";
|
||||
public static final String ACCESS_TOKEN_LIVE_TIME_EXTENDED = "access-token-live-time-extended";
|
||||
|
||||
public static final Set<String> REQUEST_DATA_PARAMETERS = Collections.unmodifiableSet(
|
||||
new HashSet<>(Arrays.asList(ENABLE_EMAIL_DELIVERY, EMAIL_SUBJECT, EMAIL_BODY, ENABLE_SMS_DELIVERY, SMS_PROVIDER_ID,
|
||||
SMS_MESSAGE_TEXT, OTP_TOKEN_LIVE_TIME, OTP_TOKEN_LENGTH, ACCESS_TOKEN_LIVE_TIME, ACCESS_TOKEN_LIVE_TIME_EXTENDED)));
|
||||
|
||||
public static final List<String> STRING_PARAMETERS = Collections
|
||||
.unmodifiableList(Arrays.asList(EMAIL_SUBJECT, EMAIL_BODY, SMS_MESSAGE_TEXT));
|
||||
|
||||
public static final List<String> BOOLEAN_PARAMETERS = Collections
|
||||
.unmodifiableList(Arrays.asList(ENABLE_EMAIL_DELIVERY, ENABLE_SMS_DELIVERY));
|
||||
|
||||
public static final List<String> NUMBER_PARAMETERS = Collections.unmodifiableList(
|
||||
Arrays.asList(SMS_PROVIDER_ID, OTP_TOKEN_LIVE_TIME, OTP_TOKEN_LENGTH, ACCESS_TOKEN_LIVE_TIME, ACCESS_TOKEN_LIVE_TIME_EXTENDED));
|
||||
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* 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.constants;
|
||||
|
||||
public final class TwoFactorConstants {
|
||||
|
||||
private TwoFactorConstants() {
|
||||
|
||||
}
|
||||
|
||||
public static final String ACCESSTOKEN_RESOURCE_NAME = "TWOFACTOR_ACCESSTOKEN";
|
||||
|
||||
public static final String SMS_DELIVERY_METHOD_NAME = "sms";
|
||||
public static final String EMAIL_DELIVERY_METHOD_NAME = "email";
|
||||
|
||||
public static final String BYPASS_TWO_FACTOR_PERMISSION = "BYPASS_TWOFACTOR";
|
||||
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* 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.converter;
|
||||
|
||||
import java.util.Collection;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.infrastructure.security.data.FineractJwtAuthenticationToken;
|
||||
import org.apache.fineract.infrastructure.security.service.TenantAwareJpaPlatformUserDetailsService;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class FineractJwtAuthenticationTokenConverter implements Converter<Jwt, FineractJwtAuthenticationToken> {
|
||||
|
||||
private final TenantAwareJpaPlatformUserDetailsService userDetailsService;
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public FineractJwtAuthenticationToken convert(@NonNull Jwt jwt) {
|
||||
try {
|
||||
UserDetails user = userDetailsService.loadUserByUsername(jwt.getSubject());
|
||||
Collection<GrantedAuthority> authorities = new JwtGrantedAuthoritiesConverter().convert(jwt);
|
||||
return new FineractJwtAuthenticationToken(jwt, authorities, user);
|
||||
} catch (UsernameNotFoundException ex) {
|
||||
throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_TOKEN), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* 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.data;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@Accessors(chain = true)
|
||||
public class AccessTokenData {
|
||||
|
||||
private String token;
|
||||
|
||||
private ZonedDateTime validFrom;
|
||||
private ZonedDateTime validTo;
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* 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.data;
|
||||
|
||||
import java.util.Collection;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
|
||||
import org.apache.fineract.useradministration.data.RoleData;
|
||||
|
||||
/**
|
||||
* Immutable data object for authentication. Used in case of Oauth2.
|
||||
*/
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@Accessors(chain = true)
|
||||
public class AuthenticatedOauthUserData {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private String username;
|
||||
@SuppressWarnings("unused")
|
||||
private Long userId;
|
||||
@SuppressWarnings("unused")
|
||||
private String accessToken;
|
||||
@SuppressWarnings("unused")
|
||||
private boolean authenticated;
|
||||
@SuppressWarnings("unused")
|
||||
private Long officeId;
|
||||
@SuppressWarnings("unused")
|
||||
private String officeName;
|
||||
@SuppressWarnings("unused")
|
||||
private Long staffId;
|
||||
@SuppressWarnings("unused")
|
||||
private String staffDisplayName;
|
||||
@SuppressWarnings("unused")
|
||||
private EnumOptionData organisationalRole;
|
||||
@SuppressWarnings("unused")
|
||||
private Collection<RoleData> roles;
|
||||
@SuppressWarnings("unused")
|
||||
private Collection<String> permissions;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private boolean shouldRenewPassword;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private boolean isTwoFactorAuthenticationRequired;
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* 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.data;
|
||||
|
||||
import java.util.Collection;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
|
||||
import org.apache.fineract.useradministration.data.RoleData;
|
||||
|
||||
/**
|
||||
* Immutable data object for authentication.
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@Accessors(chain = true)
|
||||
public class AuthenticatedUserData {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private String username;
|
||||
@SuppressWarnings("unused")
|
||||
private Long userId;
|
||||
@SuppressWarnings("unused")
|
||||
private String base64EncodedAuthenticationKey;
|
||||
@SuppressWarnings("unused")
|
||||
private boolean authenticated;
|
||||
@SuppressWarnings("unused")
|
||||
private Long officeId;
|
||||
@SuppressWarnings("unused")
|
||||
private String officeName;
|
||||
@SuppressWarnings("unused")
|
||||
private Long staffId;
|
||||
@SuppressWarnings("unused")
|
||||
private String staffDisplayName;
|
||||
@SuppressWarnings("unused")
|
||||
private EnumOptionData organisationalRole;
|
||||
@SuppressWarnings("unused")
|
||||
private Collection<RoleData> roles;
|
||||
@SuppressWarnings("unused")
|
||||
private Collection<String> permissions;
|
||||
|
||||
private Collection<Long> clients;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private boolean shouldRenewPassword;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private boolean isTwoFactorAuthenticationRequired;
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* 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.data;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Objects;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
|
||||
|
||||
public class FineractJwtAuthenticationToken extends JwtAuthenticationToken {
|
||||
|
||||
private final UserDetails user;
|
||||
|
||||
public FineractJwtAuthenticationToken(Jwt jwt, Collection<GrantedAuthority> authorities, UserDetails user) {
|
||||
super(jwt, authorities, user.getUsername());
|
||||
this.user = Objects.requireNonNull(user, "user");
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserDetails getPrincipal() {
|
||||
return user;
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* 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.data;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@Accessors(chain = true)
|
||||
public class OTPDeliveryMethod {
|
||||
|
||||
private String name;
|
||||
private String target;
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* 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.data;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@Accessors(chain = true)
|
||||
public class OTPMetadata {
|
||||
|
||||
private ZonedDateTime requestTime;
|
||||
private int tokenLiveTimeInSec;
|
||||
private boolean extendedAccessToken;
|
||||
private OTPDeliveryMethod deliveryMethod;
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* 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.data;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@Accessors(chain = true)
|
||||
public class OTPRequest {
|
||||
|
||||
private String token;
|
||||
private OTPMetadata metadata;
|
||||
|
||||
public static OTPRequest create(String token, int tokenLiveTimeInSec, boolean extendedAccessToken, OTPDeliveryMethod deliveryMethod) {
|
||||
final OTPMetadata metadata = new OTPMetadata()
|
||||
.setRequestTime(DateUtils.getLocalDateTimeOfTenant().atZone(DateUtils.getDateTimeZoneOfTenant()))
|
||||
.setTokenLiveTimeInSec(tokenLiveTimeInSec).setExtendedAccessToken(extendedAccessToken).setDeliveryMethod(deliveryMethod);
|
||||
|
||||
return new OTPRequest().setToken(token).setMetadata(metadata);
|
||||
}
|
||||
|
||||
public boolean isValid() {
|
||||
ZonedDateTime expireTime = metadata.getRequestTime().plusSeconds(metadata.getTokenLiveTimeInSec());
|
||||
return ZonedDateTime.now(DateUtils.getDateTimeZoneOfTenant()).isBefore(expireTime);
|
||||
}
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* 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.data;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.apache.commons.lang3.time.StopWatch;
|
||||
|
||||
/**
|
||||
* Immutable data object representing platform API request used for logging/debugging.
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@Accessors(chain = true)
|
||||
public final class PlatformRequestLog {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private long startTime;
|
||||
@SuppressWarnings("unused")
|
||||
private long totalTime;
|
||||
@SuppressWarnings("unused")
|
||||
private String method;
|
||||
@SuppressWarnings("unused")
|
||||
private String url;
|
||||
@SuppressWarnings("unused")
|
||||
private Map<String, String[]> parameters;
|
||||
|
||||
public static PlatformRequestLog from(final StopWatch task, final HttpServletRequest request) throws IOException {
|
||||
final String requestUrl = request.getRequestURL().toString();
|
||||
|
||||
final Map<String, String[]> parameters = new HashMap<>(request.getParameterMap());
|
||||
parameters.remove("password");
|
||||
parameters.remove("_");
|
||||
|
||||
return new PlatformRequestLog().setStartTime(task.getStartTime()).setTotalTime(task.getTime()).setMethod(request.getMethod())
|
||||
.setUrl(requestUrl).setParameters(parameters);
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* 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.data;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@Accessors(chain = true)
|
||||
public class TenantAuthenticationDetails {
|
||||
|
||||
private String userName;
|
||||
private String tenantId;
|
||||
private String password;
|
||||
}
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
/**
|
||||
* 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.data;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
|
||||
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
|
||||
import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
||||
import org.apache.fineract.infrastructure.security.constants.TwoFactorConfigurationConstants;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@ConditionalOnProperty("fineract.security.2fa.enabled")
|
||||
public class TwoFactorConfigurationValidator {
|
||||
|
||||
private final FromJsonHelper fromJsonHelper;
|
||||
|
||||
@Autowired
|
||||
public TwoFactorConfigurationValidator(FromJsonHelper fromJsonHelper) {
|
||||
this.fromJsonHelper = fromJsonHelper;
|
||||
}
|
||||
|
||||
public void validateForUpdate(final String json) {
|
||||
if (StringUtils.isBlank(json)) {
|
||||
throw new InvalidJsonException();
|
||||
}
|
||||
|
||||
boolean atLeastOneParameterPassedForUpdate = false;
|
||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
|
||||
this.fromJsonHelper.checkForUnsupportedParameters(typeOfMap, json, TwoFactorConfigurationConstants.REQUEST_DATA_PARAMETERS);
|
||||
final JsonElement element = this.fromJsonHelper.parse(json);
|
||||
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
|
||||
.resource(TwoFactorConfigurationConstants.RESOURCE_NAME);
|
||||
|
||||
for (String parameterName : TwoFactorConfigurationConstants.BOOLEAN_PARAMETERS) {
|
||||
if (this.fromJsonHelper.parameterExists(parameterName, element)) {
|
||||
atLeastOneParameterPassedForUpdate = true;
|
||||
validateBooleanParameter(parameterName, element, baseDataValidator);
|
||||
}
|
||||
}
|
||||
|
||||
for (String parameterName : TwoFactorConfigurationConstants.STRING_PARAMETERS) {
|
||||
if (this.fromJsonHelper.parameterExists(parameterName, element)) {
|
||||
atLeastOneParameterPassedForUpdate = true;
|
||||
validateStringParameter(parameterName, element, baseDataValidator);
|
||||
}
|
||||
}
|
||||
|
||||
for (String parameterName : TwoFactorConfigurationConstants.NUMBER_PARAMETERS) {
|
||||
if (this.fromJsonHelper.parameterExists(parameterName, element)) {
|
||||
atLeastOneParameterPassedForUpdate = true;
|
||||
validateNumberParameter(parameterName, element, baseDataValidator);
|
||||
}
|
||||
}
|
||||
|
||||
if (!atLeastOneParameterPassedForUpdate) {
|
||||
final Object forceError = null;
|
||||
baseDataValidator.reset().anyOfNotNull(forceError);
|
||||
}
|
||||
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
|
||||
if (!dataValidationErrors.isEmpty()) {
|
||||
throw new PlatformApiDataValidationException(dataValidationErrors);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateBooleanParameter(final String name, final JsonElement element, final DataValidatorBuilder baseDataValidator) {
|
||||
final String value = this.fromJsonHelper.extractStringNamed(name, element);
|
||||
baseDataValidator.reset().parameter(name).value(value).notNull().trueOrFalseRequired(value);
|
||||
}
|
||||
|
||||
private void validateStringParameter(final String name, final JsonElement element, final DataValidatorBuilder baseDataValidator) {
|
||||
final String value = this.fromJsonHelper.extractStringNamed(name, element);
|
||||
baseDataValidator.reset().parameter(name).value(value).notBlank().notExceedingLengthOf(1000);
|
||||
}
|
||||
|
||||
private void validateNumberParameter(final String name, final JsonElement element, final DataValidatorBuilder baseDataValidator) {
|
||||
final Integer value = this.fromJsonHelper.extractIntegerSansLocaleNamed(name, element);
|
||||
baseDataValidator.reset().parameter(name).value(value).notNull().integerGreaterThanZero();
|
||||
}
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* 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.domain;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import org.apache.fineract.infrastructure.security.data.OTPRequest;
|
||||
import org.apache.fineract.useradministration.domain.AppUser;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@Repository
|
||||
@ConditionalOnProperty("fineract.security.2fa.enabled")
|
||||
@SuppressWarnings({ "MemberName" })
|
||||
public class OTPRequestRepository {
|
||||
|
||||
private static final ConcurrentHashMap<Long, OTPRequest> OTT_REQUESTS = new ConcurrentHashMap<>();
|
||||
|
||||
public OTPRequest getOTPRequestForUser(AppUser user) {
|
||||
Assert.notNull(user, "User must not be null");
|
||||
|
||||
return OTT_REQUESTS.get(user.getId());
|
||||
}
|
||||
|
||||
public void addOTPRequest(AppUser user, OTPRequest request) {
|
||||
Assert.notNull(user, "User must not be null");
|
||||
Assert.notNull(request, "Request must not be null");
|
||||
OTT_REQUESTS.put(user.getId(), request);
|
||||
}
|
||||
|
||||
public void deleteOTPRequestForUser(AppUser user) {
|
||||
OTT_REQUESTS.remove(user.getId());
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* 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.domain;
|
||||
|
||||
public interface PlatformUserRepository {
|
||||
|
||||
PlatformUser findByUsernameAndDeletedAndEnabled(String username, boolean deleted, boolean enabled);
|
||||
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* 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.domain;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.persistence.UniqueConstraint;
|
||||
import java.time.LocalDateTime;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
|
||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||
import org.apache.fineract.infrastructure.security.data.AccessTokenData;
|
||||
import org.apache.fineract.useradministration.domain.AppUser;
|
||||
|
||||
@Entity
|
||||
@Table(name = "twofactor_access_token", uniqueConstraints = {
|
||||
@UniqueConstraint(columnNames = { "token", "appuser_id" }, name = "token_appuser_UNIQUE") })
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@Accessors(chain = true)
|
||||
public class TFAccessToken extends AbstractPersistableCustom<Long> {
|
||||
|
||||
@Column(name = "token", nullable = false, length = 32)
|
||||
private String token;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "appuser_id", nullable = false)
|
||||
private AppUser user;
|
||||
|
||||
@Column(name = "valid_from", nullable = false)
|
||||
private LocalDateTime validFrom;
|
||||
|
||||
@Column(name = "valid_to", nullable = false)
|
||||
private LocalDateTime validTo;
|
||||
|
||||
@Column(name = "enabled", nullable = false)
|
||||
private boolean enabled;
|
||||
|
||||
public static TFAccessToken create(String token, AppUser user, int tokenLiveTimeInSec) {
|
||||
LocalDateTime validFrom = DateUtils.getLocalDateTimeOfTenant();
|
||||
LocalDateTime validTo = validFrom.plusSeconds(tokenLiveTimeInSec);
|
||||
|
||||
return new TFAccessToken().setToken(token).setUser(user).setValidFrom(validFrom).setValidTo(validTo).setEnabled(true);
|
||||
}
|
||||
|
||||
public boolean isValid() {
|
||||
// valid_from is in the past inclusive, valid_to is in the future exclusive
|
||||
return this.enabled && !DateUtils.isAfterTenantDateTime(getValidFrom()) && DateUtils.isAfterTenantDateTime(getValidTo());
|
||||
}
|
||||
|
||||
public AccessTokenData toTokenData() {
|
||||
return new AccessTokenData().setToken(this.token).setValidFrom(getValidFrom().atZone(DateUtils.getDateTimeZoneOfTenant()))
|
||||
.setValidTo(getValidTo().atZone(DateUtils.getDateTimeZoneOfTenant()));
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* 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.domain;
|
||||
|
||||
import org.apache.fineract.useradministration.domain.AppUser;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||
|
||||
@ConditionalOnProperty("fineract.security.2fa.enabled")
|
||||
public interface TFAccessTokenRepository extends JpaRepository<TFAccessToken, Long>, JpaSpecificationExecutor<TFAccessToken> {
|
||||
|
||||
TFAccessToken findByUserAndToken(AppUser user, String token);
|
||||
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* 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.domain;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.persistence.UniqueConstraint;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.apache.commons.lang3.math.NumberUtils;
|
||||
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
|
||||
import org.apache.fineract.infrastructure.security.constants.TwoFactorConfigurationConstants;
|
||||
|
||||
@Entity
|
||||
@Table(name = "twofactor_configuration", uniqueConstraints = { @UniqueConstraint(columnNames = { "name" }, name = "name_UNIQUE") })
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@Accessors(chain = true)
|
||||
public class TwoFactorConfiguration extends AbstractPersistableCustom<Long> {
|
||||
|
||||
@Column(name = "name", nullable = false, length = 32)
|
||||
private String name;
|
||||
|
||||
@Column(name = "value", nullable = true, length = 1024)
|
||||
private String value;
|
||||
|
||||
public Object getObjectValue() {
|
||||
if (TwoFactorConfigurationConstants.NUMBER_PARAMETERS.contains(name)) {
|
||||
return NumberUtils.createInteger(value);
|
||||
}
|
||||
if (TwoFactorConfigurationConstants.BOOLEAN_PARAMETERS.contains(name)) {
|
||||
return BooleanUtils.toBooleanObject(value);
|
||||
}
|
||||
|
||||
return getValue();
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* 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.domain;
|
||||
|
||||
import java.util.List;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||
|
||||
@ConditionalOnProperty("fineract.security.2fa.enabled")
|
||||
public interface TwoFactorConfigurationRepository
|
||||
extends JpaRepository<TwoFactorConfiguration, Long>, JpaSpecificationExecutor<TwoFactorConfiguration> {
|
||||
|
||||
TwoFactorConfiguration findByName(String name);
|
||||
|
||||
@Override
|
||||
List<TwoFactorConfiguration> findAll();
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* 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.exception;
|
||||
|
||||
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
|
||||
|
||||
public class AccessTokenInvalidIException extends AbstractPlatformDomainRuleException {
|
||||
|
||||
public AccessTokenInvalidIException() {
|
||||
super("error.msg.twofactor.access.token.invalid", "The provided access token is invalid");
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* 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.exception;
|
||||
|
||||
import java.sql.SQLException;
|
||||
|
||||
public class EscapeSqlLiteralException extends RuntimeException {
|
||||
|
||||
public EscapeSqlLiteralException(String message, SQLException e) {
|
||||
super(message, e);
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* 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.exception;
|
||||
|
||||
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
|
||||
|
||||
public class ForcePasswordResetException extends AbstractPlatformDomainRuleException {
|
||||
|
||||
public ForcePasswordResetException() {
|
||||
super("error.msg.password.reset.days.value.must.be.greater.than.zero",
|
||||
"For enabling 'Force Password Reset Days' configuration , the value (number of days after which a user is forced to reset his password) must be set to a number greater than 0.");
|
||||
}
|
||||
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* 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.exception;
|
||||
|
||||
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
|
||||
|
||||
public class OTPDeliveryMethodInvalidException extends AbstractPlatformDomainRuleException {
|
||||
|
||||
public OTPDeliveryMethodInvalidException() {
|
||||
super("error.msg.twofactor.otp.delivery.invalid",
|
||||
"The requested OTP delivery method " + "is not supported or not currently unavailable.");
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* 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.exception;
|
||||
|
||||
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
|
||||
|
||||
public class OTPTokenInvalidException extends AbstractPlatformDomainRuleException {
|
||||
|
||||
public OTPTokenInvalidException() {
|
||||
super("error.msg.twofactor.otp.token.invalid", "The provided one time token is invalid");
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* 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.exception;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||
|
||||
/**
|
||||
* A {@link RuntimeException} that is thrown in the case where a user does not have sufficient authorization to execute
|
||||
* operation on platform.
|
||||
*/
|
||||
public class ResetPasswordException extends PlatformApiDataValidationException {
|
||||
|
||||
public ResetPasswordException(final Long userId) {
|
||||
|
||||
super("error.msg.password.outdated", "The password of the user with id " + userId + " has expired, please reset it",
|
||||
new ArrayList<ApiParameterError>(List.of(ApiParameterError.parameterError("error.msg.password.outdated",
|
||||
"The password of the user with id " + userId + " has expired, please reset it", "userId", userId)))
|
||||
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* 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.filter;
|
||||
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.time.LocalDate;
|
||||
import java.util.HashMap;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
|
||||
import org.apache.fineract.infrastructure.businessdate.service.BusinessDateReadPlatformService;
|
||||
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class BusinessDateFilter extends OncePerRequestFilter {
|
||||
|
||||
private final BusinessDateReadPlatformService businessDateReadPlatformService;
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response,
|
||||
@NonNull FilterChain filterChain) throws ServletException, IOException {
|
||||
if (ThreadLocalContextUtil.getTenant() != null) {
|
||||
HashMap<BusinessDateType, LocalDate> businessDates = businessDateReadPlatformService.getBusinessDates();
|
||||
ThreadLocalContextUtil.setBusinessDates(businessDates);
|
||||
}
|
||||
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* 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.filter;
|
||||
|
||||
import com.nimbusds.jwt.JWTParser;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
|
||||
import org.apache.fineract.infrastructure.security.service.AuthTenantDetailsService;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class TenantAwareAuthenticationFilter extends OncePerRequestFilter {
|
||||
|
||||
private final BearerTokenResolver resolver;
|
||||
private final AuthTenantDetailsService tenantDetailsService;
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response,
|
||||
@NonNull FilterChain filterChain) throws ServletException, IOException {
|
||||
try {
|
||||
String token = resolver.resolve(request);
|
||||
String tenantId;
|
||||
if (token != null) {
|
||||
var jwt = JWTParser.parse(token); // not validated here!
|
||||
var claims = jwt.getJWTClaimsSet();
|
||||
tenantId = (String) claims.getClaim("tenant");
|
||||
} else {
|
||||
tenantId = request.getParameter("tenantId");
|
||||
}
|
||||
ThreadLocalContextUtil.setTenant(tenantDetailsService.loadTenantById(tenantId, false));
|
||||
filterChain.doFilter(request, response);
|
||||
} catch (Exception e) {
|
||||
filterChain.doFilter(request, response); // don't block; real auth will fail later if token is bad
|
||||
} finally {
|
||||
ThreadLocalContextUtil.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
+191
@@ -0,0 +1,191 @@
|
||||
/**
|
||||
* 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.filter;
|
||||
|
||||
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.time.LocalDate;
|
||||
import java.util.HashMap;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.time.StopWatch;
|
||||
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
|
||||
import org.apache.fineract.infrastructure.businessdate.service.BusinessDateReadPlatformService;
|
||||
import org.apache.fineract.infrastructure.cache.domain.CacheType;
|
||||
import org.apache.fineract.infrastructure.cache.service.CacheWritePlatformService;
|
||||
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
|
||||
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
|
||||
import org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer;
|
||||
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
|
||||
import org.apache.fineract.infrastructure.security.data.PlatformRequestLog;
|
||||
import org.apache.fineract.infrastructure.security.exception.InvalidTenantIdentifierException;
|
||||
import org.apache.fineract.infrastructure.security.service.AuthTenantDetailsService;
|
||||
import org.apache.fineract.notification.service.UserNotificationService;
|
||||
import org.apache.fineract.useradministration.domain.AppUser;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.web.AuthenticationEntryPoint;
|
||||
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
|
||||
import org.springframework.security.web.util.matcher.AnyRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
|
||||
/**
|
||||
* A customised version of spring security's {@link BasicAuthenticationFilter}.
|
||||
*
|
||||
* This filter is responsible for extracting multi-tenant and basic auth credentials from the request and checking that
|
||||
* the details provided are valid.
|
||||
*
|
||||
* If multi-tenant and basic auth credentials are valid, the details of the tenant are stored in
|
||||
* {@link FineractPlatformTenant} and stored in a {@link ThreadLocal} variable for this request using
|
||||
* {@link ThreadLocalContextUtil}.
|
||||
*
|
||||
* If multi-tenant and basic auth credentials are invalid, a http error response is returned.
|
||||
*/
|
||||
|
||||
@Slf4j
|
||||
public class TenantAwareBasicAuthenticationFilter extends BasicAuthenticationFilter {
|
||||
|
||||
private static boolean FIRST_REQUEST_PROCESSED = false;
|
||||
private static final String TENANT_ID_REQUEST_HEADER = "Fineract-Platform-TenantId";
|
||||
private static final boolean EXCEPTION_IF_HEADER_MISSING = true;
|
||||
|
||||
private final ToApiJsonSerializer<PlatformRequestLog> toApiJsonSerializer;
|
||||
private final ConfigurationDomainService configurationDomainService;
|
||||
private final CacheWritePlatformService cacheWritePlatformService;
|
||||
private final UserNotificationService userNotificationService;
|
||||
private final AuthTenantDetailsService basicAuthTenantDetailsService;
|
||||
private final BusinessDateReadPlatformService businessDateReadPlatformService;
|
||||
|
||||
@Setter
|
||||
private RequestMatcher requestMatcher = AnyRequestMatcher.INSTANCE;
|
||||
|
||||
public TenantAwareBasicAuthenticationFilter(final AuthenticationManager authenticationManager,
|
||||
final AuthenticationEntryPoint authenticationEntryPoint, ToApiJsonSerializer<PlatformRequestLog> toApiJsonSerializer,
|
||||
ConfigurationDomainService configurationDomainService, CacheWritePlatformService cacheWritePlatformService,
|
||||
UserNotificationService userNotificationService, AuthTenantDetailsService basicAuthTenantDetailsService,
|
||||
BusinessDateReadPlatformService businessDateReadPlatformService) {
|
||||
super(authenticationManager, authenticationEntryPoint);
|
||||
this.toApiJsonSerializer = toApiJsonSerializer;
|
||||
this.configurationDomainService = configurationDomainService;
|
||||
this.cacheWritePlatformService = cacheWritePlatformService;
|
||||
this.userNotificationService = userNotificationService;
|
||||
this.basicAuthTenantDetailsService = basicAuthTenantDetailsService;
|
||||
this.businessDateReadPlatformService = businessDateReadPlatformService;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressFBWarnings("SLF4J_SIGN_ONLY_FORMAT")
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
||||
throws ServletException, IOException {
|
||||
|
||||
final StopWatch task = new StopWatch();
|
||||
task.start();
|
||||
|
||||
try {
|
||||
ThreadLocalContextUtil.reset();
|
||||
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
|
||||
// ignore to allow 'preflight' requests from AJAX applications
|
||||
// in different origin (domain name)
|
||||
filterChain.doFilter(request, response);
|
||||
} else {
|
||||
if (requestMatcher.matches(request)) {
|
||||
String tenantIdentifier = request.getHeader(TENANT_ID_REQUEST_HEADER);
|
||||
|
||||
if (org.apache.commons.lang3.StringUtils.isBlank(tenantIdentifier)) {
|
||||
tenantIdentifier = request.getParameter("tenantIdentifier");
|
||||
}
|
||||
|
||||
if (tenantIdentifier == null && EXCEPTION_IF_HEADER_MISSING) {
|
||||
throw new InvalidTenantIdentifierException("No tenant identifier found: Add request header of '"
|
||||
+ TENANT_ID_REQUEST_HEADER + "' or add the parameter 'tenantIdentifier' to query string of request URL.");
|
||||
}
|
||||
|
||||
String pathInfo = request.getRequestURI();
|
||||
boolean isReportRequest = false;
|
||||
if (pathInfo != null && pathInfo.contains("report")) {
|
||||
isReportRequest = true;
|
||||
}
|
||||
final FineractPlatformTenant tenant = basicAuthTenantDetailsService.loadTenantById(tenantIdentifier, isReportRequest);
|
||||
ThreadLocalContextUtil.setTenant(tenant);
|
||||
HashMap<BusinessDateType, LocalDate> businessDates = businessDateReadPlatformService.getBusinessDates();
|
||||
ThreadLocalContextUtil.setBusinessDates(businessDates);
|
||||
String authToken = request.getHeader("Authorization");
|
||||
|
||||
if (authToken != null && authToken.startsWith("Basic ")) {
|
||||
ThreadLocalContextUtil.setAuthToken(authToken.replaceFirst("Basic ", ""));
|
||||
}
|
||||
|
||||
if (!FIRST_REQUEST_PROCESSED) {
|
||||
final String baseUrl = request.getRequestURL().toString().replace(request.getPathInfo(), "/");
|
||||
System.setProperty("baseUrl", baseUrl);
|
||||
|
||||
final boolean ehcacheEnabled = configurationDomainService.isEhcacheEnabled();
|
||||
if (ehcacheEnabled) {
|
||||
cacheWritePlatformService.switchToCache(CacheType.SINGLE_NODE);
|
||||
} else {
|
||||
cacheWritePlatformService.switchToCache(CacheType.NO_CACHE);
|
||||
}
|
||||
TenantAwareBasicAuthenticationFilter.FIRST_REQUEST_PROCESSED = true;
|
||||
}
|
||||
}
|
||||
|
||||
super.doFilterInternal(request, response, filterChain);
|
||||
}
|
||||
} catch (final InvalidTenantIdentifierException e) {
|
||||
// deal with exception at low level
|
||||
SecurityContextHolder.getContext().setAuthentication(null);
|
||||
|
||||
response.addHeader("WWW-Authenticate", "Basic realm=\"" + "Fineract Platform API" + "\"");
|
||||
response.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
|
||||
} finally {
|
||||
ThreadLocalContextUtil.reset();
|
||||
task.stop();
|
||||
final PlatformRequestLog msg = PlatformRequestLog.from(task, request);
|
||||
log.debug("{}", toApiJsonSerializer.serialize(msg));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, Authentication authResult)
|
||||
throws IOException {
|
||||
super.onSuccessfulAuthentication(request, response, authResult);
|
||||
AppUser user = (AppUser) authResult.getPrincipal();
|
||||
|
||||
if (userNotificationService.hasUnreadUserNotifications(user.getId())) {
|
||||
response.addHeader("X-Notification-Refresh", "true");
|
||||
} else {
|
||||
response.addHeader("X-Notification-Refresh", "false");
|
||||
}
|
||||
|
||||
String pathURL = request.getRequestURI();
|
||||
boolean isSelfServiceRequest = pathURL != null && pathURL.contains("/self/");
|
||||
|
||||
boolean notAllowed = (isSelfServiceRequest && !user.isSelfServiceUser()) || (!isSelfServiceRequest && user.isSelfServiceUser());
|
||||
|
||||
if (notAllowed) {
|
||||
throw new BadCredentialsException("User not authorised to use the requested resource.");
|
||||
}
|
||||
}
|
||||
}
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
/**
|
||||
* 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.filter;
|
||||
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.ServletRequest;
|
||||
import jakarta.servlet.ServletResponse;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.infrastructure.security.constants.TwoFactorConstants;
|
||||
import org.apache.fineract.infrastructure.security.data.FineractJwtAuthenticationToken;
|
||||
import org.apache.fineract.infrastructure.security.domain.TFAccessToken;
|
||||
import org.apache.fineract.infrastructure.security.service.TwoFactorService;
|
||||
import org.apache.fineract.useradministration.domain.AppUser;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.web.filter.GenericFilterBean;
|
||||
|
||||
/**
|
||||
* This filter is responsible for handling two-factor authentication. The filter is enabled when 'twofactor' environment
|
||||
* profile is active.
|
||||
*
|
||||
* This filter validates an access-token provided as a header 'Fineract-Platform-TFA-Token'. If a valid token is
|
||||
* provided, a 'TWOFACTOR_AUTHENTICATED' authority is added to the current authentication. If an invalid(non-existent or
|
||||
* invalid) token is provided, 403 response is returned.
|
||||
*
|
||||
* An authenticated platform user with permission 'BYPASS_TWOFACTOR' will always be granted 'TWOFACTOR_AUTHENTICATED'
|
||||
* authority regardless of the value of the 'Fineract-Platform-TFA-Token' header.
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class TwoFactorAuthenticationFilter extends GenericFilterBean {
|
||||
|
||||
private final TwoFactorService twoFactorService;
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
|
||||
|
||||
final HttpServletRequest request = (HttpServletRequest) req;
|
||||
final HttpServletResponse response = (HttpServletResponse) res;
|
||||
|
||||
SecurityContext context = SecurityContextHolder.getContext();
|
||||
Authentication authentication = null;
|
||||
if (context != null) {
|
||||
authentication = context.getAuthentication();
|
||||
}
|
||||
|
||||
// Process two-factor only when user is authenticated
|
||||
if (authentication != null && authentication.isAuthenticated() && authentication.getPrincipal() instanceof AppUser) {
|
||||
AppUser user = (AppUser) authentication.getPrincipal();
|
||||
|
||||
if (user == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!user.hasSpecificPermissionTo(TwoFactorConstants.BYPASS_TWO_FACTOR_PERMISSION)) {
|
||||
// User can't bypass two-factor auth, check two-factor access
|
||||
// token
|
||||
String token = request.getHeader("Fineract-Platform-TFA-Token");
|
||||
if (token != null) {
|
||||
TFAccessToken accessToken = twoFactorService.fetchAccessTokenForUser(user, token);
|
||||
// Token is non-existent or invalid
|
||||
if (accessToken == null || !accessToken.isValid()) {
|
||||
response.addHeader("WWW-Authenticate", "Basic realm=\"Fineract Platform API Two Factor\"");
|
||||
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid two-factor access token provided");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// No token provided
|
||||
chain.doFilter(req, res);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
List<GrantedAuthority> updatedAuthorities = new ArrayList<>(authentication.getAuthorities());
|
||||
updatedAuthorities.add(new SimpleGrantedAuthority("TWOFACTOR_AUTHENTICATED"));
|
||||
context.setAuthentication(createUpdatedAuthentication(authentication, updatedAuthorities));
|
||||
}
|
||||
|
||||
chain.doFilter(req, res);
|
||||
}
|
||||
|
||||
private Authentication createUpdatedAuthentication(final Authentication currentAuthentication,
|
||||
final List<GrantedAuthority> updatedAuthorities) throws ServletException {
|
||||
|
||||
if (currentAuthentication instanceof UsernamePasswordAuthenticationToken) {
|
||||
UsernamePasswordAuthenticationToken updatedAuthentication = new UsernamePasswordAuthenticationToken(
|
||||
currentAuthentication.getPrincipal(), currentAuthentication.getCredentials(), updatedAuthorities);
|
||||
return updatedAuthentication;
|
||||
} else if (currentAuthentication instanceof FineractJwtAuthenticationToken) {
|
||||
FineractJwtAuthenticationToken fineractJwtAuthenticationToken = (FineractJwtAuthenticationToken) currentAuthentication;
|
||||
FineractJwtAuthenticationToken updatedAuthentication = new FineractJwtAuthenticationToken(
|
||||
fineractJwtAuthenticationToken.getToken(), updatedAuthorities, (UserDetails) currentAuthentication.getPrincipal());
|
||||
return updatedAuthentication;
|
||||
} else {
|
||||
throw new ServletException("Unknown authentication type: " + currentAuthentication.getClass().getName());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Fineract Security Module.
|
||||
*
|
||||
* This module contains all security-related components for Apache Fineract including:
|
||||
* <ul>
|
||||
* <li>Authentication APIs (HTTP Basic, OAuth2, OIDC Federation)</li>
|
||||
* <li>Two-Factor Authentication (2FA/TFA)</li>
|
||||
* <li>Security filters and converters</li>
|
||||
* <li>User authentication services</li>
|
||||
* <li>Security configuration</li>
|
||||
* </ul>
|
||||
*
|
||||
* @see org.apache.fineract.infrastructure.security.api
|
||||
* @see org.apache.fineract.infrastructure.security.config
|
||||
* @see org.apache.fineract.infrastructure.security.service
|
||||
*/
|
||||
package org.apache.fineract.infrastructure.security;
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* 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.service;
|
||||
|
||||
public interface AccessTokenGenerationService {
|
||||
|
||||
String generateRandomToken();
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* 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.service;
|
||||
|
||||
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
|
||||
|
||||
public interface AuthTenantDetailsService {
|
||||
|
||||
FineractPlatformTenant loadTenantById(String tenantId, boolean isReport);
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* 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.service;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
|
||||
import org.apache.fineract.infrastructure.core.service.tenant.TenantMapper;
|
||||
import org.apache.fineract.infrastructure.security.exception.InvalidTenantIdentifierException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.dao.EmptyResultDataAccessException;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* A JDBC implementation of {@link AuthTenantDetailsService} for loading a tenants details by a
|
||||
* <code>tenantIdentifier</code>.
|
||||
*/
|
||||
@Service
|
||||
public class AuthTenantDetailsServiceJdbc implements AuthTenantDetailsService {
|
||||
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
|
||||
@Autowired
|
||||
public AuthTenantDetailsServiceJdbc(@Qualifier("hikariTenantDataSource") final DataSource dataSource) {
|
||||
this.jdbcTemplate = new JdbcTemplate(dataSource);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Cacheable(value = "tenantsById")
|
||||
public FineractPlatformTenant loadTenantById(final String tenantIdentifier, final boolean isReport) {
|
||||
|
||||
try {
|
||||
final TenantMapper rm = new TenantMapper(isReport);
|
||||
final String sql = "select " + rm.schema() + " where t.identifier = ?";
|
||||
|
||||
return this.jdbcTemplate.queryForObject(sql, rm, new Object[] { tenantIdentifier }); // NOSONAR
|
||||
} catch (final EmptyResultDataAccessException e) {
|
||||
throw new InvalidTenantIdentifierException("The tenant identifier: " + tenantIdentifier + " is not valid.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
/**
|
||||
* 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.service;
|
||||
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
import java.io.IOException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.web.DefaultRedirectStrategy;
|
||||
import org.springframework.security.web.RedirectStrategy;
|
||||
import org.springframework.security.web.WebAttributes;
|
||||
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
|
||||
import org.springframework.security.web.util.UrlUtils;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@Slf4j
|
||||
public class CustomAuthenticationFailureHandler implements AuthenticationFailureHandler {
|
||||
|
||||
private String defaultFailureUrl;
|
||||
private boolean forwardToDestination = false;
|
||||
private boolean allowSessionCreation = true;
|
||||
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
|
||||
|
||||
public CustomAuthenticationFailureHandler() {}
|
||||
|
||||
/**
|
||||
* Performs the redirect or forward to the {@code defaultFailureUrl} if set, otherwise returns a 401 error code.
|
||||
* <p>
|
||||
* If redirecting or forwarding, {@code saveException} will be called to cache the exception for use in the target
|
||||
* view.
|
||||
*/
|
||||
@Override
|
||||
public void onAuthenticationFailure(final HttpServletRequest request, final HttpServletResponse response,
|
||||
final AuthenticationException exception) throws IOException, ServletException {
|
||||
|
||||
if (this.defaultFailureUrl == null) {
|
||||
log.debug("No failure URL set, sending 401 Unauthorized error");
|
||||
|
||||
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Authentication Failed: " + exception.getMessage());
|
||||
} else {
|
||||
saveException(request, exception);
|
||||
|
||||
if (this.forwardToDestination) {
|
||||
log.debug("Forwarding to {}", this.defaultFailureUrl);
|
||||
|
||||
request.getRequestDispatcher(this.defaultFailureUrl).forward(request, response);
|
||||
} else {
|
||||
log.debug("Redirecting to {}", this.defaultFailureUrl);
|
||||
|
||||
final String oauthToken = request.getParameter("oauth_token");
|
||||
request.setAttribute("oauth_token", oauthToken);
|
||||
final String url = this.defaultFailureUrl + "?oauth_token=" + oauthToken;
|
||||
this.redirectStrategy.sendRedirect(request, response, url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Caches the {@code AuthenticationException} for use in view rendering.
|
||||
* <p>
|
||||
* If {@code forwardToDestination} is set to true, request scope will be used, otherwise it will attempt to store
|
||||
* the exception in the session. If there is no session and {@code allowSessionCreation} is {@code true} a session
|
||||
* will be created. Otherwise the exception will not be stored.
|
||||
*/
|
||||
protected final void saveException(final HttpServletRequest request, final AuthenticationException exception) {
|
||||
if (this.forwardToDestination) {
|
||||
request.setAttribute(WebAttributes.AUTHENTICATION_EXCEPTION, exception);
|
||||
} else {
|
||||
final HttpSession session = request.getSession(false);
|
||||
|
||||
if (session != null || this.allowSessionCreation) {
|
||||
request.getSession().setAttribute(WebAttributes.AUTHENTICATION_EXCEPTION, exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The URL which will be used as the failure destination.
|
||||
*
|
||||
* @param defaultFailureUrl
|
||||
* the failure URL, for example "/loginFailed.jsp".
|
||||
*/
|
||||
public void setDefaultFailureUrl(final String defaultFailureUrl) {
|
||||
Assert.isTrue(UrlUtils.isValidRedirectUrl(defaultFailureUrl), "'" + defaultFailureUrl + "' is not a valid redirect URL");
|
||||
this.defaultFailureUrl = defaultFailureUrl;
|
||||
}
|
||||
|
||||
protected boolean isUseForward() {
|
||||
return this.forwardToDestination;
|
||||
}
|
||||
|
||||
/**
|
||||
* If set to <tt>true</tt>, performs a forward to the failure destination URL instead of a redirect. Defaults to
|
||||
* <tt>false</tt>.
|
||||
*/
|
||||
public void setUseForward(final boolean forwardToDestination) {
|
||||
this.forwardToDestination = forwardToDestination;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows overriding of the behaviour when redirecting to a target URL.
|
||||
*/
|
||||
public void setRedirectStrategy(final RedirectStrategy redirectStrategy) {
|
||||
this.redirectStrategy = redirectStrategy;
|
||||
}
|
||||
|
||||
protected RedirectStrategy getRedirectStrategy() {
|
||||
return this.redirectStrategy;
|
||||
}
|
||||
|
||||
protected boolean isAllowSessionCreation() {
|
||||
return this.allowSessionCreation;
|
||||
}
|
||||
|
||||
public void setAllowSessionCreation(final boolean allowSessionCreation) {
|
||||
this.allowSessionCreation = allowSessionCreation;
|
||||
}
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* 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.service;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
|
||||
public class RandomOTPGenerator {
|
||||
|
||||
private static final String allowedCharacters = "0123456789ABCDEFGHIJKLMNOPQRSTUVQXYZ";
|
||||
private final int tokenLength;
|
||||
private final SecureRandom secureRandom = new SecureRandom();
|
||||
|
||||
public RandomOTPGenerator(int tokenLength) {
|
||||
this.tokenLength = tokenLength;
|
||||
}
|
||||
|
||||
public String generate() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (int i = 0; i < tokenLength; i++) {
|
||||
builder.append(allowedCharacters.charAt((int) (secureRandom.nextDouble() * allowedCharacters.length())));
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
+177
@@ -0,0 +1,177 @@
|
||||
/**
|
||||
* 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.service;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.commands.domain.CommandWrapper;
|
||||
import org.apache.fineract.commands.service.CommandWrapperBuilder;
|
||||
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
|
||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||
import org.apache.fineract.infrastructure.security.exception.NoAuthorizationException;
|
||||
import org.apache.fineract.infrastructure.security.exception.ResetPasswordException;
|
||||
import org.apache.fineract.useradministration.domain.AppUser;
|
||||
import org.apache.fineract.useradministration.exception.UnAuthenticatedUserException;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* Wrapper around spring security's {@link SecurityContext} for extracted the current authenticated {@link AppUser}.
|
||||
*/
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class SpringSecurityPlatformSecurityContext implements PlatformSecurityContext {
|
||||
|
||||
// private static final Logger LOG =
|
||||
// LoggerFactory.getLogger(SpringSecurityPlatformSecurityContext.class);
|
||||
|
||||
private final ConfigurationDomainService configurationDomainService;
|
||||
|
||||
protected static final List<CommandWrapper> EXEMPT_FROM_PASSWORD_RESET_CHECK = new ArrayList<CommandWrapper>(
|
||||
List.of(new CommandWrapperBuilder().updateUser(null).build()));
|
||||
|
||||
@Override
|
||||
public AppUser authenticatedUser() {
|
||||
|
||||
AppUser currentUser = null;
|
||||
final SecurityContext context = SecurityContextHolder.getContext();
|
||||
if (context != null) {
|
||||
final Authentication auth = context.getAuthentication();
|
||||
if (auth != null) {
|
||||
Object principal = auth.getPrincipal();
|
||||
if (principal instanceof AppUser appUser) {
|
||||
currentUser = appUser;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (currentUser == null) {
|
||||
throw new UnAuthenticatedUserException();
|
||||
}
|
||||
|
||||
if (this.doesPasswordHasToBeRenewed(currentUser)) {
|
||||
throw new ResetPasswordException(currentUser.getId());
|
||||
}
|
||||
|
||||
return currentUser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void isAuthenticated() {
|
||||
authenticatedUser();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AppUser getAuthenticatedUserIfPresent() {
|
||||
|
||||
AppUser currentUser = null;
|
||||
final SecurityContext context = SecurityContextHolder.getContext();
|
||||
if (context != null) {
|
||||
final Authentication auth = context.getAuthentication();
|
||||
if (auth != null) {
|
||||
currentUser = (AppUser) auth.getPrincipal();
|
||||
}
|
||||
}
|
||||
|
||||
if (currentUser == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (this.doesPasswordHasToBeRenewed(currentUser)) {
|
||||
throw new ResetPasswordException(currentUser.getId());
|
||||
}
|
||||
|
||||
return currentUser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AppUser authenticatedUser(CommandWrapper commandWrapper) {
|
||||
|
||||
AppUser currentUser = null;
|
||||
final SecurityContext context = SecurityContextHolder.getContext();
|
||||
if (context != null) {
|
||||
final Authentication auth = context.getAuthentication();
|
||||
if (auth != null) {
|
||||
currentUser = (AppUser) auth.getPrincipal();
|
||||
}
|
||||
}
|
||||
|
||||
if (currentUser == null) {
|
||||
throw new UnAuthenticatedUserException();
|
||||
}
|
||||
|
||||
if (this.shouldCheckForPasswordForceReset(commandWrapper) && this.doesPasswordHasToBeRenewed(currentUser)) {
|
||||
throw new ResetPasswordException(currentUser.getId());
|
||||
}
|
||||
|
||||
return currentUser;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateAccessRights(final String resourceOfficeHierarchy) {
|
||||
|
||||
final AppUser user = authenticatedUser();
|
||||
final String userOfficeHierarchy = user.getOffice().getHierarchy();
|
||||
|
||||
if (!resourceOfficeHierarchy.startsWith(userOfficeHierarchy)) {
|
||||
throw new NoAuthorizationException("The user doesn't have enough permissions to access the resource.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String officeHierarchy() {
|
||||
return authenticatedUser().getOffice().getHierarchy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doesPasswordHasToBeRenewed(AppUser currentUser) {
|
||||
|
||||
if (this.configurationDomainService.isPasswordForcedResetEnable() && !currentUser.getPasswordNeverExpires()) {
|
||||
|
||||
Long passwordDurationDays = this.configurationDomainService.retrievePasswordLiveTime();
|
||||
final LocalDate passWordLastUpdateDate = currentUser.getLastTimePasswordUpdated();
|
||||
|
||||
final LocalDate passwordExpirationDate = passWordLastUpdateDate.plusDays(passwordDurationDays);
|
||||
|
||||
if (DateUtils.isBeforeTenantDate(passwordExpirationDate)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
private boolean shouldCheckForPasswordForceReset(CommandWrapper commandWrapper) {
|
||||
for (CommandWrapper commandItem : EXEMPT_FROM_PASSWORD_RESET_CHECK) {
|
||||
if (commandItem.actionName().equals(commandWrapper.actionName())
|
||||
&& commandItem.getEntityName().equals(commandWrapper.getEntityName())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
/**
|
||||
* 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.service;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.Set;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.fineract.infrastructure.core.service.database.DatabaseTypeResolver;
|
||||
import org.apache.fineract.infrastructure.security.exception.EscapeSqlLiteralException;
|
||||
import org.postgresql.core.Utils;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class SqlInjectionPreventerServiceImpl implements SqlInjectionPreventerService {
|
||||
|
||||
private final DatabaseTypeResolver databaseTypeResolver;
|
||||
|
||||
public SqlInjectionPreventerServiceImpl(DatabaseTypeResolver databaseTypeResolver) {
|
||||
this.databaseTypeResolver = databaseTypeResolver;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String encodeSql(String literal) {
|
||||
if (literal == null) {
|
||||
return "NULL";
|
||||
}
|
||||
|
||||
if (databaseTypeResolver.isMySQL()) {
|
||||
return escapeMySQLLiteral(literal);
|
||||
} else if (databaseTypeResolver.isPostgreSQL()) {
|
||||
try {
|
||||
return Utils.escapeLiteral(null, literal, true).toString();
|
||||
} catch (SQLException e) {
|
||||
throw new EscapeSqlLiteralException("Failed to escape SQL literal", e);
|
||||
}
|
||||
} else {
|
||||
// For unknown database types, return the input unchanged
|
||||
// This maintains backward compatibility while still providing basic protection for known types
|
||||
return literal;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes a string literal for MySQL/MariaDB using native MySQL standard escaping rules. This method replaces the
|
||||
* vulnerable ESAPI SQL encoding to address CVE-2025-5878.
|
||||
*
|
||||
* According to MySQL documentation, the following characters need to be escaped: - Single quote (') -> \' - Double
|
||||
* quote (") -> \" - Backslash (\) -> \\ - Null byte (\0) -> \\0 - Newline (\n) -> \\n - Carriage return (\r) -> \\r
|
||||
* - Tab (\t) -> \\t - ASCII 26 (Ctrl+Z) -> \\Z
|
||||
*
|
||||
* @param literal
|
||||
* the string literal to escape
|
||||
* @return the escaped string literal safe for MySQL/MariaDB SQL queries
|
||||
*/
|
||||
private String escapeMySQLLiteral(String literal) {
|
||||
|
||||
StringBuilder escaped = new StringBuilder(literal.length() * 2);
|
||||
|
||||
for (int i = 0; i < literal.length(); i++) {
|
||||
char c = literal.charAt(i);
|
||||
|
||||
switch (c) {
|
||||
case '\0': // Null byte
|
||||
escaped.append("\\0");
|
||||
break;
|
||||
case '\'':
|
||||
escaped.append("\\'");
|
||||
break;
|
||||
case '\"':
|
||||
escaped.append("\\\"");
|
||||
break;
|
||||
case '\\':
|
||||
escaped.append("\\\\");
|
||||
break;
|
||||
case '\n':
|
||||
escaped.append("\\n");
|
||||
break;
|
||||
case '\r':
|
||||
escaped.append("\\r");
|
||||
break;
|
||||
case '\t':
|
||||
escaped.append("\\t");
|
||||
break;
|
||||
case '\032':
|
||||
escaped.append("\\Z");
|
||||
break;
|
||||
default:
|
||||
escaped.append(c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return escaped.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String quoteIdentifier(String identifier, Set<String> allowedValues) {
|
||||
if (StringUtils.isBlank(identifier)) {
|
||||
throw new IllegalArgumentException("Identifier cannot be null or empty");
|
||||
}
|
||||
|
||||
// Whitelist validation if provided
|
||||
if (allowedValues != null && !allowedValues.contains(identifier.toLowerCase())) {
|
||||
throw new IllegalArgumentException("Identifier not in whitelist: " + identifier);
|
||||
}
|
||||
|
||||
return quoteIdentifier(identifier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String quoteIdentifier(String identifier) {
|
||||
if (StringUtils.isBlank(identifier)) {
|
||||
throw new IllegalArgumentException("Identifier cannot be null or empty");
|
||||
}
|
||||
|
||||
// Validate identifier contains only safe characters (alphanumeric and underscore)
|
||||
if (!identifier.matches("^[a-zA-Z_][a-zA-Z0-9_]*$")) {
|
||||
throw new IllegalArgumentException("Invalid identifier format: " + identifier);
|
||||
}
|
||||
|
||||
if (databaseTypeResolver.isMySQL()) {
|
||||
// MySQL/MariaDB uses backticks for identifier quoting
|
||||
return "`" + identifier.replace("`", "``") + "`";
|
||||
} else if (databaseTypeResolver.isPostgreSQL()) {
|
||||
// PostgreSQL uses double quotes for identifier quoting
|
||||
return "\"" + identifier.replace("\"", "\"\"") + "\"";
|
||||
} else {
|
||||
// For unknown database types, return identifier as-is if it passes validation
|
||||
// This maintains backward compatibility while still providing basic protection
|
||||
return identifier;
|
||||
}
|
||||
}
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* 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.service;
|
||||
|
||||
import org.apache.fineract.infrastructure.security.domain.PlatformUser;
|
||||
import org.apache.fineract.infrastructure.security.domain.PlatformUserRepository;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* Used in securityContext.xml as implementation of spring security's {@link UserDetailsService}.
|
||||
*/
|
||||
@Service("userDetailsService")
|
||||
public class TenantAwareJpaPlatformUserDetailsService implements PlatformUserDetailsService {
|
||||
|
||||
@Autowired
|
||||
private PlatformUserRepository platformUserRepository;
|
||||
|
||||
@Override
|
||||
@Cacheable(value = "usersByUsername", key = "T(org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier().concat(#username+'ubu')")
|
||||
public UserDetails loadUserByUsername(final String username) throws UsernameNotFoundException, DataAccessException {
|
||||
|
||||
// Retrieve active users only
|
||||
final boolean deleted = false;
|
||||
final boolean enabled = true;
|
||||
|
||||
final PlatformUser appUser = this.platformUserRepository.findByUsernameAndDeletedAndEnabled(username, deleted, enabled);
|
||||
|
||||
if (appUser == null) {
|
||||
throw new UsernameNotFoundException(username + ": not found");
|
||||
}
|
||||
|
||||
return appUser;
|
||||
}
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* 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.service;
|
||||
|
||||
import java.util.Map;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.security.data.OTPRequest;
|
||||
import org.apache.fineract.useradministration.domain.AppUser;
|
||||
|
||||
public interface TwoFactorConfigurationService {
|
||||
|
||||
Map<String, Object> retrieveAll();
|
||||
|
||||
boolean isSMSEnabled();
|
||||
|
||||
Integer getSMSProviderId();
|
||||
|
||||
String getSmsText();
|
||||
|
||||
boolean isEmailEnabled();
|
||||
|
||||
String getEmailSubject();
|
||||
|
||||
String getEmailBody();
|
||||
|
||||
String getFormattedEmailSubjectFor(AppUser user, OTPRequest request);
|
||||
|
||||
String getFormattedEmailBodyFor(AppUser user, OTPRequest request);
|
||||
|
||||
String getFormattedSmsTextFor(AppUser user, OTPRequest request);
|
||||
|
||||
Integer getOTPTokenLength();
|
||||
|
||||
Integer getOTPTokenLiveTime();
|
||||
|
||||
Integer getAccessTokenLiveTime();
|
||||
|
||||
Integer getAccessTokenExtendedLiveTime();
|
||||
|
||||
Map<String, Object> update(JsonCommand command);
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* 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.service;
|
||||
|
||||
import java.util.List;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.security.data.OTPDeliveryMethod;
|
||||
import org.apache.fineract.infrastructure.security.data.OTPRequest;
|
||||
import org.apache.fineract.infrastructure.security.domain.TFAccessToken;
|
||||
import org.apache.fineract.useradministration.domain.AppUser;
|
||||
|
||||
public interface TwoFactorService {
|
||||
|
||||
List<OTPDeliveryMethod> getDeliveryMethodsForUser(AppUser user);
|
||||
|
||||
OTPRequest createNewOTPToken(AppUser user, String deliveryMethodName, boolean extendedAccessToken);
|
||||
|
||||
TFAccessToken createAccessTokenFromOTP(AppUser user, String otpToken);
|
||||
|
||||
void validateTwoFactorAccessToken(AppUser user, String token);
|
||||
|
||||
TFAccessToken fetchAccessTokenForUser(AppUser user, String token);
|
||||
|
||||
TFAccessToken invalidateAccessToken(AppUser user, JsonCommand command);
|
||||
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* 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.service;
|
||||
|
||||
import java.util.UUID;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class UUIDAccessTokenGenerationService implements AccessTokenGenerationService {
|
||||
|
||||
@Override
|
||||
public String generateRandomToken() {
|
||||
return UUID.randomUUID().toString().replaceAll("-", "");
|
||||
}
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* 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.vote;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
import org.apache.fineract.useradministration.domain.AppUser;
|
||||
import org.springframework.security.authorization.AuthorizationDecision;
|
||||
import org.springframework.security.authorization.AuthorizationManager;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.web.access.intercept.RequestAuthorizationContext;
|
||||
|
||||
public class SelfServiceUserAuthorizationManager implements AuthorizationManager<RequestAuthorizationContext> {
|
||||
|
||||
@Override
|
||||
public AuthorizationDecision check(Supplier<Authentication> authentication, RequestAuthorizationContext fi) {
|
||||
if (!"OPTIONS".equalsIgnoreCase(fi.getRequest().getMethod())) {
|
||||
AppUser user = (AppUser) authentication.get().getPrincipal();
|
||||
|
||||
String pathURL = fi.getRequest().getRequestURL().toString();
|
||||
boolean isSelfServiceRequest = pathURL.contains("/self/");
|
||||
|
||||
boolean notAllowed = ((isSelfServiceRequest && !user.isSelfServiceUser())
|
||||
|| (!isSelfServiceRequest && user.isSelfServiceUser()));
|
||||
|
||||
if (notAllowed) {
|
||||
return new AuthorizationDecision(false);
|
||||
}
|
||||
}
|
||||
return new AuthorizationDecision(true);
|
||||
}
|
||||
|
||||
public static SelfServiceUserAuthorizationManager selfServiceUserAuthManager() {
|
||||
return new SelfServiceUserAuthorizationManager();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user