Two-Factor Authentication Technical Spec: https://mifosforge.jira.com/wiki/spaces/projects/pages/185277689/GSoC+2017+-+Two-Factor+Authentication
Project: http://git-wip-us.apache.org/repos/asf/fineract/repo Commit: http://git-wip-us.apache.org/repos/asf/fineract/commit/1a966e8e Tree: http://git-wip-us.apache.org/repos/asf/fineract/tree/1a966e8e Diff: http://git-wip-us.apache.org/repos/asf/fineract/diff/1a966e8e Branch: refs/heads/develop Commit: 1a966e8e735dce586feae1e1c1372902a03f31e4 Parents: b56cc43 Author: Alex Ivanov <[email protected]> Authored: Mon Jun 12 20:20:26 2017 +0100 Committer: Alex Ivanov <[email protected]> Committed: Fri Sep 22 14:24:52 2017 +0100 ---------------------------------------------------------------------- api-docs/apiLive.htm | 242 +++++++++++++++ api-docs/apidocs.css | 4 + fineract-provider/build.gradle | 36 ++- .../basicauth/twofactor/application.properties | 21 ++ .../oauth/twofactor/application.properties | 21 ++ .../commands/service/CommandWrapperBuilder.java | 16 +- .../domain/ConfigurationDomainService.java | 9 +- .../domain/ConfigurationDomainServiceJpa.java | 37 +++ .../boot/AbstractApplicationConfiguration.java | 3 +- .../core/boot/WebTwoFactorXmlConfiguration.java | 36 +++ .../security/api/AuthenticationApiResource.java | 14 +- .../security/api/TwoFactorApiResource.java | 132 ++++++++ .../api/TwoFactorConfigurationApiResource.java | 86 ++++++ .../security/api/UserDetailsApiResource.java | 17 +- .../InvalidateTFAccessTokenCommandHandler.java | 108 +++++++ .../UpdateTwoFactorConfigCommandHandler.java | 60 ++++ .../TwoFactorConfigurationConstants.java | 59 ++++ .../security/constants/TwoFactorConstants.java | 30 ++ .../security/data/AccessTokenData.java | 47 +++ .../data/AuthenticatedOauthUserData.java | 12 +- .../security/data/AuthenticatedUserData.java | 12 +- .../security/data/OTPDeliveryMethod.java | 38 +++ .../security/data/OTPMetadata.java | 53 ++++ .../security/data/OTPRequest.java | 53 ++++ .../data/TwoFactorConfigurationValidator.java | 120 ++++++++ .../security/domain/OTPRequestRepository.java | 51 ++++ .../security/domain/TFAccessToken.java | 137 +++++++++ .../domain/TFAccessTokenRepository.java | 31 ++ .../security/domain/TwoFactorConfiguration.java | 84 +++++ .../TwoFactorConfigurationRepository.java | 34 +++ .../exception/AccessTokenInvalidIException.java | 28 ++ .../OTPDeliveryMethodInvalidException.java | 29 ++ .../exception/OTPTokenInvalidException.java | 28 ++ .../InsecureTwoFactorAuthenticationFilter.java | 82 +++++ .../filter/TwoFactorAuthenticationFilter.java | 139 +++++++++ .../service/AccessTokenGenerationService.java | 24 ++ .../security/service/RandomOTPGenerator.java | 38 +++ .../service/TwoFactorConfigurationService.java | 51 ++++ .../TwoFactorConfigurationServiceImpl.java | 304 +++++++++++++++++++ .../security/service/TwoFactorService.java | 43 +++ .../security/service/TwoFactorServiceImpl.java | 229 ++++++++++++++ .../security/service/TwoFactorUtils.java | 47 +++ .../UUIDAccessTokenGenerationService.java | 32 ++ .../useradministration/domain/AppUser.java | 17 ++ .../main/resources/META-INF/spring/ehcache.xml | 4 + .../META-INF/spring/securityContext.xml | 38 ++- .../core_db/V336__two_factor_authentication.sql | 63 ++++ 47 files changed, 2763 insertions(+), 36 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/fineract/blob/1a966e8e/api-docs/apiLive.htm ---------------------------------------------------------------------- diff --git a/api-docs/apiLive.htm b/api-docs/apiLive.htm index bc0c43e..9a87171 100644 --- a/api-docs/apiLive.htm +++ b/api-docs/apiLive.htm @@ -2629,6 +2629,30 @@ <td></td> </tr> <tr> + <td><a href="#twofactor">Two-Factor Authentication</a></td> + <td>twofactor</td> + <td><a href="#twofactor_request">Request OTP</a></td> + <td><a href="#twofactor_deliverymethods">List OTP delivery methods</a></td> + <td></td> + <td></td> + </tr> + <tr> + <td></td> + <td>twofactor/validate</td> + <td><a href="#twofactor_validate">Validate OTP</a></td> + <td></td> + <td></td> + <td></td> + </tr> + <tr> + <td></td> + <td>twofactor/invalidate</td> + <td><a href="#twofactor_invalidate">Invalidate Access Token</a></td> + <td></td> + <td></td> + <td></td> + </tr> + <tr> <td><a href="#users">User</a></td> <td>users</td> <td><a href="#users_create">Create a User</a></td> @@ -2942,6 +2966,16 @@ external service Configuration</a></td> <td></td> </tr> + <tr> + <td><a href="#twofactor_config_get">Two-Factor Configuration</a></td> + <td>twofactor/configure</td> + <td></td> + <td><a href="#twofactor_config_get">Retrieve + Two-Factor Configuration</a></td> + <td><a href="#twofactor_config_update">Update + Two-Factor Configuration</a></td> + <td></td> + </tr> </table> </div> </div> @@ -3931,6 +3965,11 @@ the platform setup <a href="https://github.com/openMF/mifosx/wiki/Launching-platform-server-locally-from-the-command-line#choosing-authentication-mechanism"> wiki</a> for additional details. </p> <p> + Optionally, two-factor authentication can be enabled by using + <i>-Ptwofactor=enabled</i> on gradle build. + Details of the authentication workflow with two-factor authentication enabled can be found <a href="#twofactor">here</a>. + </p> + <p> The platform has been configured to reject plain HTTP requests and to expect all API requests to be made over <a href="http://en.wikipedia.org/wiki/HTTP_Secure">HTTPS</a>. All @@ -4057,6 +4096,142 @@ function executeAjaxRequest(url, verbType, jsonData, authKey, successFunction, e </code> </div> </div> + <a id="twofactor" name="twofactor" class="old-syle-anchor"> </a> + <div class="method-section"> + <div class="method-description"> + <h3>Two-Factor Authentication</h3> + <p> + Two-Factor authentication is supported by requesting & verifying + one-time passwords(OTP). OTPs are sent via SMS & email. + </p> + <p> + By default, two-factor authentication is disabled by default. + More information on how to enable TFA can be found <a href="#authentication_overview">here</a>. + </p> + <p> + Two-factor authentication workflow: + <ol class="normalli"> + <li class="normalli">User authticates via BasicAuth / oAauth</li> + <li>Client requests a list of supported OTP delivery methods for the authenticated user(<a href="#twofactor_deliverymethods">Get Delivery Methods</a>)</li> + <li>User selects an OTP delivery method and client sends a request for OTP(<a href="#twofactor_request">Request OTP</a>)</li> + <li>User receives an OTP and the client sends it for verification(<a href="#twofactor_validate">Validate OTP</a>)</li> + <li>If the OTP is valid, an access token is returned</li> + <li>The access token is sent in following requestes to the server as a header <i>Fineract-Platform-TFA-Token</i></li> + <li>On session end, the access token should be invalidated<a href="#twofactor_invalidate">Invalidate Access Token</a>)</li> + </ol> + </p> + <p> + Two-Factor authentication and delivery methods can be configured via + the <a href="#twofactor_configure"<i>/twofactor/configure</i> endpoint.</a> + </p> + </div> + <div class="method-example"> + + </div> + </div> + + <a id="twofactor_deliverymethods" name="twofactor_deliverymethods" class="old-syle-anchor"> </a> + <div class="method-section"> + <div class="method-description"> + <h4>Get Delivery Methods</h4> + <p>Returns a list of possible OTP delivery methods for the current user</p> + <p>Requires first-factor authenticated user.</p> + </div> + <div class="method-example"> + <code class="method-declaration">GET https://DomainName/api/v1/twofactor</code> + <code class="method-response"> +[ + { + "name": "sms", + "target": "08888888888" + }, + { + "name": "email", + "target": "[email protected]" + } +]</code> + </div> + </div> + + <a id="twofactor_request" name="twofactor_request" class="old-syle-anchor"> </a> + <div class="method-section"> + <div class="method-description"> + <h4>Request OTP</h4> + <p>Requests an OTP.</p> + <p>Requires first-factor authenticated user.</p> + <h5>Arguments</h5> + <dl class="argument-list"> + <dt>deliveryMethod</dt> + <dd> + String<span> mandatory, the delivery method name</span> + </dd> + <dt>extendedToken</dt> + <dd> + boolean<span> optional, whether to request an extended token, default false</span> + </dd> + </dl> + </div> + <div class="method-example"> + <code class="method-declaration">POST https://DomainName/api/v1/twofactor?deliveryMethod=sms&extendedToken=false</code> + <code class="method-response"> +{ + "requestTime": 1500000000000, + "tokenLiveTimeInSec": 300, + "extendedAccessToken": false, + "deliveryMethod": { + "name": "sms", + "target": "08888888888" + } +}</code> + </div> + </div> + + <a id="twofactor_validate" name="twofactor_validate" class="old-syle-anchor"> </a> + <div class="method-section"> + <div class="method-description"> + <h4>Validate OTP</h4> + <p>Validates an OTP. If the OTP is valid, an access token is created.</p> + <p>The returned access token is later sent as a header <i>Fineract-Platform-TFA-Token</i>.</p> + <p>Requires first-factor authenticated user.</p> + <h5>Arguments</h5> + <dl class="argument-list"> + <dt>token</dt> + <dd> + String<span> mandatory, the OTP to validate</span> + </dd> + </dl> + </div> + <div class="method-example"> + <code class="method-declaration">POST https://DomainName/api/v1/twofactor/validate?token=YYYYY</code> + <code class="method-response"> +{ + "token": "cb0bb6e33fc540709d50a16eb2e555f9", + "validFrom": 1501530702801, + "validTo": 1501617102801 +}</code> + </div> + </div> + + <a id="twofactor_invalidate" name="twofactor_invalidate" class="old-syle-anchor"> </a> + <div class="method-section"> + <div class="method-description"> + <h4>Invalidate Access Token</h4> + <p>Invalidates an access token.</p> + <p>Two factor access tokens should be invalidated on logout.</p> + <p>Requires fully authenticated user.</p> + </div> + <div class="method-example"> + <code class="method-declaration">POST https://DomainName/api/v1/twofactor/invalidate</code> + <code class="method-request"> +{ + "token": "cb0bb6e33fc540709d50a16eb2e555f9" +}</code> + <code class="method-response"> +{ + "resourceIdentifier": "cb0bb6e33fc540709d50a16eb2e555f9" +}</code> + </div> + </div> <a id="batch_api" name="batch_api" class="old-syle-anchor"> </a> <div class="method-section"> @@ -19391,6 +19566,73 @@ Content-Type: application/json </div> </div> + <a id="twofactor_config" name="twofactor_config" + class="old-syle-anchor"> </a> + <div class="method-section"> + <div class="method-description"> + <h3>Two-Factor Configuration</h3> + <p>The following section describes the way to configure two-factor authentication</p> + <p>Two-Factor Authentication has to be enabled by either building with Gradle arguments + <i>-Ptwofactor=enabled</i> or enabling the <i>twofactor</i> profile via env. variable + </p> + <p>In order for SMS to be enabled an SMS bridge has to be setup with the message-gateway service.</p> + </div> + <div class="method-example"> + </div> + </div> + + <a id="twofactor_config_get" name="twofactor_config_get" class="old-syle-anchor"> </a> + <div class="method-section"> + <div class="method-description"> + <h4>Retrieve Two-Factor Configuration</h4> + <p>Returns available two-factor configuration.</p> + </div> + <div class="method-example"> + <code class="method-declaration"> GET https://DomainName/api/v1/twofactor/configure + </code> + <code class="method-response"> +{ + "otp-delivery-email-body": "Hello {{username}}.\n\nYour OTP login token is {{token}}.", + "otp-delivery-sms-enable": true, + "otp-delivery-sms-provider": 6, + "otp-delivery-email-subject": "Fineract Two-Factor Authentication Token", + "otp-token-length": 5, + "access-token-live-time-extended": 604800, + "otp-delivery-email-enable": true, + "otp-token-live-time": 300, + "otp-delivery-sms-text": "Your authentication token for Fineract is {{token}}.", + "access-token-live-time": 86400 +} + </code> + </div> + </div> + + <a id="twofactor_config_update" name="twofactor_config_update" class="old-syle-anchor"> </a> + <div class="method-section"> + <div class="method-description"> + <h4>Update Two-Factor Configuration</h4> + <p>Update two-factor configuration.</p> + </div> + <div class="method-example"> + <code class="method-declaration"> PUT https://DomainName/api/v1/twofactor/configure + </code> + <code class="method-request"> +{ + "otp-delivery-sms-provider": 7 + "otp-delivery-sms-enable": false +} + </code> + <code class="method-response"> +{ + "changes": { + "otp-delivery-sms-enable": false, + "otp-delivery-sms-provider": 7 + } +} + </code> + </div> + </div> + <!-- Fund starts here --> <a id="funds" name="funds" class="old-syle-anchor"> </a> <div class="method-section"> http://git-wip-us.apache.org/repos/asf/fineract/blob/1a966e8e/api-docs/apidocs.css ---------------------------------------------------------------------- diff --git a/api-docs/apidocs.css b/api-docs/apidocs.css index f8816a7..21bc11c 100644 --- a/api-docs/apidocs.css +++ b/api-docs/apidocs.css @@ -745,4 +745,8 @@ ul.field li { tt { font-size: 9.5pt; +} + +ol.normalli li { + list-style-type: decimal; } \ No newline at end of file http://git-wip-us.apache.org/repos/asf/fineract/blob/1a966e8e/fineract-provider/build.gradle ---------------------------------------------------------------------- diff --git a/fineract-provider/build.gradle b/fineract-provider/build.gradle index 771fcde..5a2b8b3 100644 --- a/fineract-provider/build.gradle +++ b/fineract-provider/build.gradle @@ -193,17 +193,33 @@ if (project.hasProperty('env') && project.getProperty('env') == 'dev') { /* Enable Oauth2 authentication based on environment, default to HTTP basic auth */ if (project.hasProperty('security') && project.getProperty('security') == 'oauth') { - copy { - from './properties/oauth/' - into 'src/main/resources/' - include '*.properties' - } + if(project.hasProperty('twofactor') && project.getProperty('twofactor') == 'enabled') { + copy { + from './properties/oauth/twofactor/' + into 'src/main/resources/' + include '*.properties' + } + } else { + copy { + from './properties/oauth/' + into 'src/main/resources/' + include '*.properties' + } + } } else { - copy { - from './properties/basicauth/' - into 'src/main/resources/' - include '*.properties' - } + if(project.hasProperty('twofactor') && project.getProperty('twofactor') == 'enabled') { + copy { + from './properties/basicauth/twofactor/' + into 'src/main/resources/' + include '*.properties' + } + } else { + copy { + from './properties/basicauth/' + into 'src/main/resources/' + include '*.properties' + } + } } task dist(type:Zip){ http://git-wip-us.apache.org/repos/asf/fineract/blob/1a966e8e/fineract-provider/properties/basicauth/twofactor/application.properties ---------------------------------------------------------------------- diff --git a/fineract-provider/properties/basicauth/twofactor/application.properties b/fineract-provider/properties/basicauth/twofactor/application.properties new file mode 100644 index 0000000..5b4d496 --- /dev/null +++ b/fineract-provider/properties/basicauth/twofactor/application.properties @@ -0,0 +1,21 @@ +# +# 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. +# + +spring.profiles.default=basicauth +spring.profiles.active=basicauth,twofactor \ No newline at end of file http://git-wip-us.apache.org/repos/asf/fineract/blob/1a966e8e/fineract-provider/properties/oauth/twofactor/application.properties ---------------------------------------------------------------------- diff --git a/fineract-provider/properties/oauth/twofactor/application.properties b/fineract-provider/properties/oauth/twofactor/application.properties new file mode 100644 index 0000000..12cbfbe --- /dev/null +++ b/fineract-provider/properties/oauth/twofactor/application.properties @@ -0,0 +1,21 @@ +# +# 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. +# + +spring.profiles.default=basicauth +spring.profiles.active=oauth,twofactor \ No newline at end of file http://git-wip-us.apache.org/repos/asf/fineract/blob/1a966e8e/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java ---------------------------------------------------------------------- diff --git a/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java b/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java index 0ad3612..93bd160 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java +++ b/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java @@ -2929,7 +2929,7 @@ public class CommandWrapperBuilder { this.href = "/smscampaigns/"+resourceId; return this; } - + public CommandWrapperBuilder holdAmount(final Long accountId) { this.actionName = "HOLDAMOUNT"; this.entityName = "SAVINGSACCOUNT"; @@ -3041,4 +3041,18 @@ public class CommandWrapperBuilder { this.json = "{}"; return this; } + + public CommandWrapperBuilder invalidateTwoFactorAccessToken() { + this.actionName = "INVALIDATE"; + this.entityName = "TWOFACTOR_ACCESSTOKEN"; + this.href = "/twofactor/invalidate"; + return this; + } + + public CommandWrapperBuilder updateTwoFactorConfiguration() { + this.actionName = "UPDATE"; + this.entityName = "TWOFACTOR_CONFIGURATION"; + this.href = "/twofactor/configure"; + return this; + } } http://git-wip-us.apache.org/repos/asf/fineract/blob/1a966e8e/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java ---------------------------------------------------------------------- diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java index 4abbb4b..f2eab64 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java @@ -85,5 +85,12 @@ public interface ConfigurationDomainService { Long getDailyTPTLimit(); void removeGlobalConfigurationPropertyDataFromCache(String propertyName); - + + boolean isSMSOTPDeliveryEnabled(); + + boolean isEmailOTPDeliveryEnabled(); + + Integer retrieveOTPCharacterLength(); + + Integer retrieveOTPLiveTime(); } \ No newline at end of file http://git-wip-us.apache.org/repos/asf/fineract/blob/1a966e8e/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java ---------------------------------------------------------------------- diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java index 95170f8..3d51ddd 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java @@ -287,6 +287,43 @@ public class ConfigurationDomainServiceJpa implements ConfigurationDomainService configurations.remove(key); } + @Override + public boolean isSMSOTPDeliveryEnabled() { + final String propertyName = "use-sms-for-2fa"; + final GlobalConfigurationPropertyData property = getGlobalConfigurationPropertyData(propertyName); + return property.isEnabled(); + } + + @Override + public boolean isEmailOTPDeliveryEnabled() { + final String propertyName = "use-email-for-2fa"; + final GlobalConfigurationPropertyData property = getGlobalConfigurationPropertyData(propertyName); + return property.isEnabled(); + } + + @Override + public Integer retrieveOTPCharacterLength() { + final String propertyName = "otp-character-length"; + final GlobalConfigurationPropertyData property = getGlobalConfigurationPropertyData(propertyName); + int defaultValue = 6; + int value = property.getValue().intValue(); + if(value < 1) + return defaultValue; + return value; + } + + @Override + public Integer retrieveOTPLiveTime() { + final String propertyName = "otp-validity-period"; + final GlobalConfigurationPropertyData property = getGlobalConfigurationPropertyData(propertyName); + int defaultValue = 300; + int value = property.getValue().intValue(); + if(value < 1) { + return defaultValue; + } + return value; + } + private GlobalConfigurationPropertyData getGlobalConfigurationPropertyData(final String propertyName) { String identifier = ThreadLocalContextUtil.getTenant().getTenantIdentifier(); String key = identifier + "_" + propertyName; http://git-wip-us.apache.org/repos/asf/fineract/blob/1a966e8e/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/AbstractApplicationConfiguration.java ---------------------------------------------------------------------- diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/AbstractApplicationConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/AbstractApplicationConfiguration.java index 7823a0d..dd8fc20 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/AbstractApplicationConfiguration.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/AbstractApplicationConfiguration.java @@ -40,7 +40,8 @@ import org.springframework.context.annotation.PropertySource; * and MariaDB4j (because those differ in the subclasses). */ @Configuration -@Import({ WebXmlConfiguration.class, WebXmlOauthConfiguration.class, WebFrontEndConfiguration.class }) +@Import({ WebXmlConfiguration.class, WebXmlOauthConfiguration.class, WebFrontEndConfiguration.class, + WebTwoFactorXmlConfiguration.class }) @ImportResource({ "classpath*:META-INF/spring/appContext.xml" }) @PropertySource(value="classpath:META-INF/spring/jdbc.properties") @EnableAutoConfiguration(exclude = { DataSourceAutoConfiguration.class, http://git-wip-us.apache.org/repos/asf/fineract/blob/1a966e8e/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/WebTwoFactorXmlConfiguration.java ---------------------------------------------------------------------- diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/WebTwoFactorXmlConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/WebTwoFactorXmlConfiguration.java new file mode 100644 index 0000000..e8f1688 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/WebTwoFactorXmlConfiguration.java @@ -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. + */ +package org.apache.fineract.infrastructure.core.boot; + +import org.apache.fineract.infrastructure.security.filter.TwoFactorAuthenticationFilter; +import org.springframework.boot.context.embedded.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + + +@Configuration +public class WebTwoFactorXmlConfiguration { + + @Bean + public FilterRegistrationBean twoFactorFilterBean(TwoFactorAuthenticationFilter filter) { + FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter); + registrationBean.setEnabled(false); + return registrationBean; + } +} http://git-wip-us.apache.org/repos/asf/fineract/blob/1a966e8e/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/api/AuthenticationApiResource.java ---------------------------------------------------------------------- diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/api/AuthenticationApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/api/AuthenticationApiResource.java index 1638359..e5c20ed 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/api/AuthenticationApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/api/AuthenticationApiResource.java @@ -30,8 +30,10 @@ import javax.ws.rs.core.MediaType; import org.apache.fineract.infrastructure.core.data.EnumOptionData; import org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer; +import org.apache.fineract.infrastructure.security.constants.TwoFactorConstants; import org.apache.fineract.infrastructure.security.data.AuthenticatedUserData; import org.apache.fineract.infrastructure.security.service.SpringSecurityPlatformSecurityContext; +import org.apache.fineract.infrastructure.security.service.TwoFactorUtils; import org.apache.fineract.useradministration.data.RoleData; import org.apache.fineract.useradministration.domain.AppUser; import org.apache.fineract.useradministration.domain.Role; @@ -56,15 +58,17 @@ public class AuthenticationApiResource { private final DaoAuthenticationProvider customAuthenticationProvider; private final ToApiJsonSerializer<AuthenticatedUserData> apiJsonSerializerService; private final SpringSecurityPlatformSecurityContext springSecurityPlatformSecurityContext; + private final TwoFactorUtils twoFactorUtils; @Autowired public AuthenticationApiResource( @Qualifier("customAuthenticationProvider") final DaoAuthenticationProvider customAuthenticationProvider, final ToApiJsonSerializer<AuthenticatedUserData> apiJsonSerializerService, - final SpringSecurityPlatformSecurityContext springSecurityPlatformSecurityContext) { + final SpringSecurityPlatformSecurityContext springSecurityPlatformSecurityContext, TwoFactorUtils twoFactorUtils) { this.customAuthenticationProvider = customAuthenticationProvider; this.apiJsonSerializerService = apiJsonSerializerService; this.springSecurityPlatformSecurityContext = springSecurityPlatformSecurityContext; + this.twoFactorUtils = twoFactorUtils; } @POST @@ -100,12 +104,16 @@ public class AuthenticationApiResource { final EnumOptionData organisationalRole = principal.organisationalRoleData(); + boolean isTwoFactorRequired = twoFactorUtils.isTwoFactorAuthEnabled() && ! + principal.hasSpecificPermissionTo(TwoFactorConstants.BYPASS_TWO_FACTOR_PERMISSION); if (this.springSecurityPlatformSecurityContext.doesPasswordHasToBeRenewed(principal)) { - authenticatedUserData = new AuthenticatedUserData(username, principal.getId(), new String(base64EncodedAuthenticationKey)); + authenticatedUserData = new AuthenticatedUserData(username, principal.getId(), + new String(base64EncodedAuthenticationKey), isTwoFactorRequired); } else { authenticatedUserData = new AuthenticatedUserData(username, officeId, officeName, staffId, staffDisplayName, - organisationalRole, roles, permissions, principal.getId(), new String(base64EncodedAuthenticationKey)); + organisationalRole, roles, permissions, principal.getId(), + new String(base64EncodedAuthenticationKey), isTwoFactorRequired); } } http://git-wip-us.apache.org/repos/asf/fineract/blob/1a966e8e/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/api/TwoFactorApiResource.java ---------------------------------------------------------------------- diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/api/TwoFactorApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/api/TwoFactorApiResource.java new file mode 100644 index 0000000..482906a --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/api/TwoFactorApiResource.java @@ -0,0 +1,132 @@ +/** + * 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 java.util.List; +import java.util.Map; + +import javax.ws.rs.DefaultValue; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.UriInfo; + +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.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Profile; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +@Path("/twofactor") +@Component +@Profile("twofactor") +@Scope("singleton") +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; + + + + @Autowired + public TwoFactorApiResource(ToApiJsonSerializer<OTPMetadata> otpRequestSerializer, + ToApiJsonSerializer<OTPDeliveryMethod> otpDeliveryMethodSerializer, + ToApiJsonSerializer<AccessTokenData> accessTokenSerializer, + DefaultToApiJsonSerializer<Map<String, Object>> toApiJsonSerializer, + PlatformSecurityContext context, + PortfolioCommandSourceWritePlatformService + commandsSourceWritePlatformService, + TwoFactorService twoFactorService) { + this.otpRequestSerializer = otpRequestSerializer; + this.otpDeliveryMethodSerializer = otpDeliveryMethodSerializer; + this.accessTokenSerializer = accessTokenSerializer; + this.toApiJsonSerializer = toApiJsonSerializer; + this.context = context; + this.commandsSourceWritePlatformService = commandsSourceWritePlatformService; + this.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); + } +} http://git-wip-us.apache.org/repos/asf/fineract/blob/1a966e8e/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/api/TwoFactorConfigurationApiResource.java ---------------------------------------------------------------------- diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/api/TwoFactorConfigurationApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/api/TwoFactorConfigurationApiResource.java new file mode 100644 index 0000000..13fd69e --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/api/TwoFactorConfigurationApiResource.java @@ -0,0 +1,86 @@ +/** + * 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 java.util.Map; + +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +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.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Profile; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +@Path("/twofactor/configure") +@Consumes({ MediaType.APPLICATION_JSON }) +@Produces({ MediaType.APPLICATION_JSON }) +@Component +@Scope("singleton") +@Profile("twofactor") +public class TwoFactorConfigurationApiResource { + + private final String resourceNameForPermissions = "TWOFACTOR_CONFIG"; + + private final PlatformSecurityContext context; + private final TwoFactorConfigurationService configurationService; + private final DefaultToApiJsonSerializer<Map<String, Object>> toApiJsonSerializer; + private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService; + + @Autowired + public TwoFactorConfigurationApiResource(PlatformSecurityContext context, + TwoFactorConfigurationService configurationService, + DefaultToApiJsonSerializer<Map<String, Object>> toApiJsonSerializer, + PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService) { + this.context = context; + this.configurationService = configurationService; + this.toApiJsonSerializer = toApiJsonSerializer; + this.commandsSourceWritePlatformService = commandsSourceWritePlatformService; + } + + + @GET + public String retrieveAll() { + this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions); + 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); + } +} http://git-wip-us.apache.org/repos/asf/fineract/blob/1a966e8e/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/api/UserDetailsApiResource.java ---------------------------------------------------------------------- diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/api/UserDetailsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/api/UserDetailsApiResource.java index 67f0616..d05f589 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/api/UserDetailsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/api/UserDetailsApiResource.java @@ -30,8 +30,10 @@ import javax.ws.rs.core.MediaType; import org.apache.fineract.infrastructure.core.data.EnumOptionData; import org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer; +import org.apache.fineract.infrastructure.security.constants.TwoFactorConstants; import org.apache.fineract.infrastructure.security.data.AuthenticatedOauthUserData; import org.apache.fineract.infrastructure.security.service.SpringSecurityPlatformSecurityContext; +import org.apache.fineract.infrastructure.security.service.TwoFactorUtils; import org.apache.fineract.useradministration.data.RoleData; import org.apache.fineract.useradministration.domain.AppUser; import org.apache.fineract.useradministration.domain.Role; @@ -56,14 +58,17 @@ public class UserDetailsApiResource { private final ResourceServerTokenServices tokenServices; private final ToApiJsonSerializer<AuthenticatedOauthUserData> apiJsonSerializerService; private final SpringSecurityPlatformSecurityContext springSecurityPlatformSecurityContext; + private final TwoFactorUtils twoFactorUtils; @Autowired public UserDetailsApiResource(@Qualifier("tokenServices") final ResourceServerTokenServices tokenServices, final ToApiJsonSerializer<AuthenticatedOauthUserData> apiJsonSerializerService, - final SpringSecurityPlatformSecurityContext springSecurityPlatformSecurityContext) { + final SpringSecurityPlatformSecurityContext springSecurityPlatformSecurityContext, + final TwoFactorUtils twoFactorUtils) { this.tokenServices = tokenServices; this.apiJsonSerializerService = apiJsonSerializerService; this.springSecurityPlatformSecurityContext = springSecurityPlatformSecurityContext; + this.twoFactorUtils = twoFactorUtils; } @GET @@ -96,12 +101,16 @@ public class UserDetailsApiResource { final EnumOptionData organisationalRole = principal.organisationalRoleData(); + final boolean requireTwoFactorAuth = twoFactorUtils.isTwoFactorAuthEnabled() + && !principal.hasSpecificPermissionTo(TwoFactorConstants.BYPASS_TWO_FACTOR_PERMISSION); if (this.springSecurityPlatformSecurityContext.doesPasswordHasToBeRenewed(principal)) { - authenticatedUserData = new AuthenticatedOauthUserData(principal.getUsername(), principal.getId(), accessToken); + authenticatedUserData = new AuthenticatedOauthUserData(principal.getUsername(), + principal.getId(), accessToken, requireTwoFactorAuth); } else { - authenticatedUserData = new AuthenticatedOauthUserData(principal.getUsername(), officeId, officeName, staffId, staffDisplayName, - organisationalRole, roles, permissions, principal.getId(), accessToken); + authenticatedUserData = new AuthenticatedOauthUserData(principal.getUsername(), + officeId, officeName, staffId, staffDisplayName, organisationalRole, roles, + permissions, principal.getId(), accessToken, requireTwoFactorAuth); } return this.apiJsonSerializerService.serialize(authenticatedUserData); } http://git-wip-us.apache.org/repos/asf/fineract/blob/1a966e8e/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/command/InvalidateTFAccessTokenCommandHandler.java ---------------------------------------------------------------------- diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/command/InvalidateTFAccessTokenCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/command/InvalidateTFAccessTokenCommandHandler.java new file mode 100644 index 0000000..1ede412 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/command/InvalidateTFAccessTokenCommandHandler.java @@ -0,0 +1,108 @@ +/** + * 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.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.lang.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.context.annotation.Profile; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.google.gson.JsonElement; +import com.google.gson.reflect.TypeToken; + + +@Service +@CommandType(entity = "TWOFACTOR_ACCESSTOKEN", action = "INVALIDATE") +@Profile("twofactor") +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); + } + } +} http://git-wip-us.apache.org/repos/asf/fineract/blob/1a966e8e/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/command/UpdateTwoFactorConfigCommandHandler.java ---------------------------------------------------------------------- diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/command/UpdateTwoFactorConfigCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/command/UpdateTwoFactorConfigCommandHandler.java new file mode 100644 index 0000000..b8c4e60 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/command/UpdateTwoFactorConfigCommandHandler.java @@ -0,0 +1,60 @@ +/** + * 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.context.annotation.Profile; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@CommandType(entity = "TWOFACTOR_CONFIGURATION", action = "UPDATE") +@Profile("twofactor") +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(); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/fineract/blob/1a966e8e/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/constants/TwoFactorConfigurationConstants.java ---------------------------------------------------------------------- diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/constants/TwoFactorConfigurationConstants.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/constants/TwoFactorConfigurationConstants.java new file mode 100644 index 0000000..22818de --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/constants/TwoFactorConfigurationConstants.java @@ -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.constants; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class 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 = + 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 = + Arrays.asList(EMAIL_SUBJECT, EMAIL_BODY, SMS_MESSAGE_TEXT); + + public static final List<String> BOOLEAN_PARAMETERS = + Arrays.asList(ENABLE_EMAIL_DELIVERY, ENABLE_SMS_DELIVERY); + + public static final List<String> NUMBER_PARAMETERS = + Arrays.asList(SMS_PROVIDER_ID, OTP_TOKEN_LIVE_TIME, OTP_TOKEN_LENGTH, + ACCESS_TOKEN_LIVE_TIME, ACCESS_TOKEN_LIVE_TIME_EXTENDED); + +} http://git-wip-us.apache.org/repos/asf/fineract/blob/1a966e8e/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/constants/TwoFactorConstants.java ---------------------------------------------------------------------- diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/constants/TwoFactorConstants.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/constants/TwoFactorConstants.java new file mode 100644 index 0000000..7997c39 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/constants/TwoFactorConstants.java @@ -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.constants; + +public class 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"; + +} http://git-wip-us.apache.org/repos/asf/fineract/blob/1a966e8e/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/AccessTokenData.java ---------------------------------------------------------------------- diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/AccessTokenData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/AccessTokenData.java new file mode 100644 index 0000000..ebd27f7 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/AccessTokenData.java @@ -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 org.joda.time.DateTime; + +public class AccessTokenData { + + private final String token; + + private final DateTime validFrom; + private final DateTime validTo; + + public AccessTokenData(String token, DateTime validFrom, DateTime validTo) { + this.token = token; + this.validFrom = validFrom; + this.validTo = validTo; + } + + public String getToken() { + return token; + } + + public DateTime getValidFrom() { + return validFrom; + } + + public DateTime getValidTo() { + return validTo; + } +} http://git-wip-us.apache.org/repos/asf/fineract/blob/1a966e8e/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/AuthenticatedOauthUserData.java ---------------------------------------------------------------------- diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/AuthenticatedOauthUserData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/AuthenticatedOauthUserData.java index 830fc67..116daa8 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/AuthenticatedOauthUserData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/AuthenticatedOauthUserData.java @@ -54,6 +54,9 @@ public class AuthenticatedOauthUserData { @SuppressWarnings("unused") private final boolean shouldRenewPassword; + @SuppressWarnings("unused") + private final boolean isTwoFactorAuthenticationRequired; + public AuthenticatedOauthUserData(final String username, final Collection<String> permissions) { this.username = username; this.userId = null; @@ -67,11 +70,13 @@ public class AuthenticatedOauthUserData { this.roles = null; this.permissions = permissions; this.shouldRenewPassword = false; + this.isTwoFactorAuthenticationRequired = false; } public AuthenticatedOauthUserData(final String username, final Long officeId, final String officeName, final Long staffId, final String staffDisplayName, final EnumOptionData organisationalRole, final Collection<RoleData> roles, - final Collection<String> permissions, final Long userId, final String accessToken) { + final Collection<String> permissions, final Long userId, final String accessToken, + final boolean isTwoFactorAuthenticationRequired) { this.username = username; this.officeId = officeId; this.officeName = officeName; @@ -84,9 +89,11 @@ public class AuthenticatedOauthUserData { this.roles = roles; this.permissions = permissions; this.shouldRenewPassword = false; + this.isTwoFactorAuthenticationRequired = isTwoFactorAuthenticationRequired; } - public AuthenticatedOauthUserData(final String username, final Long userId, final String accessToken) { + public AuthenticatedOauthUserData(final String username, final Long userId, final String accessToken, + final boolean isTwoFactorAuthenticationRequired) { this.username = username; this.officeId = null; this.officeName = null; @@ -99,5 +106,6 @@ public class AuthenticatedOauthUserData { this.roles = null; this.permissions = null; this.shouldRenewPassword = true; + this.isTwoFactorAuthenticationRequired = isTwoFactorAuthenticationRequired; } } \ No newline at end of file http://git-wip-us.apache.org/repos/asf/fineract/blob/1a966e8e/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/AuthenticatedUserData.java ---------------------------------------------------------------------- diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/AuthenticatedUserData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/AuthenticatedUserData.java index 513f5df..c4713fc 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/AuthenticatedUserData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/AuthenticatedUserData.java @@ -54,6 +54,9 @@ public class AuthenticatedUserData { @SuppressWarnings("unused") private final boolean shouldRenewPassword; + @SuppressWarnings("unused") + private final boolean isTwoFactorAuthenticationRequired; + public AuthenticatedUserData(final String username, final Collection<String> permissions) { this.username = username; this.userId = null; @@ -67,11 +70,13 @@ public class AuthenticatedUserData { this.roles = null; this.permissions = permissions; this.shouldRenewPassword = false; + this.isTwoFactorAuthenticationRequired = false; } public AuthenticatedUserData(final String username, final Long officeId, final String officeName, final Long staffId, final String staffDisplayName, final EnumOptionData organisationalRole, final Collection<RoleData> roles, - final Collection<String> permissions, final Long userId, final String base64EncodedAuthenticationKey) { + final Collection<String> permissions, final Long userId, final String base64EncodedAuthenticationKey, + final boolean isTwoFactorAuthenticationRequired) { this.username = username; this.officeId = officeId; this.officeName = officeName; @@ -84,9 +89,11 @@ public class AuthenticatedUserData { this.roles = roles; this.permissions = permissions; this.shouldRenewPassword = false; + this.isTwoFactorAuthenticationRequired = isTwoFactorAuthenticationRequired; } - public AuthenticatedUserData(final String username, final Long userId, final String base64EncodedAuthenticationKey) { + public AuthenticatedUserData(final String username, final Long userId, final String base64EncodedAuthenticationKey, + final boolean isTwoFactorAuthenticationRequired) { this.username = username; this.officeId = null; this.officeName = null; @@ -99,5 +106,6 @@ public class AuthenticatedUserData { this.roles = null; this.permissions = null; this.shouldRenewPassword = true; + this.isTwoFactorAuthenticationRequired = isTwoFactorAuthenticationRequired; } } \ No newline at end of file http://git-wip-us.apache.org/repos/asf/fineract/blob/1a966e8e/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/OTPDeliveryMethod.java ---------------------------------------------------------------------- diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/OTPDeliveryMethod.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/OTPDeliveryMethod.java new file mode 100644 index 0000000..f05c7ae --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/OTPDeliveryMethod.java @@ -0,0 +1,38 @@ +/** + * 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; + +public class OTPDeliveryMethod { + + private final String name; + private final String target; + + public OTPDeliveryMethod(String name, String target) { + this.name = name; + this.target = target; + } + + public String getName() { + return name; + } + + public String getTarget() { + return target; + } +} http://git-wip-us.apache.org/repos/asf/fineract/blob/1a966e8e/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/OTPMetadata.java ---------------------------------------------------------------------- diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/OTPMetadata.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/OTPMetadata.java new file mode 100644 index 0000000..0365fff --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/OTPMetadata.java @@ -0,0 +1,53 @@ +/** + * 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 org.joda.time.DateTime; + +public class OTPMetadata { + + private final DateTime requestTime; + private final int tokenLiveTimeInSec; + private final boolean extendedAccessToken; + private final OTPDeliveryMethod deliveryMethod; + + public OTPMetadata(DateTime requestTime, int tokenLiveTimeInSec, + boolean extendedAccessToken, OTPDeliveryMethod deliveryMethod) { + this.requestTime = requestTime; + this.tokenLiveTimeInSec = tokenLiveTimeInSec; + this.extendedAccessToken = extendedAccessToken; + this.deliveryMethod = deliveryMethod; + } + + public DateTime getRequestTime() { + return requestTime; + } + + public int getTokenLiveTimeInSec() { + return tokenLiveTimeInSec; + } + + public boolean isExtendedAccessToken() { + return extendedAccessToken; + } + + public OTPDeliveryMethod getDeliveryMethod() { + return deliveryMethod; + } +} http://git-wip-us.apache.org/repos/asf/fineract/blob/1a966e8e/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/OTPRequest.java ---------------------------------------------------------------------- diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/OTPRequest.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/OTPRequest.java new file mode 100644 index 0000000..f9f3e24 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/OTPRequest.java @@ -0,0 +1,53 @@ +/** + * 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 org.apache.fineract.infrastructure.core.service.DateUtils; +import org.joda.time.DateTime; + +public class OTPRequest { + + private final String token; + private final OTPMetadata metadata; + + public OTPRequest(String token, OTPMetadata metadata) { + this.token = token; + this.metadata = metadata; + } + + public static OTPRequest create(String token, int tokenLiveTimeInSec, boolean extendedAccessToken, + OTPDeliveryMethod deliveryMethod) { + final OTPMetadata metadata = new OTPMetadata(DateUtils.getLocalDateTimeOfTenant().toDateTime(), + tokenLiveTimeInSec, extendedAccessToken, deliveryMethod); + return new OTPRequest(token, metadata); + } + + public String getToken() { + return token; + } + + public OTPMetadata getMetadata() { + return metadata; + } + + public boolean isValid() { + DateTime expireTime = metadata.getRequestTime().plusSeconds(metadata.getTokenLiveTimeInSec()); + return DateTime.now().isBefore(expireTime); + } +} http://git-wip-us.apache.org/repos/asf/fineract/blob/1a966e8e/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/TwoFactorConfigurationValidator.java ---------------------------------------------------------------------- diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/TwoFactorConfigurationValidator.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/TwoFactorConfigurationValidator.java new file mode 100644 index 0000000..41cf69b --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/TwoFactorConfigurationValidator.java @@ -0,0 +1,120 @@ +/** + * 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.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang.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.context.annotation.Profile; +import org.springframework.stereotype.Component; + +import com.google.gson.JsonElement; +import com.google.gson.reflect.TypeToken; + +@Component +@Profile("twofactor") +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(); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/fineract/blob/1a966e8e/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/domain/OTPRequestRepository.java ---------------------------------------------------------------------- diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/domain/OTPRequestRepository.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/domain/OTPRequestRepository.java new file mode 100644 index 0000000..fc11300 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/domain/OTPRequestRepository.java @@ -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.domain; + +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.fineract.infrastructure.security.data.OTPRequest; +import org.apache.fineract.useradministration.domain.AppUser; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Repository; +import org.springframework.util.Assert; + +@Repository +@Profile("twofactor") +public class OTPRequestRepository { + + private final ConcurrentHashMap<Long, OTPRequest> OTPrequests = new ConcurrentHashMap<>(); + + + public OTPRequest getOTPRequestForUser(AppUser user) { + Assert.notNull(user); + + return this.OTPrequests.get(user.getId()); + } + + public void addOTPRequest(AppUser user, OTPRequest request) { + Assert.notNull(user); + Assert.notNull(request); + this.OTPrequests.put(user.getId(), request); + } + + public void deleteOTPRequestForUser(AppUser user) { + this.OTPrequests.remove(user.getId()); + } +}
