Merge branch 'TwoFactor' into develop
This commit is contained in:
@@ -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>
|
||||
@@ -3930,6 +3964,11 @@
|
||||
Default authentication is using HTTP Basic Auth. Oauth2 can be enabled by using <i>-Psecurity=oauth</i> option on gradle build command , refer
|
||||
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
|
||||
@@ -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": "user@example.com"
|
||||
}
|
||||
]</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">
|
||||
|
||||
@@ -745,4 +745,8 @@ ul.field li {
|
||||
|
||||
tt {
|
||||
font-size: 9.5pt;
|
||||
}
|
||||
|
||||
ol.normalli li {
|
||||
list-style-type: decimal;
|
||||
}
|
||||
@@ -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){
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
+14
@@ -3118,4 +3118,18 @@ public class CommandWrapperBuilder {
|
||||
this.href = "/emailcampaigns/configuration/";
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
+8
-1
@@ -85,5 +85,12 @@ public interface ConfigurationDomainService {
|
||||
Long getDailyTPTLimit();
|
||||
|
||||
void removeGlobalConfigurationPropertyDataFromCache(String propertyName);
|
||||
|
||||
|
||||
boolean isSMSOTPDeliveryEnabled();
|
||||
|
||||
boolean isEmailOTPDeliveryEnabled();
|
||||
|
||||
Integer retrieveOTPCharacterLength();
|
||||
|
||||
Integer retrieveOTPLiveTime();
|
||||
}
|
||||
+37
@@ -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;
|
||||
|
||||
+2
-1
@@ -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,
|
||||
|
||||
+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.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
+11
-3
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+132
@@ -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);
|
||||
}
|
||||
}
|
||||
+86
@@ -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);
|
||||
}
|
||||
}
|
||||
+13
-4
@@ -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);
|
||||
}
|
||||
|
||||
+108
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+60
@@ -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();
|
||||
}
|
||||
}
|
||||
+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.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);
|
||||
|
||||
}
|
||||
+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.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";
|
||||
|
||||
}
|
||||
+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 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;
|
||||
}
|
||||
}
|
||||
+10
-2
@@ -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;
|
||||
}
|
||||
}
|
||||
+10
-2
@@ -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;
|
||||
}
|
||||
}
|
||||
+38
@@ -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;
|
||||
}
|
||||
}
|
||||
+53
@@ -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;
|
||||
}
|
||||
}
|
||||
+53
@@ -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);
|
||||
}
|
||||
}
|
||||
+120
@@ -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();
|
||||
}
|
||||
}
|
||||
+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.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());
|
||||
}
|
||||
}
|
||||
+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.domain;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.ManyToOne;
|
||||
import javax.persistence.Table;
|
||||
import javax.persistence.Temporal;
|
||||
import javax.persistence.TemporalType;
|
||||
import javax.persistence.UniqueConstraint;
|
||||
|
||||
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;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.LocalDateTime;
|
||||
|
||||
@Entity
|
||||
@Table(name = "twofactor_access_token",
|
||||
uniqueConstraints = {@UniqueConstraint(columnNames = { "token", "appuser_id" }, name = "token_appuser_UNIQUE")})
|
||||
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;
|
||||
|
||||
@Temporal(TemporalType.TIMESTAMP)
|
||||
@Column(name = "valid_from", nullable = false)
|
||||
private Date validFrom;
|
||||
|
||||
@Temporal(TemporalType.TIMESTAMP)
|
||||
@Column(name = "valid_to", nullable = false)
|
||||
private Date validTo;
|
||||
|
||||
@Column(name = "enabled", nullable = false)
|
||||
private boolean enabled;
|
||||
|
||||
public TFAccessToken() {
|
||||
}
|
||||
|
||||
public static TFAccessToken create(String token, AppUser user, int tokenLiveTimeInSec) {
|
||||
DateTime validFrom = DateUtils.getLocalDateTimeOfTenant().toDateTime();
|
||||
DateTime validTo = validFrom.plusSeconds(tokenLiveTimeInSec);
|
||||
|
||||
return new TFAccessToken(token, user, validFrom.toDate(), validTo.toDate(), true);
|
||||
}
|
||||
|
||||
public TFAccessToken(String token, AppUser user, Date validFrom, Date validTo, boolean enabled) {
|
||||
this.token = token;
|
||||
this.user = user;
|
||||
this.validFrom = validFrom;
|
||||
this.validTo = validTo;
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public boolean isValid() {
|
||||
return this.enabled && isDateInTheFuture(getValidToDate())
|
||||
&& isDateInThePast(getValidFromDate());
|
||||
}
|
||||
|
||||
public AccessTokenData toTokenData() {
|
||||
return new AccessTokenData(this.token, getValidFromDate().toDateTime(),
|
||||
getValidToDate().toDateTime());
|
||||
}
|
||||
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
public AppUser getUser() {
|
||||
return user;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public LocalDateTime getValidFromDate() {
|
||||
return new LocalDateTime(validFrom);
|
||||
}
|
||||
|
||||
public LocalDateTime getValidToDate() {
|
||||
return new LocalDateTime(validTo);
|
||||
}
|
||||
|
||||
public void setToken(String token) {
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
public void setUser(AppUser user) {
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
public void setValidFrom(Date validFrom) {
|
||||
this.validFrom = validFrom;
|
||||
}
|
||||
|
||||
public void setValidTo(Date validTo) {
|
||||
this.validTo = validTo;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
private boolean isDateInTheFuture(LocalDateTime dateTime) {
|
||||
return dateTime.isAfter(DateUtils.getLocalDateTimeOfTenant());
|
||||
}
|
||||
|
||||
private boolean isDateInThePast(LocalDateTime dateTime) {
|
||||
return dateTime.isBefore(DateUtils.getLocalDateTimeOfTenant());
|
||||
}
|
||||
}
|
||||
+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.context.annotation.Profile;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||
|
||||
@Profile("twofactor")
|
||||
public interface TFAccessTokenRepository extends JpaRepository<TFAccessToken, Long>, JpaSpecificationExecutor<TFAccessToken> {
|
||||
|
||||
TFAccessToken findByUserAndToken(AppUser user, String token);
|
||||
|
||||
}
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* 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 javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Table;
|
||||
import javax.persistence.UniqueConstraint;
|
||||
|
||||
import org.apache.commons.lang.BooleanUtils;
|
||||
import org.apache.commons.lang.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")})
|
||||
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 String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getStringValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public Boolean getBooleanValue() {
|
||||
return BooleanUtils.toBooleanObject(value);
|
||||
}
|
||||
|
||||
public Integer getIntegerValue() {
|
||||
try {
|
||||
return NumberUtils.createInteger(value);
|
||||
} catch (NumberFormatException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public Object getObjectValue() {
|
||||
if(TwoFactorConfigurationConstants.NUMBER_PARAMETERS.contains(name)) {
|
||||
return getIntegerValue();
|
||||
}
|
||||
if(TwoFactorConfigurationConstants.BOOLEAN_PARAMETERS.contains(name)) {
|
||||
return getBooleanValue();
|
||||
}
|
||||
|
||||
return getStringValue();
|
||||
}
|
||||
|
||||
public void setStringValue(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public void setBooleanValue(boolean value) {
|
||||
this.value = String.valueOf(value);
|
||||
}
|
||||
|
||||
public void setIntegerValue(long value) {
|
||||
this.value = String.valueOf(value);
|
||||
}
|
||||
}
|
||||
+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.context.annotation.Profile;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||
|
||||
@Profile("twofactor")
|
||||
public interface TwoFactorConfigurationRepository extends
|
||||
JpaRepository<TwoFactorConfiguration, Long>, JpaSpecificationExecutor<TwoFactorConfiguration> {
|
||||
|
||||
TwoFactorConfiguration findByName(final String name);
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
+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");
|
||||
}
|
||||
}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* 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 java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
|
||||
import org.apache.fineract.useradministration.domain.AppUser;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
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.stereotype.Service;
|
||||
|
||||
/**
|
||||
* A dummy {@link TwoFactorAuthenticationFilter} filter used when 'twofactor'
|
||||
* environment profile is not active.
|
||||
*
|
||||
* This filter adds 'TWOFACTOR_AUTHENTICATED' authority to every authenticated
|
||||
* platform user.
|
||||
*/
|
||||
@Service(value = "twoFactorAuthFilter")
|
||||
@Profile("!twofactor")
|
||||
public class InsecureTwoFactorAuthenticationFilter extends TwoFactorAuthenticationFilter {
|
||||
|
||||
public InsecureTwoFactorAuthenticationFilter() {
|
||||
super(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
|
||||
|
||||
SecurityContext context = SecurityContextHolder.getContext();
|
||||
Authentication authentication = null;
|
||||
if(context != null) {
|
||||
authentication = context.getAuthentication();
|
||||
}
|
||||
|
||||
// Add two-factor authenticated authority if user is authenticated
|
||||
if(authentication != null && authentication.isAuthenticated()) {
|
||||
AppUser user = (AppUser) authentication.getPrincipal();
|
||||
|
||||
if(user == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<GrantedAuthority> updatedAuthorities = new ArrayList<>(authentication.getAuthorities());
|
||||
updatedAuthorities.add(new SimpleGrantedAuthority("TWOFACTOR_AUTHENTICATED"));
|
||||
UsernamePasswordAuthenticationToken updatedAuthentication =
|
||||
new UsernamePasswordAuthenticationToken(authentication.getPrincipal(),
|
||||
authentication.getCredentials(), updatedAuthorities);
|
||||
context.setAuthentication(updatedAuthentication);
|
||||
}
|
||||
|
||||
chain.doFilter(req, res);
|
||||
}
|
||||
}
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
/**
|
||||
* 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 java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.fineract.infrastructure.security.constants.TwoFactorConstants;
|
||||
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.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
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.oauth2.provider.OAuth2Authentication;
|
||||
import org.springframework.stereotype.Service;
|
||||
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, otherwise
|
||||
* {@link InsecureTwoFactorAuthenticationFilter} is used.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
@Service(value = "twoFactorAuthFilter")
|
||||
@Profile("twofactor")
|
||||
public class TwoFactorAuthenticationFilter extends GenericFilterBean {
|
||||
|
||||
private final TwoFactorService twoFactorService;
|
||||
|
||||
@Autowired
|
||||
public TwoFactorAuthenticationFilter(TwoFactorService twoFactorService) {
|
||||
this.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()) {
|
||||
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"));
|
||||
final Authentication updatedAuthentication = createUpdatedAuthentication(authentication,
|
||||
updatedAuthorities);
|
||||
context.setAuthentication(updatedAuthentication);
|
||||
}
|
||||
|
||||
chain.doFilter(req, res);
|
||||
}
|
||||
|
||||
private Authentication createUpdatedAuthentication(final Authentication currentAuthentication,
|
||||
final List<GrantedAuthority> updatedAuthorities) {
|
||||
|
||||
final UsernamePasswordAuthenticationToken authentication = new
|
||||
UsernamePasswordAuthenticationToken(currentAuthentication.getPrincipal(),
|
||||
currentAuthentication.getCredentials(), updatedAuthorities);
|
||||
|
||||
if(currentAuthentication instanceof OAuth2Authentication) {
|
||||
final OAuth2Authentication oAuth2Authentication = (OAuth2Authentication) currentAuthentication;
|
||||
return new OAuth2Authentication(oAuth2Authentication.getOAuth2Request(), authentication);
|
||||
}
|
||||
|
||||
return authentication;
|
||||
}
|
||||
}
|
||||
+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();
|
||||
}
|
||||
+38
@@ -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.service;
|
||||
|
||||
public class RandomOTPGenerator {
|
||||
|
||||
private static final String allowedCharacters = "0123456789ABCDEFGHIJKLMNOPQRSTUVQXYZ";
|
||||
private final int tokenLength;
|
||||
|
||||
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) (Math.random() * (allowedCharacters.length()))));
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
+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.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);
|
||||
}
|
||||
+304
@@ -0,0 +1,304 @@
|
||||
/**
|
||||
* 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.io.StringReader;
|
||||
import java.io.StringWriter;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.security.constants.TwoFactorConfigurationConstants;
|
||||
import org.apache.fineract.infrastructure.security.constants.TwoFactorConstants;
|
||||
import org.apache.fineract.infrastructure.security.data.OTPRequest;
|
||||
import org.apache.fineract.infrastructure.security.domain.TwoFactorConfiguration;
|
||||
import org.apache.fineract.infrastructure.security.domain.TwoFactorConfigurationRepository;
|
||||
import org.apache.fineract.useradministration.domain.AppUser;
|
||||
import org.joda.time.LocalDateTime;
|
||||
import org.joda.time.format.DateTimeFormat;
|
||||
import org.joda.time.format.DateTimeFormatter;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.cache.annotation.CacheEvict;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.github.mustachejava.DefaultMustacheFactory;
|
||||
import com.github.mustachejava.Mustache;
|
||||
import com.github.mustachejava.MustacheFactory;
|
||||
|
||||
@Service
|
||||
@Profile("twofactor")
|
||||
public class TwoFactorConfigurationServiceImpl implements TwoFactorConfigurationService {
|
||||
|
||||
private static final String DEFAULT_EMAIL_SUBJECT = "Fineract Two-Factor Authentication Token";
|
||||
private static final String DEFAULT_EMAIL_BODY = "Hello {username}.\n" +
|
||||
"Your OTP login token is {token}.";
|
||||
private static final String DEFAULT_SMS_TEXT = "Your authentication token for Fineract is " +
|
||||
"{token}.";
|
||||
|
||||
private final TwoFactorConfigurationRepository configurationRepository;
|
||||
|
||||
|
||||
@Autowired
|
||||
public TwoFactorConfigurationServiceImpl(TwoFactorConfigurationRepository configurationRepository) {
|
||||
this.configurationRepository = configurationRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Cacheable(value = "tfConfig", key = "T(org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier()")
|
||||
public Map<String, Object> retrieveAll() {
|
||||
List<TwoFactorConfiguration> configurationList = configurationRepository.findAll();
|
||||
Map<String, Object> configurationMap = new HashMap<>();
|
||||
for(final TwoFactorConfiguration configuration : configurationList) {
|
||||
configurationMap.put(configuration.getName(), configuration.getObjectValue());
|
||||
}
|
||||
return configurationMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
@CacheEvict(value = "tfConfig", allEntries = true)
|
||||
public Map<String, Object> update(JsonCommand command) {
|
||||
Map<String, Object> actualChanges = new HashMap<>();
|
||||
|
||||
|
||||
for(final String parameterName : TwoFactorConfigurationConstants.BOOLEAN_PARAMETERS) {
|
||||
TwoFactorConfiguration configuration = configurationRepository.findByName(parameterName);
|
||||
if(configuration == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if(command.isChangeInBooleanParameterNamed(parameterName, configuration.getBooleanValue())) {
|
||||
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(parameterName);
|
||||
actualChanges.put(parameterName, newValue);
|
||||
configuration.setBooleanValue(newValue);
|
||||
configurationRepository.save(configuration);
|
||||
}
|
||||
}
|
||||
|
||||
for(final String parameterName : TwoFactorConfigurationConstants.STRING_PARAMETERS) {
|
||||
TwoFactorConfiguration configuration = configurationRepository.findByName(parameterName);
|
||||
if(configuration == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if(command.isChangeInStringParameterNamed(parameterName, configuration.getStringValue())) {
|
||||
final String newValue = command.stringValueOfParameterNamed(parameterName).trim();
|
||||
actualChanges.put(parameterName, newValue);
|
||||
configuration.setStringValue(newValue);
|
||||
configurationRepository.save(configuration);
|
||||
}
|
||||
}
|
||||
|
||||
for(final String parameterName : TwoFactorConfigurationConstants.NUMBER_PARAMETERS) {
|
||||
TwoFactorConfiguration configuration = configurationRepository.findByName(parameterName);
|
||||
if(configuration == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if(command.isChangeInIntegerSansLocaleParameterNamed(parameterName, configuration.getIntegerValue())) {
|
||||
final Long newValue = command.longValueOfParameterNamed(parameterName);
|
||||
actualChanges.put(parameterName, newValue);
|
||||
configuration.setIntegerValue(newValue);
|
||||
configurationRepository.save(configuration);
|
||||
}
|
||||
}
|
||||
|
||||
if(!actualChanges.isEmpty()) {
|
||||
configurationRepository.flush();
|
||||
}
|
||||
|
||||
return actualChanges;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Cacheable(value = "tfConfig", key = "T(org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier()+'|smsEnabled'")
|
||||
public boolean isSMSEnabled() {
|
||||
return getBooleanConfig(TwoFactorConfigurationConstants.ENABLE_SMS_DELIVERY, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Cacheable(value = "tfConfig", key = "T(org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier()+'|smsProvider'")
|
||||
public Integer getSMSProviderId() {
|
||||
Integer value = getIntegerConfig(TwoFactorConfigurationConstants.SMS_PROVIDER_ID,
|
||||
null);
|
||||
if(value < 1) {
|
||||
return null;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Cacheable(value = "tfConfig", key = "T(org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier()+'|smsText'")
|
||||
public String getSmsText() {
|
||||
return getStringConfig(TwoFactorConfigurationConstants.SMS_MESSAGE_TEXT, DEFAULT_SMS_TEXT);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Cacheable(value = "tfConfig", key = "T(org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier()+'|emailEnabled'")
|
||||
public boolean isEmailEnabled() {
|
||||
return getBooleanConfig(TwoFactorConfigurationConstants.ENABLE_EMAIL_DELIVERY, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Cacheable(value = "tfConfig", key = "T(org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier()+'|emailSubject'")
|
||||
public String getEmailSubject() {
|
||||
return getStringConfig(TwoFactorConfigurationConstants.EMAIL_SUBJECT, DEFAULT_EMAIL_SUBJECT);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Cacheable(value = "tfConfig", key = "T(org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier()+'|emailBody'")
|
||||
public String getEmailBody() {
|
||||
return getStringConfig(TwoFactorConfigurationConstants.EMAIL_BODY, DEFAULT_EMAIL_BODY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFormattedEmailSubjectFor(AppUser user, OTPRequest request) {
|
||||
final Map<String, Object> templateData = processTemplateDataFor(user, request);
|
||||
return compileTextTemplate(getEmailSubject(), TwoFactorConstants.EMAIL_DELIVERY_METHOD_NAME, templateData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFormattedEmailBodyFor(AppUser user, OTPRequest request) {
|
||||
final Map<String, Object> templateData = processTemplateDataFor(user, request);
|
||||
return compileTextTemplate(getEmailBody(), TwoFactorConstants.EMAIL_DELIVERY_METHOD_NAME, templateData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFormattedSmsTextFor(AppUser user, OTPRequest request) {
|
||||
final Map<String, Object> templateData = processTemplateDataFor(user, request);
|
||||
return compileTextTemplate(getSmsText(), TwoFactorConstants.SMS_DELIVERY_METHOD_NAME, templateData);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Cacheable(value = "tfConfig", key = "T(org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier()+'|otpLength'")
|
||||
public Integer getOTPTokenLength() {
|
||||
Integer defaultValue = 1;
|
||||
return getIntegerConfig(TwoFactorConfigurationConstants.OTP_TOKEN_LENGTH,
|
||||
defaultValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Cacheable(value = "tfConfig", key = "T(org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier()+'|otpTime'")
|
||||
public Integer getOTPTokenLiveTime() {
|
||||
Integer defaultValue = 300;
|
||||
Integer value = getIntegerConfig(TwoFactorConfigurationConstants.OTP_TOKEN_LIVE_TIME,
|
||||
defaultValue);
|
||||
if(value < 1) {
|
||||
return defaultValue;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Cacheable(value = "tfConfig", key = "T(org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier()+'|tokenTime'")
|
||||
public Integer getAccessTokenLiveTime() {
|
||||
Integer defaultValue = 86400;
|
||||
Integer value = getIntegerConfig(TwoFactorConfigurationConstants.ACCESS_TOKEN_LIVE_TIME,
|
||||
defaultValue);
|
||||
if(value < 1) {
|
||||
return defaultValue;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Cacheable(value = "tfConfig", key = "T(org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier()+'|tokenExtendedTime'")
|
||||
public Integer getAccessTokenExtendedLiveTime() {
|
||||
Integer defaultValue = 604800;
|
||||
Integer value = getIntegerConfig(TwoFactorConfigurationConstants.ACCESS_TOKEN_LIVE_TIME_EXTENDED,
|
||||
defaultValue);
|
||||
if(value < 1) {
|
||||
return defaultValue;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private boolean getBooleanConfig(final String name, final boolean defaultValue) {
|
||||
final TwoFactorConfiguration configuration =
|
||||
configurationRepository.findByName(name);
|
||||
Boolean value = configuration.getBooleanValue();
|
||||
if(value == null) {
|
||||
return defaultValue;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private String getStringConfig(final String name, final String defaultValue) {
|
||||
final TwoFactorConfiguration configuration =
|
||||
configurationRepository.findByName(name);
|
||||
String value = configuration.getStringValue();
|
||||
if(value == null) {
|
||||
return defaultValue;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private Integer getIntegerConfig(final String name, final Integer defaultValue) {
|
||||
final TwoFactorConfiguration configuration =
|
||||
configurationRepository.findByName(name);
|
||||
Integer value = configuration.getIntegerValue();
|
||||
if(value == null) {
|
||||
return defaultValue;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private Map<String, Object> processTemplateDataFor(AppUser user, OTPRequest request) {
|
||||
Map<String, Object> templateData = new HashMap<>();
|
||||
|
||||
templateData.put("username", user.getUsername());
|
||||
templateData.put("email", user.getEmail());
|
||||
templateData.put("firstname", user.getFirstname());
|
||||
templateData.put("lastname", user.getLastname());
|
||||
if(user.getStaff() != null && user.getStaff().mobileNo() != null) {
|
||||
templateData.put("mobileno", user.getStaff().mobileNo());
|
||||
}
|
||||
|
||||
templateData.put("token", request.getToken());
|
||||
templateData.put("tokenlivetime", request.getMetadata().getTokenLiveTimeInSec());
|
||||
|
||||
DateTimeFormatter timeFormatter = DateTimeFormat.forPattern("HH:mm:ss");
|
||||
DateTimeFormatter dateFormatter = DateTimeFormat.forPattern("dd.MM.yyyy");
|
||||
|
||||
final LocalDateTime requestTime = request.getMetadata().getRequestTime().toLocalDateTime();
|
||||
final LocalDateTime expireTime = requestTime.plusSeconds(request.getMetadata().getTokenLiveTimeInSec());
|
||||
|
||||
templateData.put("requestdate", requestTime.toLocalDate().toString(dateFormatter));
|
||||
templateData.put("requesttime", requestTime.toLocalTime().toString(timeFormatter));
|
||||
|
||||
templateData.put("expiredate", expireTime.toLocalDate().toString(dateFormatter));
|
||||
templateData.put("expiretime", expireTime.toLocalTime().toString(timeFormatter));
|
||||
|
||||
return templateData;
|
||||
}
|
||||
|
||||
private String compileTextTemplate(final String template, final String name,
|
||||
final Map<String, Object> params) {
|
||||
final MustacheFactory mf = new DefaultMustacheFactory();
|
||||
final Mustache mustache = mf.compile(new StringReader(template), name);
|
||||
|
||||
final StringWriter stringWriter = new StringWriter();
|
||||
mustache.execute(stringWriter, params);
|
||||
|
||||
return stringWriter.toString();
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* 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);
|
||||
|
||||
}
|
||||
+229
@@ -0,0 +1,229 @@
|
||||
/**
|
||||
* 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.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.domain.EmailDetail;
|
||||
import org.apache.fineract.infrastructure.core.service.PlatformEmailService;
|
||||
import org.apache.fineract.infrastructure.security.constants.TwoFactorConstants;
|
||||
import org.apache.fineract.infrastructure.security.data.OTPDeliveryMethod;
|
||||
import org.apache.fineract.infrastructure.security.data.OTPRequest;
|
||||
import org.apache.fineract.infrastructure.security.domain.OTPRequestRepository;
|
||||
import org.apache.fineract.infrastructure.security.domain.TFAccessToken;
|
||||
import org.apache.fineract.infrastructure.security.domain.TFAccessTokenRepository;
|
||||
import org.apache.fineract.infrastructure.security.exception.AccessTokenInvalidIException;
|
||||
import org.apache.fineract.infrastructure.security.exception.OTPDeliveryMethodInvalidException;
|
||||
import org.apache.fineract.infrastructure.security.exception.OTPTokenInvalidException;
|
||||
import org.apache.fineract.infrastructure.sms.domain.SmsMessage;
|
||||
import org.apache.fineract.infrastructure.sms.domain.SmsMessageRepository;
|
||||
import org.apache.fineract.infrastructure.sms.scheduler.SmsMessageScheduledJobService;
|
||||
import org.apache.fineract.useradministration.domain.AppUser;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.cache.annotation.CacheEvict;
|
||||
import org.springframework.cache.annotation.CachePut;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
@Profile("twofactor")
|
||||
public class TwoFactorServiceImpl implements TwoFactorService {
|
||||
|
||||
|
||||
|
||||
private final AccessTokenGenerationService accessTokenGenerationService;
|
||||
private final PlatformEmailService emailService;
|
||||
private final SmsMessageScheduledJobService smsMessageScheduledJobService;
|
||||
|
||||
private final OTPRequestRepository otpRequestRepository;
|
||||
private final TFAccessTokenRepository tfAccessTokenRepository;
|
||||
private final SmsMessageRepository smsMessageRepository;
|
||||
|
||||
private final TwoFactorConfigurationService configurationService;
|
||||
|
||||
@Autowired
|
||||
public TwoFactorServiceImpl(AccessTokenGenerationService accessTokenGenerationService,
|
||||
PlatformEmailService emailService,
|
||||
SmsMessageScheduledJobService smsMessageScheduledJobService,
|
||||
OTPRequestRepository otpRequestRepository,
|
||||
TFAccessTokenRepository tfAccessTokenRepository,
|
||||
SmsMessageRepository smsMessageRepository,
|
||||
TwoFactorConfigurationService configurationService) {
|
||||
this.accessTokenGenerationService = accessTokenGenerationService;
|
||||
this.emailService = emailService;
|
||||
this.smsMessageScheduledJobService = smsMessageScheduledJobService;
|
||||
this.otpRequestRepository = otpRequestRepository;
|
||||
this.tfAccessTokenRepository = tfAccessTokenRepository;
|
||||
this.smsMessageRepository = smsMessageRepository;
|
||||
this.configurationService = configurationService;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public List<OTPDeliveryMethod> getDeliveryMethodsForUser(final AppUser user) {
|
||||
List<OTPDeliveryMethod> deliveryMethods = new ArrayList<>();
|
||||
|
||||
OTPDeliveryMethod smsMethod = getSMSDeliveryMethodForUser(user);
|
||||
if(smsMethod != null) {
|
||||
deliveryMethods.add(smsMethod);
|
||||
}
|
||||
OTPDeliveryMethod emailDelivery = getEmailDeliveryMethodForUser(user);
|
||||
if(emailDelivery != null) {
|
||||
deliveryMethods.add(emailDelivery);
|
||||
}
|
||||
|
||||
return deliveryMethods;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OTPRequest createNewOTPToken(final AppUser user, final String deliveryMethodName,
|
||||
final boolean extendedAccessToken) {
|
||||
if(TwoFactorConstants.SMS_DELIVERY_METHOD_NAME.equalsIgnoreCase(deliveryMethodName)) {
|
||||
OTPDeliveryMethod smsDelivery = getSMSDeliveryMethodForUser(user);
|
||||
if(smsDelivery == null) {
|
||||
throw new OTPDeliveryMethodInvalidException();
|
||||
}
|
||||
final OTPRequest request = generateNewToken(smsDelivery, extendedAccessToken);
|
||||
final String smsText = configurationService.getFormattedSmsTextFor(user, request);
|
||||
SmsMessage smsMessage = SmsMessage.pendingSms(null, null, null, user.getStaff(), smsText,
|
||||
user.getStaff().mobileNo(), null);
|
||||
this.smsMessageRepository.save(smsMessage);
|
||||
smsMessageScheduledJobService.sendTriggeredMessage(Collections.singleton(smsMessage),
|
||||
configurationService.getSMSProviderId());
|
||||
otpRequestRepository.addOTPRequest(user, request);
|
||||
return request;
|
||||
} else if(TwoFactorConstants.EMAIL_DELIVERY_METHOD_NAME.equalsIgnoreCase(deliveryMethodName)) {
|
||||
OTPDeliveryMethod emailDelivery = getEmailDeliveryMethodForUser(user);
|
||||
if(emailDelivery == null) {
|
||||
throw new OTPDeliveryMethodInvalidException();
|
||||
}
|
||||
final OTPRequest request = generateNewToken(emailDelivery, extendedAccessToken);
|
||||
final String emailSubject = configurationService.getFormattedEmailSubjectFor(user, request);
|
||||
final String emailBody = configurationService.getFormattedEmailBodyFor(user, request);
|
||||
final EmailDetail emailData = new EmailDetail(emailSubject, emailBody, user.getEmail(),
|
||||
user.getFirstname() + " " + user.getLastname());
|
||||
emailService.sendDefinedEmail(emailData);
|
||||
otpRequestRepository.addOTPRequest(user, request);
|
||||
return request;
|
||||
}
|
||||
|
||||
throw new OTPDeliveryMethodInvalidException();
|
||||
}
|
||||
|
||||
@Override
|
||||
@CachePut(value = "userTFAccessToken",
|
||||
key = "T(org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil)" +
|
||||
".getTenant().getTenantIdentifier().concat(#user.username).concat(#result.token + 'tok')")
|
||||
public TFAccessToken createAccessTokenFromOTP(final AppUser user, final String otpToken) {
|
||||
|
||||
OTPRequest otpRequest = otpRequestRepository.getOTPRequestForUser(user);
|
||||
if(otpRequest == null || !otpRequest.isValid() || !otpRequest.getToken().equalsIgnoreCase(otpToken)) {
|
||||
throw new OTPTokenInvalidException();
|
||||
}
|
||||
|
||||
otpRequestRepository.deleteOTPRequestForUser(user);
|
||||
|
||||
String token = accessTokenGenerationService.generateRandomToken();
|
||||
int liveTime;
|
||||
if(otpRequest.getMetadata().isExtendedAccessToken()) {
|
||||
liveTime = configurationService.getAccessTokenExtendedLiveTime();
|
||||
} else {
|
||||
liveTime = configurationService.getAccessTokenLiveTime();
|
||||
}
|
||||
TFAccessToken accessToken = TFAccessToken.create(token, user, liveTime);
|
||||
tfAccessTokenRepository.save(accessToken);
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateTwoFactorAccessToken(AppUser user, String token) {
|
||||
TFAccessToken accessToken = fetchAccessTokenForUser(user, token);
|
||||
|
||||
if(accessToken == null || !accessToken.isValid()) {
|
||||
throw new AccessTokenInvalidIException();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@CacheEvict(value = "userTFAccessToken",
|
||||
key = "T(org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil)" +
|
||||
".getTenant().getTenantIdentifier().concat(#user.username).concat(#result.token + 'tok')")
|
||||
public TFAccessToken invalidateAccessToken(final AppUser user, final JsonCommand command) {
|
||||
|
||||
final String token = command.stringValueOfParameterNamed("token");
|
||||
final TFAccessToken accessToken = fetchAccessTokenForUser(user, token);
|
||||
|
||||
if(accessToken == null || !accessToken.isValid()) {
|
||||
throw new AccessTokenInvalidIException();
|
||||
}
|
||||
|
||||
accessToken.setEnabled(false);
|
||||
tfAccessTokenRepository.save(accessToken);
|
||||
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Cacheable(value = "userTFAccessToken",
|
||||
key = "T(org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil)" +
|
||||
".getTenant().getTenantIdentifier().concat(#user.username).concat(#token + 'tok')")
|
||||
public TFAccessToken fetchAccessTokenForUser(final AppUser user, final String token) {
|
||||
return tfAccessTokenRepository.findByUserAndToken(user, token);
|
||||
}
|
||||
|
||||
private OTPDeliveryMethod getSMSDeliveryMethodForUser(final AppUser user) {
|
||||
if(!configurationService.isSMSEnabled()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if(configurationService.getSMSProviderId() == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if(user.getStaff() == null) {
|
||||
return null;
|
||||
}
|
||||
String mobileNo = user.getStaff().mobileNo();
|
||||
if(StringUtils.isBlank(mobileNo)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new OTPDeliveryMethod(TwoFactorConstants.SMS_DELIVERY_METHOD_NAME, mobileNo);
|
||||
}
|
||||
|
||||
private OTPDeliveryMethod getEmailDeliveryMethodForUser(final AppUser user) {
|
||||
if(!configurationService.isEmailEnabled()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new OTPDeliveryMethod(TwoFactorConstants.EMAIL_DELIVERY_METHOD_NAME, user.getEmail());
|
||||
}
|
||||
|
||||
private OTPRequest generateNewToken(final OTPDeliveryMethod deliveryMethod, final boolean extendedAccessToken) {
|
||||
int tokenLiveTime = configurationService.getOTPTokenLiveTime();
|
||||
int otpLength = configurationService.getOTPTokenLength();
|
||||
String token = new RandomOTPGenerator(otpLength).generate();
|
||||
return OTPRequest.create(token, tokenLiveTime, extendedAccessToken, 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.service;
|
||||
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class TwoFactorUtils {
|
||||
|
||||
private static final String TWO_FACTOR_PROFILE_NAME = "twofactor";
|
||||
|
||||
private final Environment environment;
|
||||
|
||||
@Autowired
|
||||
public TwoFactorUtils(Environment environment) {
|
||||
this.environment = environment;
|
||||
}
|
||||
|
||||
|
||||
public boolean isTwoFactorAuthEnabled() {
|
||||
for(final String profile : this.environment.getActiveProfiles()) {
|
||||
if(TWO_FACTOR_PROFILE_NAME.equals(profile)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
+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.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("-", "");
|
||||
}
|
||||
}
|
||||
+17
@@ -491,6 +491,23 @@ public class AppUser extends AbstractPersistableCustom<Long> implements Platform
|
||||
return hasNotPermission;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the user has a given permission explicitly.
|
||||
*
|
||||
* @param permissionCode the permission code to check for.
|
||||
* @return whether the user has the specified permission
|
||||
*/
|
||||
public boolean hasSpecificPermissionTo(final String permissionCode) {
|
||||
boolean hasPermission = false;
|
||||
for (final Role role : this.roles) {
|
||||
if(role.hasPermissionTo(permissionCode)) {
|
||||
hasPermission = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return hasPermission;
|
||||
}
|
||||
|
||||
public void validateHasReadPermission(final String resourceType) {
|
||||
|
||||
final String authorizationMessage = "User has no authority to view " + resourceType.toLowerCase() + "s";
|
||||
|
||||
@@ -48,4 +48,8 @@
|
||||
overflowToDisk="false" />
|
||||
<cache name="hooks" maxEntriesLocalHeap="10000" eternal="true"
|
||||
overflowToDisk="false" />
|
||||
<cache name="userTFAccessToken" maxEntriesLocalHeap="10000"
|
||||
overflowToDisk="false" timeToIdleSeconds="7200" />
|
||||
<cache name="tfConfig" maxEntriesLocalHeap="10000" eternal="true"
|
||||
overflowToDisk="false" />
|
||||
</ehcache>
|
||||
@@ -42,19 +42,26 @@
|
||||
method="POST" requires-channel="https" />
|
||||
<intercept-url pattern="/api/*/self/registration/user" access="permitAll"
|
||||
method="POST" requires-channel="https" />
|
||||
<intercept-url pattern="/api/**" access="isFullyAuthenticated()"
|
||||
<intercept-url pattern="/api/*/twofactor" access="isFullyAuthenticated()"
|
||||
method="GET" requires-channel="https" />
|
||||
<intercept-url pattern="/api/*/twofactor" access="isFullyAuthenticated()"
|
||||
method="POST" requires-channel="https" />
|
||||
<intercept-url pattern="/api/*/twofactor/validate" access="isFullyAuthenticated()"
|
||||
method="POST" requires-channel="https" />
|
||||
<intercept-url pattern="/api/**" access="isFullyAuthenticated() and hasAuthority('TWOFACTOR_AUTHENTICATED')"
|
||||
method="GET" requires-channel="https" />
|
||||
<intercept-url pattern="/api/**" access="isFullyAuthenticated()"
|
||||
<intercept-url pattern="/api/**" access="isFullyAuthenticated() and hasAuthority('TWOFACTOR_AUTHENTICATED')"
|
||||
method="POST" requires-channel="https" />
|
||||
<intercept-url pattern="/api/**" access="isFullyAuthenticated()"
|
||||
<intercept-url pattern="/api/**" access="isFullyAuthenticated() and hasAuthority('TWOFACTOR_AUTHENTICATED')"
|
||||
method="PUT" requires-channel="https" />
|
||||
<intercept-url pattern="/api/**" access="isFullyAuthenticated()"
|
||||
<intercept-url pattern="/api/**" access="isFullyAuthenticated() and hasAuthority('TWOFACTOR_AUTHENTICATED')"
|
||||
method="DELETE" requires-channel="https" />
|
||||
<intercept-url pattern="/api/**" access="isFullyAuthenticated()"
|
||||
<intercept-url pattern="/api/**" access="isFullyAuthenticated() and hasAuthority('TWOFACTOR_AUTHENTICATED')"
|
||||
method="HEAD" requires-channel="https" />
|
||||
|
||||
<custom-filter after="SECURITY_CONTEXT_FILTER"
|
||||
ref="basicAuthenticationProcessingFilter" />
|
||||
<custom-filter ref="twoFactorAuthFilter" after="BASIC_AUTH_FILTER" />
|
||||
</http>
|
||||
|
||||
<beans:bean id="basicAuthenticationEntryPoint"
|
||||
@@ -83,27 +90,34 @@
|
||||
erase-credentials="false">
|
||||
<authentication-provider ref="customAuthenticationProvider" />
|
||||
</authentication-manager>
|
||||
</beans:beans>
|
||||
</beans:beans>
|
||||
<beans:beans profile="oauth">
|
||||
<http create-session="stateless" use-expressions="true" pattern="/api/v1/**"
|
||||
entry-point-ref="oauthAuthenticationEntryPoint"
|
||||
access-decision-manager-ref="accessDecisionManager">
|
||||
<anonymous enabled="false" />
|
||||
<intercept-url pattern="/api//v1/**" method="OPTIONS"
|
||||
access="permitAll" requires-channel="https" />
|
||||
<intercept-url pattern="/api/v1/**" access="isFullyAuthenticated()"
|
||||
access="permitAll" requires-channel="https" />
|
||||
<intercept-url pattern="/api/*/twofactor" access="isFullyAuthenticated()"
|
||||
method="GET" requires-channel="https" />
|
||||
<intercept-url pattern="/api/*/twofactor" access="isFullyAuthenticated()"
|
||||
method="POST" requires-channel="https" />
|
||||
<intercept-url pattern="/api/*/twofactor/validate" access="isFullyAuthenticated()"
|
||||
method="POST" requires-channel="https" />
|
||||
<intercept-url pattern="/api/v1/**" access="isFullyAuthenticated() and hasAuthority('TWOFACTOR_AUTHENTICATED')"
|
||||
method="GET" requires-channel="https" />
|
||||
<intercept-url pattern="/api/v1/**" access="isFullyAuthenticated()"
|
||||
<intercept-url pattern="/api/v1/**" access="isFullyAuthenticated() and hasAuthority('TWOFACTOR_AUTHENTICATED')"
|
||||
method="POST" requires-channel="https" />
|
||||
<intercept-url pattern="/api/v1/**" access="isFullyAuthenticated()"
|
||||
<intercept-url pattern="/api/v1/**" access="isFullyAuthenticated() and hasAuthority('TWOFACTOR_AUTHENTICATED')"
|
||||
method="PUT" requires-channel="https" />
|
||||
<intercept-url pattern="/api/v1/**" access="isFullyAuthenticated()"
|
||||
<intercept-url pattern="/api/v1/**" access="isFullyAuthenticated() and hasAuthority('TWOFACTOR_AUTHENTICATED')"
|
||||
method="DELETE" requires-channel="https" />
|
||||
<intercept-url pattern="/api/v1/**" access="isFullyAuthenticated()"
|
||||
<intercept-url pattern="/api/v1/**" access="isFullyAuthenticated() and hasAuthority('TWOFACTOR_AUTHENTICATED')"
|
||||
method="HEAD" requires-channel="https" />
|
||||
<custom-filter ref="tenantIdentifierProcessingFilter"
|
||||
position="FIRST" />
|
||||
<custom-filter before="PRE_AUTH_FILTER" ref="resourceServerFilter" />
|
||||
<custom-filter ref="twoFactorAuthFilter" after="BASIC_AUTH_FILTER" />
|
||||
<access-denied-handler ref="oauthAccessDeniedHandler" />
|
||||
</http>
|
||||
|
||||
|
||||
+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.
|
||||
--
|
||||
|
||||
-- Access Token Table
|
||||
|
||||
CREATE TABLE `twofactor_access_token` (
|
||||
`id` bigint(20) NOT NULL AUTO_INCREMENT,
|
||||
`token` varchar(32) NOT NULL,
|
||||
`appuser_id` bigint(20) NOT NULL,
|
||||
`valid_from` datetime NOT NULL,
|
||||
`valid_to` datetime NOT NULL,
|
||||
`enabled` bit(1) NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `token_appuser_UNIQUE` (`token`,`appuser_id`),
|
||||
KEY `user` (`appuser_id`),
|
||||
KEY `token` (`token`),
|
||||
CONSTRAINT `fk_2fa_access_token_user_id` FOREIGN KEY (`appuser_id`) REFERENCES `m_appuser` (`id`)
|
||||
);
|
||||
|
||||
-- Configuration
|
||||
|
||||
CREATE TABLE `twofactor_configuration` (
|
||||
`id` bigint(20) NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(40) NOT NULL,
|
||||
`value` varchar(1024) DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `key_UNIQUE` (`name`)
|
||||
);
|
||||
|
||||
INSERT INTO `twofactor_configuration` (`name`, `value`) VALUES
|
||||
('otp-delivery-email-enable', 'true'),
|
||||
('otp-delivery-email-subject', 'Fineract Two-Factor Authentication Token'),
|
||||
('otp-delivery-email-body', 'Hello {{username}}.\nYour OTP login token is {{token}}.'),
|
||||
('otp-delivery-sms-enable', 'false'),
|
||||
('otp-delivery-sms-provider', '1'),
|
||||
('otp-delivery-sms-text', 'Your authentication token for Fineract is {{token}}.'),
|
||||
('otp-token-live-time', '300'),
|
||||
('otp-token-length', '5'),
|
||||
('access-token-live-time', '86400'),
|
||||
('access-token-live-time-extended', '604800');
|
||||
|
||||
|
||||
INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES
|
||||
('authorisation', 'INVALIDATE_TWOFACTOR_ACCESSTOKEN', 'TWOFACTOR_ACCESSTOKEN', 'INVALIDATE', '0'),
|
||||
('configuration', 'READ_TWOFACTOR_CONFIGURATION', 'TWOFACTOR_CONFIGURATION', 'READ', '0'),
|
||||
('configuration', 'UPDATE_TWOFACTOR_CONFIGURATION', 'TWOFACTOR_CONFIGURATION', 'UPDATE', '0'),
|
||||
('special', 'BYPASS_TWOFACTOR', NULL, NULL, '0');
|
||||
Reference in New Issue
Block a user