You are viewing a plain text version of this content. The canonical link for it is here.
Posted to issues@fineract.apache.org by MegaAlex <gi...@git.apache.org> on 2017/06/21 13:13:00 UTC

[GitHub] fineract pull request #374: [WIP] Two-Factor Authentication

GitHub user MegaAlex opened a pull request:

    https://github.com/apache/fineract/pull/374

    [WIP] Two-Factor Authentication

    Work in progress PR. 

You can merge this pull request into a Git repository by running:

    $ git pull https://github.com/MegaAlex/incubator-fineract tfaPhase1WIP

Alternatively you can review and apply these changes as the patch at:

    https://github.com/apache/fineract/pull/374.patch

To close this pull request, make a commit to your master/trunk branch
with (at least) the following in the commit message:

    This closes #374
    
----

----


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] fineract pull request #374: Two-Factor Authentication

Posted by MegaAlex <gi...@git.apache.org>.
Github user MegaAlex commented on a diff in the pull request:

    https://github.com/apache/fineract/pull/374#discussion_r133809070
  
    --- Diff: fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/TwoFactorServiceImpl.java ---
    @@ -0,0 +1,221 @@
    +/**
    + * 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.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;
    +        } else {
    --- End diff --
    
    Without the else, no error would be returned if a request for OTP is sent for undefined delivery method. Would you like me to do the validation somewhere else?


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] fineract pull request #374: Two-Factor Authentication

Posted by avikganguly01 <gi...@git.apache.org>.
Github user avikganguly01 commented on a diff in the pull request:

    https://github.com/apache/fineract/pull/374#discussion_r135409628
  
    --- Diff: api-docs/api-docs.iml ---
    @@ -0,0 +1,9 @@
    +<?xml version="1.0" encoding="UTF-8"?>
    --- End diff --
    
    Add iml files to your .gitignore


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] fineract pull request #374: Two-Factor Authentication

Posted by avikganguly01 <gi...@git.apache.org>.
Github user avikganguly01 commented on a diff in the pull request:

    https://github.com/apache/fineract/pull/374#discussion_r135409691
  
    --- Diff: 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 = new DateTime();
    --- End diff --
    
    @MegaAlex : Point is tenant timezone and hosted server timezone can be different, so use DateUtils.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] fineract pull request #374: Two-Factor Authentication

Posted by MegaAlex <gi...@git.apache.org>.
Github user MegaAlex commented on a diff in the pull request:

    https://github.com/apache/fineract/pull/374#discussion_r133807224
  
    --- Diff: 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')"
    --- End diff --
    
    In the implementation I am proposing, I look at 2fa as an authorisation problem. Users that are two-factor authenticated are granted the _TWOFACTOR_AUTHENTICATED_ authority. Endpoints under _/twofactor_ and _/twofactor/validate_ don't require fully authenticated uses, thus no need to check for TWOFACTOR_AUTHENTICATED.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] fineract issue #374: [WIP] Two-Factor Authentication

Posted by nazeer1100126 <gi...@git.apache.org>.
Github user nazeer1100126 commented on the issue:

    https://github.com/apache/fineract/pull/374
  
    @MegaAlex Send PR with single commit and please resolve the conflicts


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] fineract issue #374: Two-Factor Authentication

Posted by Ezcred <gi...@git.apache.org>.
Github user Ezcred commented on the issue:

    https://github.com/apache/fineract/pull/374
  
    Hi @MegaAlex @nazeer1100126 ,
    
    By when do you think this pull request will be merged ? Any plans of getting otp based authentication ( without using username/password) similar to what uber, whatsapp do.
    
    -
    Thanks


---

[GitHub] fineract issue #374: [WIP] Two-Factor Authentication

Posted by MegaAlex <gi...@git.apache.org>.
Github user MegaAlex commented on the issue:

    https://github.com/apache/fineract/pull/374
  
    @nazeer1100126 ready for review


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] fineract issue #374: Two-Factor Authentication

Posted by MegaAlex <gi...@git.apache.org>.
Github user MegaAlex commented on the issue:

    https://github.com/apache/fineract/pull/374
  
    @nazeer1100126 comments are addressed, commits are squashed.
    



---

[GitHub] fineract pull request #374: Two-Factor Authentication

Posted by nazeer1100126 <gi...@git.apache.org>.
Github user nazeer1100126 commented on a diff in the pull request:

    https://github.com/apache/fineract/pull/374#discussion_r133721774
  
    --- Diff: fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/TwoFactorServiceImpl.java ---
    @@ -0,0 +1,221 @@
    +/**
    + * 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.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;
    +        } else {
    --- End diff --
    
    unnecessary else block.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] fineract pull request #374: Two-Factor Authentication

Posted by nazeer1100126 <gi...@git.apache.org>.
Github user nazeer1100126 commented on a diff in the pull request:

    https://github.com/apache/fineract/pull/374#discussion_r133673728
  
    --- Diff: 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 = new DateTime();
    --- End diff --
    
    make use of DateUtils to get tenant date & time.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] fineract pull request #374: Two-Factor Authentication

Posted by asfgit <gi...@git.apache.org>.
Github user asfgit closed the pull request at:

    https://github.com/apache/fineract/pull/374


---

[GitHub] fineract pull request #374: Two-Factor Authentication

Posted by MegaAlex <gi...@git.apache.org>.
Github user MegaAlex commented on a diff in the pull request:

    https://github.com/apache/fineract/pull/374#discussion_r134323952
  
    --- Diff: fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/TwoFactorServiceImpl.java ---
    @@ -0,0 +1,221 @@
    +/**
    + * 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.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;
    +        } else {
    --- End diff --
    
    Oh, makes perfect sense, yes :+1: 


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] fineract pull request #374: Two-Factor Authentication

Posted by nazeer1100126 <gi...@git.apache.org>.
Github user nazeer1100126 commented on a diff in the pull request:

    https://github.com/apache/fineract/pull/374#discussion_r134218439
  
    --- Diff: fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/TwoFactorServiceImpl.java ---
    @@ -0,0 +1,221 @@
    +/**
    + * 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.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;
    +        } else {
    --- End diff --
    
    I mean to say, you no need to have else block. Directly throw new OTPDeliveryMethodInvalidException();


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] fineract pull request #374: Two-Factor Authentication

Posted by nazeer1100126 <gi...@git.apache.org>.
Github user nazeer1100126 commented on a diff in the pull request:

    https://github.com/apache/fineract/pull/374#discussion_r133669678
  
    --- Diff: fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java ---
    @@ -3041,4 +3041,18 @@ public CommandWrapperBuilder deleteAdHoc(Long adHocId) {
             this.json = "{}";
             return this;
         }
    +
    +    public CommandWrapperBuilder invaldiateTwoFactorAccessToken() {
    --- End diff --
    
     invaldiateTwoFactorAccessToken spelling mistake.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] fineract pull request #374: Two-Factor Authentication

Posted by nazeer1100126 <gi...@git.apache.org>.
Github user nazeer1100126 commented on a diff in the pull request:

    https://github.com/apache/fineract/pull/374#discussion_r133724939
  
    --- Diff: fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/TwoFactorConfigurationValidator.java ---
    @@ -0,0 +1,120 @@
    +/**
    + * Licensed to the Apache Software Foundation (ASF) under one
    + * or more contributor license agreements. See the NOTICE file
    + * distributed with this work for additional information
    + * regarding copyright ownership. The ASF licenses this file
    + * to you under the Apache License, Version 2.0 (the
    + * "License"); you may not use this file except in compliance
    + * with the License. You may obtain a copy of the License at
    + *
    + * http://www.apache.org/licenses/LICENSE-2.0
    + *
    + * Unless required by applicable law or agreed to in writing,
    + * software distributed under the License is distributed on an
    + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    + * KIND, either express or implied. See the License for the
    + * specific language governing permissions and limitations
    + * under the License.
    + */
    +package org.apache.fineract.infrastructure.security.data;
    +
    +import java.lang.reflect.Type;
    +import java.util.ArrayList;
    +import java.util.List;
    +import java.util.Map;
    +
    +import org.apache.commons.lang.StringUtils;
    +import org.apache.fineract.infrastructure.core.data.ApiParameterError;
    +import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
    +import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
    +import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
    +import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
    +import org.apache.fineract.infrastructure.security.constants.TwoFactorConfigurationConstants;
    +import org.springframework.beans.factory.annotation.Autowired;
    +import org.springframework.context.annotation.Profile;
    +import org.springframework.stereotype.Component;
    +
    +import com.google.gson.JsonElement;
    +import com.google.gson.reflect.TypeToken;
    +
    +@Component
    +@Profile("twofactor")
    +public class TwoFactorConfigurationValidator {
    +
    +    private final FromJsonHelper fromJsonHelper;
    +
    +    @Autowired
    +    public TwoFactorConfigurationValidator(FromJsonHelper fromJsonHelper) {
    +        this.fromJsonHelper = fromJsonHelper;
    +    }
    +
    +    public void validateForUpdate(final String json) {
    +        if (StringUtils.isBlank(json)) {
    +            throw new InvalidJsonException();
    +        }
    +
    +        boolean atLeastOneParameterPassedForUpdate = false;
    +        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
    +        this.fromJsonHelper.checkForUnsupportedParameters(typeOfMap, json,
    +                TwoFactorConfigurationConstants.REQUEST_DATA_PARAMETERS);
    +        final JsonElement element = this.fromJsonHelper.parse(json);
    +
    +        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
    +        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
    +                .resource(TwoFactorConfigurationConstants.RESOURCE_NAME);
    +
    +
    +        for(String parameterName : TwoFactorConfigurationConstants.BOOLEAN_PARAMETERS) {
    +            if(this.fromJsonHelper.parameterExists(parameterName, element)) {
    +                atLeastOneParameterPassedForUpdate = true;
    +                validateBooleanParameter(parameterName, element, baseDataValidator);
    +            }
    +        }
    +
    +        for(String parameterName : TwoFactorConfigurationConstants.STRING_PARAMETERS) {
    +            if(this.fromJsonHelper.parameterExists(parameterName, element)) {
    +                atLeastOneParameterPassedForUpdate = true;
    +                validateStringParameter(parameterName, element, baseDataValidator);
    +            }
    +        }
    +
    +        for(String parameterName : TwoFactorConfigurationConstants.NUMBER_PARAMETERS) {
    +            if (this.fromJsonHelper.parameterExists(parameterName, element)) {
    +                atLeastOneParameterPassedForUpdate = true;
    +                validateNumberParameter(parameterName, element, baseDataValidator);
    +            }
    +        }
    +
    +        if(!atLeastOneParameterPassedForUpdate) {
    +            final Object forceError = null;
    +            baseDataValidator.reset().anyOfNotNull(forceError);
    +        }
    +
    +        throwExceptionIfValidationWarningsExist(dataValidationErrors);
    +    }
    +
    +    private void throwExceptionIfValidationWarningsExist(
    +            final List<ApiParameterError> dataValidationErrors) {
    +        if(!dataValidationErrors.isEmpty()) {
    +            throw new PlatformApiDataValidationException(dataValidationErrors);
    +        }
    +    }
    +
    +    private void validateBooleanParameter(final String name, final JsonElement element,
    +                  final DataValidatorBuilder baseDataValidator) {
    +        final String value = this.fromJsonHelper.extractStringNamed(name, element);
    +        baseDataValidator.reset().parameter(name).value(value).notNull().trueOrFalseRequired(value);
    +    }
    +
    +    private void validateStringParameter(final String name, final JsonElement element,
    --- End diff --
    
    with this approach of validation, we can not validate on length property.
    if for any parameter if have max length 50 , then it will hit to database. 


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] fineract pull request #374: Two-Factor Authentication

Posted by nazeer1100126 <gi...@git.apache.org>.
Github user nazeer1100126 commented on a diff in the pull request:

    https://github.com/apache/fineract/pull/374#discussion_r134218197
  
    --- Diff: fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/TwoFactorConfigurationValidator.java ---
    @@ -0,0 +1,120 @@
    +/**
    + * Licensed to the Apache Software Foundation (ASF) under one
    + * or more contributor license agreements. See the NOTICE file
    + * distributed with this work for additional information
    + * regarding copyright ownership. The ASF licenses this file
    + * to you under the Apache License, Version 2.0 (the
    + * "License"); you may not use this file except in compliance
    + * with the License. You may obtain a copy of the License at
    + *
    + * http://www.apache.org/licenses/LICENSE-2.0
    + *
    + * Unless required by applicable law or agreed to in writing,
    + * software distributed under the License is distributed on an
    + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    + * KIND, either express or implied. See the License for the
    + * specific language governing permissions and limitations
    + * under the License.
    + */
    +package org.apache.fineract.infrastructure.security.data;
    +
    +import java.lang.reflect.Type;
    +import java.util.ArrayList;
    +import java.util.List;
    +import java.util.Map;
    +
    +import org.apache.commons.lang.StringUtils;
    +import org.apache.fineract.infrastructure.core.data.ApiParameterError;
    +import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
    +import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
    +import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
    +import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
    +import org.apache.fineract.infrastructure.security.constants.TwoFactorConfigurationConstants;
    +import org.springframework.beans.factory.annotation.Autowired;
    +import org.springframework.context.annotation.Profile;
    +import org.springframework.stereotype.Component;
    +
    +import com.google.gson.JsonElement;
    +import com.google.gson.reflect.TypeToken;
    +
    +@Component
    +@Profile("twofactor")
    +public class TwoFactorConfigurationValidator {
    +
    +    private final FromJsonHelper fromJsonHelper;
    +
    +    @Autowired
    +    public TwoFactorConfigurationValidator(FromJsonHelper fromJsonHelper) {
    +        this.fromJsonHelper = fromJsonHelper;
    +    }
    +
    +    public void validateForUpdate(final String json) {
    +        if (StringUtils.isBlank(json)) {
    +            throw new InvalidJsonException();
    +        }
    +
    +        boolean atLeastOneParameterPassedForUpdate = false;
    +        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
    +        this.fromJsonHelper.checkForUnsupportedParameters(typeOfMap, json,
    +                TwoFactorConfigurationConstants.REQUEST_DATA_PARAMETERS);
    +        final JsonElement element = this.fromJsonHelper.parse(json);
    +
    +        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
    +        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
    +                .resource(TwoFactorConfigurationConstants.RESOURCE_NAME);
    +
    +
    +        for(String parameterName : TwoFactorConfigurationConstants.BOOLEAN_PARAMETERS) {
    +            if(this.fromJsonHelper.parameterExists(parameterName, element)) {
    +                atLeastOneParameterPassedForUpdate = true;
    +                validateBooleanParameter(parameterName, element, baseDataValidator);
    +            }
    +        }
    +
    +        for(String parameterName : TwoFactorConfigurationConstants.STRING_PARAMETERS) {
    +            if(this.fromJsonHelper.parameterExists(parameterName, element)) {
    +                atLeastOneParameterPassedForUpdate = true;
    +                validateStringParameter(parameterName, element, baseDataValidator);
    +            }
    +        }
    +
    +        for(String parameterName : TwoFactorConfigurationConstants.NUMBER_PARAMETERS) {
    +            if (this.fromJsonHelper.parameterExists(parameterName, element)) {
    +                atLeastOneParameterPassedForUpdate = true;
    +                validateNumberParameter(parameterName, element, baseDataValidator);
    +            }
    +        }
    +
    +        if(!atLeastOneParameterPassedForUpdate) {
    +            final Object forceError = null;
    +            baseDataValidator.reset().anyOfNotNull(forceError);
    +        }
    +
    +        throwExceptionIfValidationWarningsExist(dataValidationErrors);
    +    }
    +
    +    private void throwExceptionIfValidationWarningsExist(
    +            final List<ApiParameterError> dataValidationErrors) {
    +        if(!dataValidationErrors.isEmpty()) {
    +            throw new PlatformApiDataValidationException(dataValidationErrors);
    +        }
    +    }
    +
    +    private void validateBooleanParameter(final String name, final JsonElement element,
    +                  final DataValidatorBuilder baseDataValidator) {
    +        final String value = this.fromJsonHelper.extractStringNamed(name, element);
    +        baseDataValidator.reset().parameter(name).value(value).notNull().trueOrFalseRequired(value);
    +    }
    +
    +    private void validateStringParameter(final String name, final JsonElement element,
    --- End diff --
    
    I mean to say, if i have two string parameters and 100 & 200 is max length allowed for them, it is not possible with this approach right? 


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] fineract pull request #374: Two-Factor Authentication

Posted by nazeer1100126 <gi...@git.apache.org>.
Github user nazeer1100126 commented on a diff in the pull request:

    https://github.com/apache/fineract/pull/374#discussion_r133724306
  
    --- Diff: 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')"
    --- End diff --
    
    for pattern="/api/*/twofactor , why we have only isFullyAuthenticated() access?
     for other pattern like  pattern="/api/** we have access access="isFullyAuthenticated() and hasAuthority('TWOFACTOR_AUTHENTICATED') ?


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] fineract issue #374: Two-Factor Authentication

Posted by nazeer1100126 <gi...@git.apache.org>.
Github user nazeer1100126 commented on the issue:

    https://github.com/apache/fineract/pull/374
  
    @MegaAlex does all review comments taken care? If yes, can you send the PR with single commit please. 


---

[GitHub] fineract pull request #374: Two-Factor Authentication

Posted by nazeer1100126 <gi...@git.apache.org>.
Github user nazeer1100126 commented on a diff in the pull request:

    https://github.com/apache/fineract/pull/374#discussion_r133719473
  
    --- Diff: fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/TwoFactorConfigurationServiceImpl.java ---
    @@ -0,0 +1,293 @@
    +/**
    + * 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.TwoFactorConstants;
    +import org.apache.fineract.infrastructure.security.constants.TwoFactorConfigurationConstants;
    +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.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
    +    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
    +    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;
    +    }
    +
    +    public boolean isSMSEnabled() {
    +        return getBooleanConfig(TwoFactorConfigurationConstants.ENABLE_SMS_DELIVERY, false);
    +    }
    +
    +    @Override
    +    public Integer getSMSProviderId() {
    +        Integer value = getIntegerConfig(TwoFactorConfigurationConstants.SMS_PROVIDER_ID,
    +                null);
    +        if(value < 1) {
    +            return null;
    +        }
    +        return value;
    +    }
    +
    +    @Override
    +    public String getSmsText() {
    +        return getStringConfig(TwoFactorConfigurationConstants.SMS_MESSAGE_TEXT, DEFAULT_SMS_TEXT);
    +    }
    +
    +    @Override
    +    public boolean isEmailEnabled() {
    +        return getBooleanConfig(TwoFactorConfigurationConstants.ENABLE_EMAIL_DELIVERY, false);
    +    }
    +
    +    @Override
    +    public String getEmailSubject() {
    +        return getStringConfig(TwoFactorConfigurationConstants.EMAIL_SUBJECT, DEFAULT_EMAIL_SUBJECT);
    +    }
    +
    +    @Override
    +    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
    +    public Integer getOTPTokenLength() {
    +        Integer defaultValue = 5;
    +        Integer value = getIntegerConfig(TwoFactorConfigurationConstants.OTP_TOKEN_LENGTH,
    +                defaultValue);
    +        if(value < 1) {
    --- End diff --
    
    instead of checking here for value less than 1 , from getInteger method only return default value


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] fineract pull request #374: Two-Factor Authentication

Posted by MegaAlex <gi...@git.apache.org>.
Github user MegaAlex commented on a diff in the pull request:

    https://github.com/apache/fineract/pull/374#discussion_r134326228
  
    --- Diff: 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 = new DateTime();
    --- End diff --
    
    I opted for using timestamps for the expiry times for connivance as the period is not that long. Is using date time more feasible in this case also?


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] fineract pull request #374: Two-Factor Authentication

Posted by nazeer1100126 <gi...@git.apache.org>.
Github user nazeer1100126 commented on a diff in the pull request:

    https://github.com/apache/fineract/pull/374#discussion_r133669923
  
    --- Diff: fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/PlatformEmailService.java ---
    @@ -26,5 +26,4 @@ void sendToUserAccount(String organisationName,String contactName,
                                String address, String username, String unencodedPassword);
     
         void sendDefinedEmail(EmailDetail emailDetails);
    --- End diff --
    
    there is no change in this file , please remove this file


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] fineract pull request #374: Two-Factor Authentication

Posted by MegaAlex <gi...@git.apache.org>.
Github user MegaAlex commented on a diff in the pull request:

    https://github.com/apache/fineract/pull/374#discussion_r133808492
  
    --- Diff: fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/TwoFactorConfigurationValidator.java ---
    @@ -0,0 +1,120 @@
    +/**
    + * Licensed to the Apache Software Foundation (ASF) under one
    + * or more contributor license agreements. See the NOTICE file
    + * distributed with this work for additional information
    + * regarding copyright ownership. The ASF licenses this file
    + * to you under the Apache License, Version 2.0 (the
    + * "License"); you may not use this file except in compliance
    + * with the License. You may obtain a copy of the License at
    + *
    + * http://www.apache.org/licenses/LICENSE-2.0
    + *
    + * Unless required by applicable law or agreed to in writing,
    + * software distributed under the License is distributed on an
    + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    + * KIND, either express or implied. See the License for the
    + * specific language governing permissions and limitations
    + * under the License.
    + */
    +package org.apache.fineract.infrastructure.security.data;
    +
    +import java.lang.reflect.Type;
    +import java.util.ArrayList;
    +import java.util.List;
    +import java.util.Map;
    +
    +import org.apache.commons.lang.StringUtils;
    +import org.apache.fineract.infrastructure.core.data.ApiParameterError;
    +import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
    +import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
    +import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
    +import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
    +import org.apache.fineract.infrastructure.security.constants.TwoFactorConfigurationConstants;
    +import org.springframework.beans.factory.annotation.Autowired;
    +import org.springframework.context.annotation.Profile;
    +import org.springframework.stereotype.Component;
    +
    +import com.google.gson.JsonElement;
    +import com.google.gson.reflect.TypeToken;
    +
    +@Component
    +@Profile("twofactor")
    +public class TwoFactorConfigurationValidator {
    +
    +    private final FromJsonHelper fromJsonHelper;
    +
    +    @Autowired
    +    public TwoFactorConfigurationValidator(FromJsonHelper fromJsonHelper) {
    +        this.fromJsonHelper = fromJsonHelper;
    +    }
    +
    +    public void validateForUpdate(final String json) {
    +        if (StringUtils.isBlank(json)) {
    +            throw new InvalidJsonException();
    +        }
    +
    +        boolean atLeastOneParameterPassedForUpdate = false;
    +        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
    +        this.fromJsonHelper.checkForUnsupportedParameters(typeOfMap, json,
    +                TwoFactorConfigurationConstants.REQUEST_DATA_PARAMETERS);
    +        final JsonElement element = this.fromJsonHelper.parse(json);
    +
    +        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
    +        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
    +                .resource(TwoFactorConfigurationConstants.RESOURCE_NAME);
    +
    +
    +        for(String parameterName : TwoFactorConfigurationConstants.BOOLEAN_PARAMETERS) {
    +            if(this.fromJsonHelper.parameterExists(parameterName, element)) {
    +                atLeastOneParameterPassedForUpdate = true;
    +                validateBooleanParameter(parameterName, element, baseDataValidator);
    +            }
    +        }
    +
    +        for(String parameterName : TwoFactorConfigurationConstants.STRING_PARAMETERS) {
    +            if(this.fromJsonHelper.parameterExists(parameterName, element)) {
    +                atLeastOneParameterPassedForUpdate = true;
    +                validateStringParameter(parameterName, element, baseDataValidator);
    +            }
    +        }
    +
    +        for(String parameterName : TwoFactorConfigurationConstants.NUMBER_PARAMETERS) {
    +            if (this.fromJsonHelper.parameterExists(parameterName, element)) {
    +                atLeastOneParameterPassedForUpdate = true;
    +                validateNumberParameter(parameterName, element, baseDataValidator);
    +            }
    +        }
    +
    +        if(!atLeastOneParameterPassedForUpdate) {
    +            final Object forceError = null;
    +            baseDataValidator.reset().anyOfNotNull(forceError);
    +        }
    +
    +        throwExceptionIfValidationWarningsExist(dataValidationErrors);
    +    }
    +
    +    private void throwExceptionIfValidationWarningsExist(
    +            final List<ApiParameterError> dataValidationErrors) {
    +        if(!dataValidationErrors.isEmpty()) {
    +            throw new PlatformApiDataValidationException(dataValidationErrors);
    +        }
    +    }
    +
    +    private void validateBooleanParameter(final String name, final JsonElement element,
    +                  final DataValidatorBuilder baseDataValidator) {
    +        final String value = this.fromJsonHelper.extractStringNamed(name, element);
    +        baseDataValidator.reset().parameter(name).value(value).notNull().trueOrFalseRequired(value);
    +    }
    +
    +    private void validateStringParameter(final String name, final JsonElement element,
    --- End diff --
    
    I am not sure that I understand you completely. AFAICS the values are correctly verified for length > 1000. Do we need to verify the keys also as they are hard-coded? 


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] fineract pull request #374: Two-Factor Authentication

Posted by MegaAlex <gi...@git.apache.org>.
Github user MegaAlex commented on a diff in the pull request:

    https://github.com/apache/fineract/pull/374#discussion_r134325509
  
    --- Diff: fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/TwoFactorConfigurationValidator.java ---
    @@ -0,0 +1,120 @@
    +/**
    + * Licensed to the Apache Software Foundation (ASF) under one
    + * or more contributor license agreements. See the NOTICE file
    + * distributed with this work for additional information
    + * regarding copyright ownership. The ASF licenses this file
    + * to you under the Apache License, Version 2.0 (the
    + * "License"); you may not use this file except in compliance
    + * with the License. You may obtain a copy of the License at
    + *
    + * http://www.apache.org/licenses/LICENSE-2.0
    + *
    + * Unless required by applicable law or agreed to in writing,
    + * software distributed under the License is distributed on an
    + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    + * KIND, either express or implied. See the License for the
    + * specific language governing permissions and limitations
    + * under the License.
    + */
    +package org.apache.fineract.infrastructure.security.data;
    +
    +import java.lang.reflect.Type;
    +import java.util.ArrayList;
    +import java.util.List;
    +import java.util.Map;
    +
    +import org.apache.commons.lang.StringUtils;
    +import org.apache.fineract.infrastructure.core.data.ApiParameterError;
    +import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
    +import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
    +import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
    +import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
    +import org.apache.fineract.infrastructure.security.constants.TwoFactorConfigurationConstants;
    +import org.springframework.beans.factory.annotation.Autowired;
    +import org.springframework.context.annotation.Profile;
    +import org.springframework.stereotype.Component;
    +
    +import com.google.gson.JsonElement;
    +import com.google.gson.reflect.TypeToken;
    +
    +@Component
    +@Profile("twofactor")
    +public class TwoFactorConfigurationValidator {
    +
    +    private final FromJsonHelper fromJsonHelper;
    +
    +    @Autowired
    +    public TwoFactorConfigurationValidator(FromJsonHelper fromJsonHelper) {
    +        this.fromJsonHelper = fromJsonHelper;
    +    }
    +
    +    public void validateForUpdate(final String json) {
    +        if (StringUtils.isBlank(json)) {
    +            throw new InvalidJsonException();
    +        }
    +
    +        boolean atLeastOneParameterPassedForUpdate = false;
    +        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
    +        this.fromJsonHelper.checkForUnsupportedParameters(typeOfMap, json,
    +                TwoFactorConfigurationConstants.REQUEST_DATA_PARAMETERS);
    +        final JsonElement element = this.fromJsonHelper.parse(json);
    +
    +        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
    +        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
    +                .resource(TwoFactorConfigurationConstants.RESOURCE_NAME);
    +
    +
    +        for(String parameterName : TwoFactorConfigurationConstants.BOOLEAN_PARAMETERS) {
    +            if(this.fromJsonHelper.parameterExists(parameterName, element)) {
    +                atLeastOneParameterPassedForUpdate = true;
    +                validateBooleanParameter(parameterName, element, baseDataValidator);
    +            }
    +        }
    +
    +        for(String parameterName : TwoFactorConfigurationConstants.STRING_PARAMETERS) {
    +            if(this.fromJsonHelper.parameterExists(parameterName, element)) {
    +                atLeastOneParameterPassedForUpdate = true;
    +                validateStringParameter(parameterName, element, baseDataValidator);
    +            }
    +        }
    +
    +        for(String parameterName : TwoFactorConfigurationConstants.NUMBER_PARAMETERS) {
    +            if (this.fromJsonHelper.parameterExists(parameterName, element)) {
    +                atLeastOneParameterPassedForUpdate = true;
    +                validateNumberParameter(parameterName, element, baseDataValidator);
    +            }
    +        }
    +
    +        if(!atLeastOneParameterPassedForUpdate) {
    +            final Object forceError = null;
    +            baseDataValidator.reset().anyOfNotNull(forceError);
    +        }
    +
    +        throwExceptionIfValidationWarningsExist(dataValidationErrors);
    +    }
    +
    +    private void throwExceptionIfValidationWarningsExist(
    +            final List<ApiParameterError> dataValidationErrors) {
    +        if(!dataValidationErrors.isEmpty()) {
    +            throw new PlatformApiDataValidationException(dataValidationErrors);
    +        }
    +    }
    +
    +    private void validateBooleanParameter(final String name, final JsonElement element,
    +                  final DataValidatorBuilder baseDataValidator) {
    +        final String value = this.fromJsonHelper.extractStringNamed(name, element);
    +        baseDataValidator.reset().parameter(name).value(value).notNull().trueOrFalseRequired(value);
    +    }
    +
    +    private void validateStringParameter(final String name, final JsonElement element,
    --- End diff --
    
    I validate string parameters here because of the 1000 char limit of the column in the 'key-value' table that is used to store the configs. I haven't really implemented per-key length limits because that would be kind of redundant for the set of this config parameters. I validate them only for the 1000 hard column limit. Sorry if that doesn't make sense.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---