1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package io.github.rose.security.rest.mfa;
17
18 import static org.apache.commons.lang3.StringUtils.repeat;
19
20 import io.github.rose.security.SecurityProperties;
21 import io.github.rose.security.rest.mfa.config.EmailMfaConfig;
22 import io.github.rose.security.rest.mfa.config.MfaConfig;
23 import io.github.rose.security.rest.mfa.config.SmsMfaConfig;
24 import io.github.rose.security.rest.mfa.provider.MfaProvider;
25 import io.github.rose.security.rest.mfa.provider.MfaProviderConfig;
26 import io.github.rose.security.rest.mfa.provider.MfaProviderType;
27 import io.github.rose.security.support.TokenFactory;
28 import io.github.rose.security.util.SecurityUser;
29 import io.github.rose.security.util.SecurityUtils;
30 import io.github.rose.security.util.TokenPair;
31 import java.util.*;
32 import java.util.stream.Collectors;
33 import org.apache.commons.lang3.StringUtils;
34 import org.springframework.beans.factory.annotation.Autowired;
35 import org.springframework.stereotype.Service;
36
37 @Service
38 public class DefaultMfaSettingService implements MfaSettingService {
39
40 private static final RuntimeException PROVIDER_NOT_CONFIGURED_ERROR =
41 new RuntimeException("mfa provider is not configured");
42
43 private static final RuntimeException PROVIDER_NOT_AVAILABLE_ERROR =
44 new RuntimeException("mfa provider is not available");
45
46 private final Map<MfaProviderType, MfaProvider<MfaProviderConfig, MfaConfig>> providers =
47 new EnumMap<>(MfaProviderType.class);
48
49 private final TokenFactory tokenFactory;
50
51 private final MfaProperties mfaProperties;
52
53 private final SecurityProperties securityProperties;
54
55 public DefaultMfaSettingService(
56 TokenFactory tokenFactory, MfaProperties mfaProperties, SecurityProperties securityProperties) {
57 this.tokenFactory = tokenFactory;
58 this.mfaProperties = mfaProperties;
59 this.securityProperties = securityProperties;
60 }
61
62 private static String obfuscate(
63 String input, int seenMargin, char obfuscationChar, int startIndexInclusive, int endIndexExclusive) {
64 String part = input.substring(startIndexInclusive, endIndexExclusive);
65 String obfuscatedPart;
66 if (part.length() <= seenMargin * 2) {
67 obfuscatedPart = repeat(obfuscationChar, part.length());
68 } else {
69 obfuscatedPart = part.substring(0, seenMargin)
70 + repeat(obfuscationChar, part.length() - seenMargin * 2)
71 + part.substring(part.length() - seenMargin);
72 }
73 return input.substring(0, startIndexInclusive) + obfuscatedPart + input.substring(endIndexExclusive);
74 }
75
76 @Autowired
77 private void setProviders(Collection<MfaProvider> providers) {
78 providers.forEach(provider -> {
79 this.providers.put(provider.getType(), provider);
80 });
81 }
82
83 @Override
84 public void prepareVerificationCode() {
85 MfaConfig mfaConfig = mfaProperties.getDefaultConfig();
86 MfaProviderConfig providerConfig = mfaProperties
87 .getProviderConfig(mfaConfig.getProviderType())
88 .orElseThrow(() -> PROVIDER_NOT_CONFIGURED_ERROR);
89 getTwoFaProvider(mfaConfig.getProviderType())
90 .prepareVerificationCode(SecurityUtils.getCurrentUser(), providerConfig, mfaConfig);
91 }
92
93 @Override
94 public TokenPair checkVerificationCode(String verificationCode) {
95 SecurityUser user = SecurityUtils.getCurrentUser();
96 MfaConfig mfaConfig = mfaProperties.getDefaultConfig();
97 MfaProviderConfig providerConfig = mfaProperties
98 .getProviderConfig(mfaConfig.getProviderType())
99 .orElseThrow(() -> PROVIDER_NOT_CONFIGURED_ERROR);
100
101 boolean verificationSuccess = false;
102 if (StringUtils.isNotBlank(verificationCode)) {
103 if (StringUtils.isNumeric(verificationCode) || mfaConfig.getProviderType() == MfaProviderType.BACKUP_CODE) {
104 verificationSuccess = getTwoFaProvider(mfaConfig.getProviderType())
105 .checkVerificationCode(user, verificationCode, providerConfig, mfaConfig);
106 }
107 }
108
109 if (verificationSuccess) {
110 return tokenFactory.createTokenPair(user);
111 } else {
112 throw new RuntimeException("Verification code is incorrect");
113 }
114 }
115
116 private MfaProvider<MfaProviderConfig, MfaConfig> getTwoFaProvider(MfaProviderType providerType) {
117 return Optional.ofNullable(providers.get(providerType)).orElseThrow(() -> PROVIDER_NOT_AVAILABLE_ERROR);
118 }
119
120 @Override
121 public List<MfaAuthController.TwoFaProviderInfo> getAvailableTwoFaProviders() {
122 return mfaProperties.getAllConfigs().stream()
123 .map(config -> {
124 String contact = null;
125 switch (config.getProviderType()) {
126 case SMS:
127 String phoneNumber = ((SmsMfaConfig) config).getPhoneNumber();
128 contact =
129 obfuscate(phoneNumber, 2, '*', phoneNumber.indexOf('+') + 1, phoneNumber.length());
130 break;
131 case EMAIL:
132 String email = ((EmailMfaConfig) config).getEmail();
133 contact = obfuscate(email, 2, '*', 0, email.indexOf('@'));
134 break;
135 }
136 return MfaAuthController.TwoFaProviderInfo.builder()
137 .type(config.getProviderType())
138 .useByDefault(config.isUseByDefault())
139 .contact(contact)
140 .minVerificationCodeSendPeriod(mfaProperties.getMinVerificationCodeSendPeriod())
141 .build();
142 })
143 .collect(Collectors.toList());
144 }
145 }