fineract-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From nazeer1100...@apache.org
Subject [1/4] fineract git commit: Two-Factor Authentication
Date Mon, 27 Nov 2017 12:11:24 GMT
Repository: fineract
Updated Branches:
  refs/heads/develop 4dbecc7f0 -> c689c143e


http://git-wip-us.apache.org/repos/asf/fineract/blob/1a966e8e/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/domain/TFAccessToken.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/domain/TFAccessToken.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/domain/TFAccessToken.java
new file mode 100644
index 0000000..694046f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/domain/TFAccessToken.java
@@ -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());
+    }
+}

http://git-wip-us.apache.org/repos/asf/fineract/blob/1a966e8e/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/domain/TFAccessTokenRepository.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/domain/TFAccessTokenRepository.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/domain/TFAccessTokenRepository.java
new file mode 100644
index 0000000..cbdbc8f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/domain/TFAccessTokenRepository.java
@@ -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);
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/fineract/blob/1a966e8e/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/domain/TwoFactorConfiguration.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/domain/TwoFactorConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/domain/TwoFactorConfiguration.java
new file mode 100644
index 0000000..e45af00
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/domain/TwoFactorConfiguration.java
@@ -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);
+    }
+}

http://git-wip-us.apache.org/repos/asf/fineract/blob/1a966e8e/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/domain/TwoFactorConfigurationRepository.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/domain/TwoFactorConfigurationRepository.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/domain/TwoFactorConfigurationRepository.java
new file mode 100644
index 0000000..0407f0c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/domain/TwoFactorConfigurationRepository.java
@@ -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();
+}

http://git-wip-us.apache.org/repos/asf/fineract/blob/1a966e8e/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/exception/AccessTokenInvalidIException.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/exception/AccessTokenInvalidIException.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/exception/AccessTokenInvalidIException.java
new file mode 100644
index 0000000..4c39bd1
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/exception/AccessTokenInvalidIException.java
@@ -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");
+    }
+}

http://git-wip-us.apache.org/repos/asf/fineract/blob/1a966e8e/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/exception/OTPDeliveryMethodInvalidException.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/exception/OTPDeliveryMethodInvalidException.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/exception/OTPDeliveryMethodInvalidException.java
new file mode 100644
index 0000000..0fe0237
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/exception/OTPDeliveryMethodInvalidException.java
@@ -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.");
+    }
+}

http://git-wip-us.apache.org/repos/asf/fineract/blob/1a966e8e/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/exception/OTPTokenInvalidException.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/exception/OTPTokenInvalidException.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/exception/OTPTokenInvalidException.java
new file mode 100644
index 0000000..12d11e9
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/exception/OTPTokenInvalidException.java
@@ -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");
+    }
+}

http://git-wip-us.apache.org/repos/asf/fineract/blob/1a966e8e/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/filter/InsecureTwoFactorAuthenticationFilter.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/filter/InsecureTwoFactorAuthenticationFilter.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/filter/InsecureTwoFactorAuthenticationFilter.java
new file mode 100644
index 0000000..2f63eba
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/filter/InsecureTwoFactorAuthenticationFilter.java
@@ -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);
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/fineract/blob/1a966e8e/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/filter/TwoFactorAuthenticationFilter.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/filter/TwoFactorAuthenticationFilter.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/filter/TwoFactorAuthenticationFilter.java
new file mode 100644
index 0000000..6db141e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/filter/TwoFactorAuthenticationFilter.java
@@ -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;
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/fineract/blob/1a966e8e/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/AccessTokenGenerationService.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/AccessTokenGenerationService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/AccessTokenGenerationService.java
new file mode 100644
index 0000000..e477593
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/AccessTokenGenerationService.java
@@ -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();
+}

http://git-wip-us.apache.org/repos/asf/fineract/blob/1a966e8e/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/RandomOTPGenerator.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/RandomOTPGenerator.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/RandomOTPGenerator.java
new file mode 100644
index 0000000..d3bf551
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/RandomOTPGenerator.java
@@ -0,0 +1,38 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.security.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();
+    }
+}

