| /** |
| * 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 com.github.mustachejava.DefaultMustacheFactory; |
| import com.github.mustachejava.Mustache; |
| import com.github.mustachejava.MustacheFactory; |
| import java.io.StringReader; |
| import java.io.StringWriter; |
| import java.time.LocalDateTime; |
| import java.time.format.DateTimeFormatter; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import org.apache.commons.lang3.BooleanUtils; |
| import org.apache.commons.lang3.math.NumberUtils; |
| 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.springframework.beans.factory.annotation.Autowired; |
| import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; |
| import org.springframework.cache.annotation.CacheEvict; |
| import org.springframework.cache.annotation.Cacheable; |
| import org.springframework.stereotype.Service; |
| |
| @Service |
| @ConditionalOnProperty("fineract.security.2fa.enabled") |
| 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, BooleanUtils.toBooleanObject(configuration.getValue()))) { |
| final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(parameterName); |
| actualChanges.put(parameterName, newValue); |
| configuration.setValue(String.valueOf(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.getValue())) { |
| final String newValue = command.stringValueOfParameterNamed(parameterName).trim(); |
| actualChanges.put(parameterName, newValue); |
| configuration.setValue(newValue); |
| configurationRepository.save(configuration); |
| } |
| } |
| |
| for (final String parameterName : TwoFactorConfigurationConstants.NUMBER_PARAMETERS) { |
| TwoFactorConfiguration configuration = configurationRepository.findByName(parameterName); |
| if (configuration == null) { |
| continue; |
| } |
| |
| if (command.isChangeInIntegerSansLocaleParameterNamed(parameterName, NumberUtils.createInteger(configuration.getValue()))) { |
| final Long newValue = command.longValueOfParameterNamed(parameterName); |
| actualChanges.put(parameterName, newValue); |
| configuration.setValue(String.valueOf(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 == null || 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 = BooleanUtils.toBooleanObject(configuration.getValue()); |
| 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.getValue(); |
| if (value == null) { |
| return defaultValue; |
| } |
| return value; |
| } |
| |
| private Integer getIntegerConfig(final String name, final Integer defaultValue) { |
| final TwoFactorConfiguration configuration = configurationRepository.findByName(name); |
| Integer value = NumberUtils.createInteger(configuration.getValue()); |
| 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 = DateTimeFormatter.ofPattern("HH:mm:ss"); |
| DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy"); |
| |
| final LocalDateTime requestTime = request.getMetadata().getRequestTime().toLocalDateTime(); |
| final LocalDateTime expireTime = requestTime.plusSeconds(request.getMetadata().getTokenLiveTimeInSec()); |
| |
| templateData.put("requestdate", requestTime.toLocalDate().format(dateFormatter)); |
| templateData.put("requesttime", requestTime.toLocalTime().format(timeFormatter)); |
| |
| templateData.put("expiredate", expireTime.toLocalDate().format(dateFormatter)); |
| templateData.put("expiretime", expireTime.toLocalTime().format(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(); |
| } |
| } |