1 package net.sumaris.server.http.security;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 import net.sumaris.core.dao.administration.user.PersonDao;
26 import net.sumaris.core.dao.administration.user.UserTokenDao;
27 import net.sumaris.core.exception.DataNotFoundException;
28 import net.sumaris.core.model.referential.StatusEnum;
29 import net.sumaris.core.model.referential.UserProfileEnum;
30 import net.sumaris.core.util.crypto.CryptoUtils;
31 import net.sumaris.core.vo.administration.user.PersonVO;
32 import net.sumaris.server.config.SumarisServerConfigurationOption;
33 import net.sumaris.server.service.administration.AccountService;
34 import net.sumaris.server.service.crypto.ServerCryptoService;
35 import net.sumaris.server.vo.security.AuthDataVO;
36 import org.apache.commons.collections.CollectionUtils;
37 import org.apache.commons.lang3.StringUtils;
38 import org.nuiton.i18n.I18n;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
41 import org.springframework.beans.factory.annotation.Autowired;
42 import org.springframework.core.env.Environment;
43 import org.springframework.dao.DataRetrievalFailureException;
44 import org.springframework.security.core.GrantedAuthority;
45 import org.springframework.security.core.authority.mapping.Attributes2GrantedAuthoritiesMapper;
46 import org.springframework.security.core.authority.mapping.SimpleAttributes2GrantedAuthoritiesMapper;
47 import org.springframework.stereotype.Service;
48
49 import javax.annotation.PostConstruct;
50 import java.text.ParseException;
51 import java.util.ArrayList;
52 import java.util.List;
53 import java.util.Objects;
54 import java.util.Optional;
55 import java.util.stream.Collectors;
56
57 @Service("authService")
58 public class AuthServiceImpl implements AuthService {
59
60
61 private static final Logger log =
62 LoggerFactory.getLogger(AuthServiceImpl.class);
63
64 private final ValidationExpiredCache challenges;
65 private final ValidationExpiredCacheMap<AuthUser> checkedTokens;
66 private final boolean debug;
67
68 @Autowired
69 private ServerCryptoService cryptoService;
70
71 @Autowired
72 private AccountService accountService;
73
74 @Autowired
75 private PersonDao personDao;
76
77 @Autowired
78 private UserTokenDao userTokenDao;
79
80 private Attributes2GrantedAuthoritiesMapper authoritiesMapper;
81
82 @Autowired
83 public AuthServiceImpl(Environment environment) {
84
85 int challengeLifeTimeInSeconds = Integer.parseInt(environment.getProperty(SumarisServerConfigurationOption.AUTH_CHALLENGE_LIFE_TIME.getKey(), SumarisServerConfigurationOption.AUTH_CHALLENGE_LIFE_TIME.getDefaultValue()));
86 this.challenges = new ValidationExpiredCache(challengeLifeTimeInSeconds);
87
88 int tokenLifeTimeInSeconds = Integer.parseInt(environment.getProperty(SumarisServerConfigurationOption.AUTH_TOKEN_LIFE_TIME.getKey(), SumarisServerConfigurationOption.AUTH_TOKEN_LIFE_TIME.getDefaultValue()));
89 this.checkedTokens = new ValidationExpiredCacheMap<>(tokenLifeTimeInSeconds);
90
91 authoritiesMapper = new SimpleAttributes2GrantedAuthoritiesMapper();
92
93 this.debug = log.isDebugEnabled();
94 }
95
96 @PostConstruct
97 public void registerListeners() {
98
99 personDao.addListener(new PersonDao.Listener() {
100 @Override
101 public void onSave(PersonVO person) {
102 if (!StringUtils.isNotBlank(person.getPubkey())) return;
103 List<String> tokens = userTokenDao.getAllByPubkey(person.getPubkey());
104 if (CollectionUtils.isEmpty(tokens)) return;
105 tokens.forEach(checkedTokens::remove);
106 }
107
108 @Override
109 public void onDelete(int id) {
110
111 }
112 });
113 }
114
115 @Override
116 public Optional<AuthUser> authenticate(String token) {
117
118
119 if (AnonymousUser.TOKEN.equals(token)) return Optional.of(AnonymousUser.INSTANCE);
120
121
122 if (checkedTokens.contains(token)) return Optional.of(checkedTokens.get(token));
123
124
125 AuthDataVO authData;
126 try {
127 authData = AuthDataVO.parse(token);
128 } catch(ParseException e) {
129 log.warn("Authentication failed. Invalid token: " + token);
130 return Optional.empty();
131 }
132
133
134 AuthUser authUser = authenticate(authData);
135
136 return Optional.ofNullable(authUser);
137 }
138
139 private AuthUser authenticate(AuthDataVO authData) {
140
141
142 try {
143 if (authData.getPubkey().length() < 6) {
144 if (debug) log.debug("Authentication failed. Bad pubkey format: " + authData.getPubkey());
145 return null;
146 }
147 if (!canAuth(authData.getPubkey())) {
148 if (debug) log.debug("Authentication failed. User is not allowed to authenticate: " + authData.getPubkey());
149 return null;
150 }
151 } catch(DataNotFoundException | DataRetrievalFailureException e) {
152 log.debug("Authentication failed. User not found: " + authData.getPubkey());
153 return null;
154 }
155
156
157 boolean isStoredToken = accountService.isStoredToken(authData.asToken(), authData.getPubkey());
158 if (!isStoredToken) {
159 log.debug("Unknown token. Check if response to new challenge...");
160
161
162 if (!challenges.contains(authData.getChallenge())) {
163 if (debug)
164 log.debug("Authentication failed. Challenge not found or expired: " + authData.getChallenge());
165 return null;
166 }
167 }
168
169
170 if (!cryptoService.verify(authData.getChallenge(), authData.getSignature(), authData.getPubkey())) {
171 if (debug) log.debug("Authentication failed. Bad challenge signature in token: " + authData.toString());
172 return null;
173 }
174
175
176
177
178 challenges.remove(authData.getChallenge());
179
180
181 List<GrantedAuthority> authorities = getAuthorities(authData.getPubkey());
182
183
184 AuthUserity/AuthUser.html#AuthUser">AuthUser authUser = new AuthUser(authData, authorities);
185
186
187 String token = authData.toString();
188 checkedTokens.add(token, authUser);
189
190 if(!isStoredToken) {
191
192 try {
193 accountService.addToken(token, authData.getPubkey());
194 } catch (RuntimeException e) {
195
196 log.error("Could not save auth token.", e);
197 }
198 }
199
200 if (debug) log.debug(String.format("Authentication succeed for user with pubkey {%s}", authData.getPubkey().substring(0, 6)));
201
202 return authUser;
203 }
204
205 private List<GrantedAuthority> getAuthorities(String pubkey) {
206 List<Integer> profileIds = accountService.getProfileIdsByPubkey(pubkey);
207
208 return new ArrayList<>(authoritiesMapper.getGrantedAuthorities(profileIds.stream()
209 .map(id -> UserProfileEnum.getLabelById(id).orElse(null))
210 .filter(Objects::nonNull)
211 .collect(Collectors.toSet())));
212
213 }
214
215 private boolean canAuth(final String pubkey) throws DataNotFoundException {
216 PersonVO person = personDao.getByPubkeyOrNull(pubkey);
217 if (person == null) {
218 throw new DataRetrievalFailureException(I18n.t("sumaris.error.account.notFound"));
219 }
220
221
222 StatusEnum status = StatusEnum.valueOf(person.getStatusId());
223 if (StatusEnum.DISABLE.equals(status) || StatusEnum.DELETED.equals(status)) {
224 return false;
225 }
226
227
228
229
230
231
232
233
234
235 return true;
236 }
237
238 public AuthDataVO createNewChallenge() {
239 String challenge = newChallenge();
240 String signature = cryptoService.sign(challenge);
241
242 AuthDataVOy/AuthDataVO.html#AuthDataVO">AuthDataVO result = new AuthDataVO(cryptoService.getServerPubkey(), challenge, signature);
243
244 if (debug) log.debug("New authentication challenge: " + result.toString());
245
246
247 challenges.add(challenge);
248
249 return result;
250 }
251
252
253
254 private String newChallenge() {
255 byte[] randomNonce = cryptoService.getBoxRandomNonce();
256 String randomNonceStr = CryptoUtils.encodeBase64(randomNonce);
257 return cryptoService.hash(randomNonceStr);
258 }
259 }