http://git-wip-us.apache.org/repos/asf/fineract/blob/1a966e8e/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/TwoFactorConfigurationService.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/TwoFactorConfigurationService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/TwoFactorConfigurationService.java
new file mode 100644
index 0000000..aaf9354
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/TwoFactorConfigurationService.java
@@ -0,0 +1,51 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.security.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);
+}

http://git-wip-us.apache.org/repos/asf/fineract/blob/1a966e8e/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/TwoFactorConfigurationServiceImpl.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/TwoFactorConfigurationServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/TwoFactorConfigurationServiceImpl.java
new file mode 100644
index 0000000..bd4c108
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/TwoFactorConfigurationServiceImpl.java
@@ -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();
+    }
+}

http://git-wip-us.apache.org/repos/asf/fineract/blob/1a966e8e/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/TwoFactorService.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/TwoFactorService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/TwoFactorService.java
new file mode 100644
index 0000000..da556d0
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/TwoFactorService.java
@@ -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);
+
+}

http://git-wip-us.apache.org/repos/asf/fineract/blob/1a966e8e/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/TwoFactorServiceImpl.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/TwoFactorServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/TwoFactorServiceImpl.java
new file mode 100644
index 0000000..64361c2
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/TwoFactorServiceImpl.java
@@ -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);
+    }
+}

http://git-wip-us.apache.org/repos/asf/fineract/blob/1a966e8e/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/TwoFactorUtils.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/TwoFactorUtils.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/TwoFactorUtils.java
new file mode 100644
index 0000000..8929246
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/TwoFactorUtils.java
@@ -0,0 +1,47 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.security.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;
+    }
+}

http://git-wip-us.apache.org/repos/asf/fineract/blob/1a966e8e/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/UUIDAccessTokenGenerationService.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/UUIDAccessTokenGenerationService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/UUIDAccessTokenGenerationService.java
new file mode 100644
index 0000000..12e09c4
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/UUIDAccessTokenGenerationService.java
@@ -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("-", "");
+    }
+}

http://git-wip-us.apache.org/repos/asf/fineract/blob/1a966e8e/fineract-provider/src/main/java/org/apache/fineract/useradministration/domain/AppUser.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/domain/AppUser.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/domain/AppUser.java
index 9c8a86f..d5bcfd5 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/useradministration/domain/AppUser.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/domain/AppUser.java
@@ -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";

http://git-wip-us.apache.org/repos/asf/fineract/blob/1a966e8e/fineract-provider/src/main/resources/META-INF/spring/ehcache.xml
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/resources/META-INF/spring/ehcache.xml b/fineract-provider/src/main/resources/META-INF/spring/ehcache.xml
index b991c69..b5a442d 100644
--- a/fineract-provider/src/main/resources/META-INF/spring/ehcache.xml
+++ b/fineract-provider/src/main/resources/META-INF/spring/ehcache.xml
@@ -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>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/fineract/blob/1a966e8e/fineract-provider/src/main/resources/META-INF/spring/securityContext.xml
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/resources/META-INF/spring/securityContext.xml b/fineract-provider/src/main/resources/META-INF/spring/securityContext.xml
index f03220f..a4db7fe 100644
--- a/fineract-provider/src/main/resources/META-INF/spring/securityContext.xml
+++ b/fineract-provider/src/main/resources/META-INF/spring/securityContext.xml
@@ -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>
 	

http://git-wip-us.apache.org/repos/asf/fineract/blob/1a966e8e/fineract-provider/src/main/resources/sql/migrations/core_db/V336__two_factor_authentication.sql
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V336__two_factor_authentication.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V336__two_factor_authentication.sql
new file mode 100644
index 0000000..bd5d359
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V336__two_factor_authentication.sql
@@ -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');
\ No newline at end of file


Mime
View raw message