blob: 29c3641e9edac6d1de84d0881ec574b7c75fadf9 [file] [log] [blame]
Michael Hanl87106d12015-09-14 18:13:51 +02001package de.ids_mannheim.korap.security.auth;
2
3import de.ids_mannheim.korap.auditing.AuditRecord;
Michael Hanl87106d12015-09-14 18:13:51 +02004import de.ids_mannheim.korap.config.KustvaktConfiguration;
5import de.ids_mannheim.korap.config.URIParam;
6import de.ids_mannheim.korap.exceptions.*;
Michael Hanl19390652016-01-16 11:01:24 +01007import de.ids_mannheim.korap.interfaces.AuthenticationIface;
8import de.ids_mannheim.korap.interfaces.AuthenticationManagerIface;
9import de.ids_mannheim.korap.interfaces.EncryptionIface;
Michael Hanlf21773f2015-10-16 23:02:31 +020010import de.ids_mannheim.korap.interfaces.db.AuditingIface;
11import de.ids_mannheim.korap.interfaces.db.EntityHandlerIface;
Michael Hanl415276b2016-01-29 16:39:37 +010012import de.ids_mannheim.korap.interfaces.db.UserDataDbIface;
Michael Hanl87106d12015-09-14 18:13:51 +020013import de.ids_mannheim.korap.user.*;
Michael Hanl87106d12015-09-14 18:13:51 +020014import de.ids_mannheim.korap.utils.StringUtils;
15import de.ids_mannheim.korap.utils.TimeUtils;
16import net.sf.ehcache.Cache;
17import net.sf.ehcache.CacheManager;
18import net.sf.ehcache.Element;
19import org.joda.time.DateTime;
20import org.slf4j.Logger;
Michael Hanlac113e52016-01-19 15:49:20 +010021import org.slf4j.LoggerFactory;
Michael Hanl87106d12015-09-14 18:13:51 +020022import org.springframework.cache.annotation.CachePut;
23
24import java.io.UnsupportedEncodingException;
25import java.security.NoSuchAlgorithmException;
Michael Hanl87106d12015-09-14 18:13:51 +020026import java.util.Map;
27
28/**
29 * contains the logic to authentication and registration processes. Uses
30 * interface implementations (AuthenticationIface) for different databases and handlers
31 *
32 * @author hanl
33 */
34public class KustvaktAuthenticationManager extends AuthenticationManagerIface {
35
36 private static String KEY = "kustvakt:key";
Michael Hanlac113e52016-01-19 15:49:20 +010037 private static Logger jlog = LoggerFactory
Michael Hanlfdd9a012015-11-13 15:56:38 +010038 .getLogger(KustvaktAuthenticationManager.class);
Michael Hanl87106d12015-09-14 18:13:51 +020039 private EncryptionIface crypto;
40 private EntityHandlerIface entHandler;
41 private AuditingIface auditing;
Michael Hanle17eaa52016-01-22 20:55:05 +010042 private KustvaktConfiguration config;
Michael Hanl87106d12015-09-14 18:13:51 +020043 private final LoginCounter counter;
44 private Cache user_cache;
45
46 public KustvaktAuthenticationManager(EntityHandlerIface userdb,
47 EncryptionIface crypto, KustvaktConfiguration config,
48 AuditingIface auditer) {
49 this.entHandler = userdb;
Michael Hanle17eaa52016-01-22 20:55:05 +010050 this.config = config;
Michael Hanl87106d12015-09-14 18:13:51 +020051 this.crypto = crypto;
52 this.auditing = auditer;
53 this.counter = new LoginCounter(config);
54 this.user_cache = CacheManager.getInstance().getCache("users");
55 }
56
57 /**
58 * get session object if token was a session token
59 *
60 * @param token
61 * @param host
62 * @param useragent
63 * @return
64 * @throws KustvaktException
65 */
66 public TokenContext getTokenStatus(String token, String host,
67 String useragent) throws KustvaktException {
Michael Hanl3520dcd2016-02-08 19:11:37 +010068 jlog.info("getting session status of token type '{}'",
69 token.split(" ")[0]);
Michael Hanl87106d12015-09-14 18:13:51 +020070 AuthenticationIface provider = getProvider(
Michael Hanl19390652016-01-16 11:01:24 +010071 StringUtils.getTokenType(token), null);
72
73 if (provider == null)
74 // throw exception for missing type paramter
75 throw new KustvaktException(StatusCodes.ILLEGAL_ARGUMENT,
76 "token type not defined or found", "token_type");
77
Michael Hanl87106d12015-09-14 18:13:51 +020078 TokenContext context = provider.getUserStatus(token);
Michael Hanlf1e85e72016-01-21 16:55:45 +010079 // if (!matchStatus(host, useragent, context))
80 // provider.removeUserSession(token);
Michael Hanl87106d12015-09-14 18:13:51 +020081 return context;
82 }
83
84 public User getUser(String username) throws KustvaktException {
85 User user;
86 String key = cache_key(username);
87 Element e = user_cache.get(key);
88
Michael Hanlc4446022016-02-12 18:03:17 +010089 if (User.UserFactory.isDemo(username))
90 return User.UserFactory.getDemoUser();
91
Michael Hanl87106d12015-09-14 18:13:51 +020092 if (e != null) {
93 Map map = (Map) e.getObjectValue();
94 user = User.UserFactory.toUser(map);
95 }else {
Michael Hanl7368aa42016-02-05 18:15:47 +010096 user = entHandler.getAccount(username);
97 user_cache.put(new Element(key, user.toCache()));
98 // todo: not valid. for the duration of the session, the host should not change!
Michael Hanl87106d12015-09-14 18:13:51 +020099 }
100 //todo:
101 // user.addField(Attributes.HOST, context.getHostAddress());
102 // user.addField(Attributes.USER_AGENT, context.getUserAgent());
103 return user;
104 }
105
106 public TokenContext refresh(TokenContext context) throws KustvaktException {
Michael Hanl19390652016-01-16 11:01:24 +0100107 AuthenticationIface provider = getProvider(context.getTokenType(),
108 null);
109 if (provider == null) {
Michael Hanl5fac8ab2016-01-29 16:33:04 +0100110 //todo:
Michael Hanl19390652016-01-16 11:01:24 +0100111 }
112
Michael Hanl87106d12015-09-14 18:13:51 +0200113 try {
114 provider.removeUserSession(context.getToken());
115 User user = getUser(context.getUsername());
Michael Hanl19390652016-01-16 11:01:24 +0100116 return provider.createUserSession(user, context.params());
Michael Hanl87106d12015-09-14 18:13:51 +0200117 }catch (KustvaktException e) {
118 throw new WrappedException(e, StatusCodes.LOGIN_FAILED);
119 }
120 }
121
122 /**
123 * @param type
124 * @param attributes contains username and password to authenticate the user.
125 * Depending of the authentication schema, may contain other values as well
126 * @return User
127 * @throws KustvaktException
128 */
Michael Hanl5fac8ab2016-01-29 16:33:04 +0100129 @Override
Michael Hanl87106d12015-09-14 18:13:51 +0200130 public User authenticate(int type, String username, String password,
Michael Hanl5fac8ab2016-01-29 16:33:04 +0100131 Map<String, Object> attributes) throws KustvaktException {
Michael Hanl87106d12015-09-14 18:13:51 +0200132 User user;
133 switch (type) {
134 case 1:
135 // todo:
136 user = authenticateShib(attributes);
137 break;
138 default:
139 user = authenticate(username, password, attributes);
140 break;
141 }
142 auditing.audit(AuditRecord
143 .serviceRecord(user.getId(), StatusCodes.LOGIN_SUCCESSFUL,
144 user.toString()));
145 return user;
146 }
147
Michael Hanlc2a9f622016-01-28 16:40:06 +0100148 // todo: dont use annotations for caching
Michael Hanl5fac8ab2016-01-29 16:33:04 +0100149 @Override
Michael Hanl87106d12015-09-14 18:13:51 +0200150 @CachePut(value = "users", key = "#user.getUsername()")
Michael Hanl5fac8ab2016-01-29 16:33:04 +0100151 public TokenContext createTokenContext(User user, Map<String, Object> attr,
Michael Hanl87106d12015-09-14 18:13:51 +0200152 String provider_key) throws KustvaktException {
Michael Hanl19390652016-01-16 11:01:24 +0100153 AuthenticationIface provider = getProvider(provider_key,
154 Attributes.API_AUTHENTICATION);
Michael Hanl87106d12015-09-14 18:13:51 +0200155
156 if (attr.get(Attributes.SCOPES) != null)
Michael Hanl5dd931a2016-01-29 16:40:38 +0100157 this.getUserData(user, UserDetails.class);
Michael Hanl87106d12015-09-14 18:13:51 +0200158
159 TokenContext context = provider.createUserSession(user, attr);
160 if (context == null)
161 throw new KustvaktException(StatusCodes.NOT_SUPPORTED);
Michael Hanl5fac8ab2016-01-29 16:33:04 +0100162 context.setUserAgent((String) attr.get(Attributes.USER_AGENT));
Michael Hanl87106d12015-09-14 18:13:51 +0200163 context.setHostAddress(Attributes.HOST);
164 return context;
165 }
166
167 //todo: test
Michael Hanlf1e85e72016-01-21 16:55:45 +0100168 @Deprecated
Michael Hanl87106d12015-09-14 18:13:51 +0200169 private boolean matchStatus(String host, String useragent,
170 TokenContext context) {
171 if (host.equals(context.getHostAddress())) {
172 if (useragent.equals(context.getUserAgent()))
173 return true;
174 }
175 return false;
176 }
177
Michael Hanl5fac8ab2016-01-29 16:33:04 +0100178 private User authenticateShib(Map<String, Object> attributes)
Michael Hanl87106d12015-09-14 18:13:51 +0200179 throws KustvaktException {
180 // todo use persistent id, since eppn is not unique
181 String eppn = (String) attributes.get(Attributes.EPPN);
182
183 if (eppn == null || eppn.isEmpty())
184 throw new KustvaktException(StatusCodes.REQUEST_INVALID);
185
186 if (!attributes.containsKey(Attributes.EMAIL)
Michael Hanle17eaa52016-01-22 20:55:05 +0100187 && crypto.validateEntry(eppn, Attributes.EMAIL) != null)
Michael Hanl87106d12015-09-14 18:13:51 +0200188 attributes.put(Attributes.EMAIL, eppn);
189
190 // fixme?!
191 User user = isRegistered(eppn);
192 if (user == null)
193 user = createShibbUserAccount(attributes);
194 return user;
195 }
196
197 //todo: what if attributes null?
198 private User authenticate(String username, String password,
Michael Hanl5fac8ab2016-01-29 16:33:04 +0100199 Map<String, Object> attr) throws KustvaktException {
200 Map<String, Object> attributes = crypto.validateMap(attr);
Michael Hanl19390652016-01-16 11:01:24 +0100201 String safeUS;
Michael Hanl87106d12015-09-14 18:13:51 +0200202 User unknown;
203 // just to make sure that the plain password does not appear anywhere in the logs!
204
205 try {
Michael Hanle17eaa52016-01-22 20:55:05 +0100206 safeUS = crypto.validateEntry(username, Attributes.USERNAME);
Michael Hanl87106d12015-09-14 18:13:51 +0200207 }catch (KustvaktException e) {
208 throw new WrappedException(e, StatusCodes.LOGIN_FAILED, username);
209 }
210
211 if (safeUS == null || safeUS.isEmpty())
212 throw new WrappedException(new KustvaktException(username,
213 StatusCodes.BAD_CREDENTIALS), StatusCodes.LOGIN_FAILED);
214 else {
215 try {
216 unknown = entHandler.getAccount(safeUS);
Michael Hanl87106d12015-09-14 18:13:51 +0200217 }catch (EmptyResultException e) {
218 // mask exception to disable user guessing in possible attacks
219 throw new WrappedException(new KustvaktException(username,
220 StatusCodes.BAD_CREDENTIALS), StatusCodes.LOGIN_FAILED,
221 username);
222 }catch (KustvaktException e) {
223 throw new WrappedException(e, StatusCodes.LOGIN_FAILED,
224 attributes.toString());
225 }
226 }
Michael Hanle17eaa52016-01-22 20:55:05 +0100227 jlog.trace("Authentication: found username " + unknown.getUsername());
Michael Hanl87106d12015-09-14 18:13:51 +0200228 if (unknown instanceof KorAPUser) {
229 if (password == null || password.isEmpty())
230 throw new WrappedException(
231 new KustvaktException(unknown.getId(),
232 StatusCodes.BAD_CREDENTIALS),
233 StatusCodes.LOGIN_FAILED, username);
234
235 KorAPUser user = (KorAPUser) unknown;
236 boolean check = crypto.checkHash(password, user.getPassword());
237
238 if (!check) {
239 // the fail counter only applies for wrong password
240 jlog.warn("Wrong Password!");
241 processLoginFail(unknown);
242 throw new WrappedException(new KustvaktException(user.getId(),
243 StatusCodes.BAD_CREDENTIALS), StatusCodes.LOGIN_FAILED,
244 username);
245 }
246
247 // bad credentials error has presedence over account locked or unconfirmed codes
248 // since latter can lead to account guessing of third parties
249 if (user.isAccountLocked()) {
250 URIParam param = (URIParam) user.getField(URIParam.class);
251
252 if (param.hasValues()) {
253 jlog.debug("Account is not yet activated for user '{}'",
254 user.getUsername());
255 if (TimeUtils.getNow().isAfter(param.getUriExpiration())) {
Michael Hanlac113e52016-01-19 15:49:20 +0100256 jlog.error(
257 "URI token is expired. Deleting account for user {}",
258 user.getUsername());
Michael Hanl87106d12015-09-14 18:13:51 +0200259 deleteAccount(user);
260 throw new WrappedException(
261 new KustvaktException(unknown.getId(),
262 StatusCodes.EXPIRED,
263 "account confirmation uri has expired",
264 param.getUriFragment()),
265 StatusCodes.LOGIN_FAILED, username);
266 }
267 throw new WrappedException(
268 new KustvaktException(unknown.getId(),
269 StatusCodes.UNCONFIRMED_ACCOUNT),
270 StatusCodes.LOGIN_FAILED, username);
271 }
Michael Hanlac113e52016-01-19 15:49:20 +0100272 jlog.error("ACCESS DENIED: account not active for '{}'",
273 unknown.getUsername());
Michael Hanl87106d12015-09-14 18:13:51 +0200274 throw new WrappedException(
275 new KustvaktException(unknown.getId(),
276 StatusCodes.ACCOUNT_DEACTIVATED),
277 StatusCodes.LOGIN_FAILED, username);
278 }
279
280 }else if (unknown instanceof ShibUser) {
281 //todo
282 }
283 jlog.debug("Authentication done: " + safeUS);
284 return unknown;
285 }
286
287 public User isRegistered(String username) throws KustvaktException {
288 User user;
289 if (username == null || username.isEmpty())
290 throw new KustvaktException(username, StatusCodes.ILLEGAL_ARGUMENT,
291 "username must be set", username);
292
293 try {
294 user = entHandler.getAccount(username);
295 }catch (EmptyResultException e) {
296 jlog.debug("user does not exist ({})", username);
297 return null;
298
299 }catch (KustvaktException e) {
Michael Hanlac113e52016-01-19 15:49:20 +0100300 jlog.error("KorAPException", e);
Michael Hanl87106d12015-09-14 18:13:51 +0200301 throw new KustvaktException(username, StatusCodes.ILLEGAL_ARGUMENT,
302 "username invalid", username);
303 }
304 return user;
305 }
306
307 public void logout(TokenContext context) throws KustvaktException {
308 String key = cache_key(context.getUsername());
309 try {
Michael Hanl19390652016-01-16 11:01:24 +0100310 AuthenticationIface provider = getProvider(context.getTokenType(),
311 null);
312
313 if (provider == null) {
Michael Hanlf1e85e72016-01-21 16:55:45 +0100314 //todo:
315 return;
Michael Hanl19390652016-01-16 11:01:24 +0100316 }
Michael Hanl87106d12015-09-14 18:13:51 +0200317 provider.removeUserSession(context.getToken());
318 }catch (KustvaktException e) {
319 throw new WrappedException(e, StatusCodes.LOGOUT_FAILED,
320 context.toString());
321 }
322 auditing.audit(AuditRecord.serviceRecord(context.getUsername(),
323 StatusCodes.LOGOUT_SUCCESSFUL, context.toString()));
324 user_cache.remove(key);
325 }
326
327 private void processLoginFail(User user) throws KustvaktException {
328 counter.registerFail(user.getUsername());
329 if (!counter.validate(user.getUsername())) {
330 try {
331 this.lockAccount(user);
332 }catch (KustvaktException e) {
Michael Hanlac113e52016-01-19 15:49:20 +0100333 jlog.error("user account could not be locked!", e);
Michael Hanl87106d12015-09-14 18:13:51 +0200334 throw new WrappedException(e,
335 StatusCodes.UPDATE_ACCOUNT_FAILED);
336 }
337 throw new WrappedException(new KustvaktException(user.getId(),
338 StatusCodes.ACCOUNT_DEACTIVATED), StatusCodes.LOGIN_FAILED);
339 }
340 }
341
342 public void lockAccount(User user) throws KustvaktException {
343 if (!(user instanceof KorAPUser))
344 throw new KustvaktException(StatusCodes.REQUEST_INVALID);
345
346 KorAPUser u = (KorAPUser) user;
347 u.setAccountLocked(true);
348 jlog.info("locking account for user: {}", user.getUsername());
349 entHandler.updateAccount(u);
350 }
351
352 public KorAPUser checkPasswordAllowance(KorAPUser user, String oldPassword,
353 String newPassword) throws KustvaktException {
354 String dbPassword = user.getPassword();
355
356 if (oldPassword.trim().equals(newPassword.trim())) {
357 // TODO: special error StatusCodes for this?
358 throw new WrappedException(new KustvaktException(user.getId(),
359 StatusCodes.ILLEGAL_ARGUMENT),
360 StatusCodes.PASSWORD_RESET_FAILED, newPassword);
361 }
362
363 boolean check = crypto.checkHash(oldPassword, dbPassword);
364
365 if (!check)
366 throw new WrappedException(new KustvaktException(user.getId(),
367 StatusCodes.BAD_CREDENTIALS),
368 StatusCodes.PASSWORD_RESET_FAILED);
369
370 try {
371 user.setPassword(crypto.produceSecureHash(newPassword));
372 }catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
373 // throw new KorAPException(StatusCodes.ILLEGAL_ARGUMENT,
374 // "Creating password hash failed!", "password");
375 throw new WrappedException(new KustvaktException(user.getId(),
376 StatusCodes.ILLEGAL_ARGUMENT, "password invalid",
377 newPassword), StatusCodes.PASSWORD_RESET_FAILED,
378 user.toString(), newPassword);
379 }
380 return user;
381 }
382
383 //fixme: use clientinfo for logging/auditing?! = from where did he access the reset function?
384 @Override
385 public void resetPassword(String uriFragment, String username,
386 String newPassphrase) throws KustvaktException {
387 String safeUser, safePass;
388
389 try {
Michael Hanle17eaa52016-01-22 20:55:05 +0100390 safeUser = crypto.validateEntry(username, Attributes.USERNAME);
391 safePass = crypto.validateEntry(newPassphrase, Attributes.PASSWORD);
Michael Hanl87106d12015-09-14 18:13:51 +0200392 }catch (KustvaktException e) {
Michael Hanlac113e52016-01-19 15:49:20 +0100393 jlog.error("Error", e);
Michael Hanl87106d12015-09-14 18:13:51 +0200394 throw new WrappedException(new KustvaktException(username,
395 StatusCodes.ILLEGAL_ARGUMENT, "password invalid",
396 newPassphrase), StatusCodes.PASSWORD_RESET_FAILED, username,
397 newPassphrase);
398 }
399
400 try {
401 safePass = crypto.produceSecureHash(safePass);
402 }catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
Michael Hanlac113e52016-01-19 15:49:20 +0100403 jlog.error("Encoding/Algorithm Error", e);
Michael Hanl87106d12015-09-14 18:13:51 +0200404 throw new WrappedException(new KustvaktException(username,
405 StatusCodes.ILLEGAL_ARGUMENT, "password invalid",
406 newPassphrase), StatusCodes.PASSWORD_RESET_FAILED, username,
407 uriFragment, newPassphrase);
408 }
409 int result = entHandler
410 .resetPassphrase(safeUser, uriFragment, safePass);
411
412 if (result == 0)
413 throw new WrappedException(
414 new KustvaktException(username, StatusCodes.EXPIRED,
415 "URI fragment expired", uriFragment),
416 StatusCodes.PASSWORD_RESET_FAILED, username, uriFragment);
417 else if (result == 1)
418 jlog.info("successfully reset password for user {}", safeUser);
419 }
420
421 public void confirmRegistration(String uriFragment, String username)
422 throws KustvaktException {
423 String safeUser;
424 try {
Michael Hanle17eaa52016-01-22 20:55:05 +0100425 safeUser = crypto.validateEntry(username, Attributes.USERNAME);
Michael Hanl87106d12015-09-14 18:13:51 +0200426 }catch (KustvaktException e) {
Michael Hanlac113e52016-01-19 15:49:20 +0100427 jlog.error("error", e);
Michael Hanl87106d12015-09-14 18:13:51 +0200428 throw new WrappedException(e,
429 StatusCodes.ACCOUNT_CONFIRMATION_FAILED, username,
430 uriFragment);
431 }
432 int r = entHandler.activateAccount(safeUser, uriFragment);
433 if (r == 0) {
434 User user;
435 try {
436 user = entHandler.getAccount(username);
437 }catch (EmptyResultException e) {
438 throw new WrappedException(new KustvaktException(username,
439 StatusCodes.BAD_CREDENTIALS),
440 StatusCodes.ACCOUNT_CONFIRMATION_FAILED, username,
441 uriFragment);
442 }
443 entHandler.deleteAccount(user.getId());
444 throw new WrappedException(
445 new KustvaktException(user.getId(), StatusCodes.EXPIRED),
446 StatusCodes.ACCOUNT_CONFIRMATION_FAILED, username,
447 uriFragment);
448 }else if (r == 1)
449 jlog.info("successfully confirmed user registration for user {}",
450 safeUser);
451 // register successful audit!
452 }
453
454 /**
455 * @param attributes
456 * @return
457 * @throws KustvaktException
458 */
459 //fixme: remove clientinfo object (not needed), use json representation to get stuff
Michael Hanl5fac8ab2016-01-29 16:33:04 +0100460 public User createUserAccount(Map<String, Object> attributes,
461 boolean confirmation_required) throws KustvaktException {
462 Map<String, Object> safeMap = crypto.validateMap(attributes);
Michael Hanl87106d12015-09-14 18:13:51 +0200463 if (safeMap.get(Attributes.USERNAME) == null || ((String) safeMap
464 .get(Attributes.USERNAME)).isEmpty())
465 throw new KustvaktException(StatusCodes.ILLEGAL_ARGUMENT,
466 "username must be set", "username");
467 if (safeMap.get(Attributes.PASSWORD) == null || ((String) safeMap
468 .get(Attributes.PASSWORD)).isEmpty())
469 throw new KustvaktException(safeMap.get(Attributes.USERNAME),
470 StatusCodes.ILLEGAL_ARGUMENT, "password must be set",
471 "password");
472
Michael Hanl5fac8ab2016-01-29 16:33:04 +0100473 String username = crypto
474 .validateEntry((String) safeMap.get(Attributes.USERNAME),
475 Attributes.USERNAME);
476 String safePass = crypto
477 .validateEntry((String) safeMap.get(Attributes.PASSWORD),
478 Attributes.PASSWORD);
Michael Hanl87106d12015-09-14 18:13:51 +0200479 String hash;
480 try {
481 hash = crypto.produceSecureHash(safePass);
482 }catch (UnsupportedEncodingException | NoSuchAlgorithmException e) {
Michael Hanlac113e52016-01-19 15:49:20 +0100483 jlog.error("Encryption error", e);
Michael Hanl87106d12015-09-14 18:13:51 +0200484 throw new KustvaktException(StatusCodes.ILLEGAL_ARGUMENT);
485 }
486
Michael Hanle17eaa52016-01-22 20:55:05 +0100487 KorAPUser user = User.UserFactory.getUser(username);
Michael Hanl7368aa42016-02-05 18:15:47 +0100488 user.setAccountLocked(confirmation_required);
489
Michael Hanle17eaa52016-01-22 20:55:05 +0100490 if (confirmation_required) {
Michael Hanle17eaa52016-01-22 20:55:05 +0100491 URIParam param = new URIParam(crypto.createToken(),
492 TimeUtils.plusSeconds(config.getExpiration()).getMillis());
Michael Hanl19390652016-01-16 11:01:24 +0100493 user.addField(param);
494 }
Michael Hanl87106d12015-09-14 18:13:51 +0200495 user.setPassword(hash);
496 try {
Michael Hanlc4446022016-02-12 18:03:17 +0100497 jlog.info("Creating new user account for user {}",
498 user.getUsername());
Michael Hanl87106d12015-09-14 18:13:51 +0200499 entHandler.createAccount(user);
Michael Hanl5dd931a2016-01-29 16:40:38 +0100500 UserDetails details = new UserDetails(user.getId());
Michael Hanl5fac8ab2016-01-29 16:33:04 +0100501 details.readDefaults(safeMap);
502 details.checkRequired();
503
Michael Hanl5dd931a2016-01-29 16:40:38 +0100504 UserSettings settings = new UserSettings(user.getId());
Michael Hanl5fac8ab2016-01-29 16:33:04 +0100505 settings.readDefaults(safeMap);
506 settings.checkRequired();
507
Michael Hanl5dd931a2016-01-29 16:40:38 +0100508 UserdataFactory.getDaoInstance(UserDetails.class).store(details);
509 UserdataFactory.getDaoInstance(UserSettings.class).store(settings);
Michael Hanl87106d12015-09-14 18:13:51 +0200510 }catch (KustvaktException e) {
Michael Hanl83356752016-02-06 14:33:50 +0100511 e.printStackTrace();
Michael Hanl87106d12015-09-14 18:13:51 +0200512 throw new WrappedException(e, StatusCodes.CREATE_ACCOUNT_FAILED,
513 user.toString());
514 }
515
516 auditing.audit(AuditRecord.serviceRecord(user.getUsername(),
517 StatusCodes.CREATE_ACCOUNT_SUCCESSFUL));
518 return user;
519 }
520
521 //todo:
Michael Hanl5fac8ab2016-01-29 16:33:04 +0100522 private ShibUser createShibbUserAccount(Map<String, Object> attributes)
Michael Hanl87106d12015-09-14 18:13:51 +0200523 throws KustvaktException {
524 jlog.debug("creating shibboleth user account for user attr: {}",
525 attributes);
Michael Hanl5fac8ab2016-01-29 16:33:04 +0100526 Map<String, Object> safeMap = crypto.validateMap(attributes);
Michael Hanl87106d12015-09-14 18:13:51 +0200527
528 //todo eppn non-unique.join with idp or use persistent_id as username identifier
529 ShibUser user = User.UserFactory
530 .getShibInstance((String) safeMap.get(Attributes.EPPN),
531 (String) safeMap.get(Attributes.MAIL),
532 (String) safeMap.get(Attributes.CN));
533 user.setAffiliation((String) safeMap.get(Attributes.EDU_AFFIL));
Michael Hanl87106d12015-09-14 18:13:51 +0200534 user.setAccountCreation(TimeUtils.getNow().getMillis());
535 entHandler.createAccount(user);
Michael Hanl25aac542016-02-01 18:16:44 +0100536
537 UserDetails d = new UserDetails(user.getId());
538 d.readDefaults(attributes);
539 d.checkRequired();
540
541 UserdataFactory.getDaoInstance(d.getClass()).store(d);
542
543 UserSettings s = new UserSettings(user.getId());
544 s.readDefaults(attributes);
545 s.checkRequired();
546 UserdataFactory.getDaoInstance(s.getClass()).store(s);
547
Michael Hanl87106d12015-09-14 18:13:51 +0200548 return user;
549 }
550
551 /**
552 * link shibboleth and korap user account to one another.
553 *
554 * @param current currently logged in user
555 * @param for_name foreign user name the current account should be linked to
556 * @param transstrat transfer status of user data (details, settings, user queries)
557 * 0 = the currently logged in data should be kept
558 * 1 = the foreign account data should be kept
559 * @throws NotAuthorizedException
560 * @throws KustvaktException
561 */
562 // todo:
563 public void accountLink(User current, String for_name, int transstrat)
564 throws KustvaktException {
565 // User foreign = entHandler.getAccount(for_name);
566
567 // if (current.getAccountLink() == null && current.getAccountLink()
568 // .isEmpty()) {
569 // if (current instanceof KorAPUser && foreign instanceof ShibUser) {
570 // if (transstrat == 1)
571 // current.transfer(foreign);
572 //// foreign.setAccountLink(current.getUsername());
573 //// current.setAccountLink(foreign.getUsername());
574 // // entHandler.purgeDetails(foreign);
575 // // entHandler.purgeSettings(foreign);
576 // }else if (foreign instanceof KorAPUser
577 // && current instanceof ShibUser) {
578 // if (transstrat == 0)
579 // foreign.transfer(current);
580 //// current.setAccountLink(foreign.getUsername());
581 // // entHandler.purgeDetails(current);
582 // // entHandler.purgeSettings(current);
583 // // entHandler.purgeSettings(current);
584 // }
585 // entHandler.updateAccount(current);
586 // entHandler.updateAccount(foreign);
587 // }
588 }
589
Michael Hanl87106d12015-09-14 18:13:51 +0200590 public boolean updateAccount(User user) throws KustvaktException {
591 boolean result;
592 String key = cache_key(user.getUsername());
593 if (user instanceof DemoUser)
594 throw new KustvaktException(user.getId(),
595 StatusCodes.REQUEST_INVALID,
596 "account not updateable for demo user", user.getUsername());
597 else {
Michael Hanle17eaa52016-01-22 20:55:05 +0100598 // crypto.validate(user);
Michael Hanl87106d12015-09-14 18:13:51 +0200599 try {
600 result = entHandler.updateAccount(user) > 0;
601 }catch (KustvaktException e) {
Michael Hanlac113e52016-01-19 15:49:20 +0100602 jlog.error("Error ", e);
Michael Hanl87106d12015-09-14 18:13:51 +0200603 throw new WrappedException(e,
604 StatusCodes.UPDATE_ACCOUNT_FAILED);
605 }
606 }
607 if (result) {
608 user_cache.remove(key);
609 auditing.audit(AuditRecord.serviceRecord(user.getId(),
610 StatusCodes.UPDATE_ACCOUNT_SUCCESSFUL, user.toString()));
611 }
612 return result;
613 }
614
615 public boolean deleteAccount(User user) throws KustvaktException {
616 boolean result;
617 String key = cache_key(user.getUsername());
618 if (user instanceof DemoUser)
619 return true;
620 else {
621 try {
622 result = entHandler.deleteAccount(user.getId()) > 0;
623 }catch (KustvaktException e) {
Michael Hanlac113e52016-01-19 15:49:20 +0100624 jlog.error("Error ", e);
Michael Hanl87106d12015-09-14 18:13:51 +0200625 throw new WrappedException(e,
626 StatusCodes.DELETE_ACCOUNT_FAILED);
627 }
628 }
629 if (result) {
630 user_cache.remove(key);
631 auditing.audit(AuditRecord.serviceRecord(user.getUsername(),
632 StatusCodes.DELETE_ACCOUNT_SUCCESSFUL, user.toString()));
633 }
634 return result;
635 }
636
637 public Object[] validateResetPasswordRequest(String username, String email)
638 throws KustvaktException {
639 String mail, uritoken;
Michael Hanle17eaa52016-01-22 20:55:05 +0100640 mail = crypto.validateEntry(email, Attributes.EMAIL);
Michael Hanl87106d12015-09-14 18:13:51 +0200641 User ident;
642 try {
643 ident = entHandler.getAccount(username);
644 if (ident instanceof DemoUser)
645 // throw new NotAuthorizedException(StatusCodes.PERMISSION_DENIED,
646 // "password reset now allowed for DemoUser", "");
647 throw new WrappedException(username,
648 StatusCodes.PASSWORD_RESET_FAILED, username);
649 }catch (EmptyResultException e) {
650 throw new WrappedException(new KustvaktException(username,
651 StatusCodes.ILLEGAL_ARGUMENT, "username not found",
652 username), StatusCodes.PASSWORD_RESET_FAILED, username);
653 }
654
Michael Hanl5dd931a2016-01-29 16:40:38 +0100655 Userdata data = this.getUserData(ident, UserDetails.class);
Michael Hanl87106d12015-09-14 18:13:51 +0200656 KorAPUser user = (KorAPUser) ident;
Michael Hanl5fac8ab2016-01-29 16:33:04 +0100657
658 if (!mail.equals(data.get(Attributes.EMAIL)))
Michael Hanl87106d12015-09-14 18:13:51 +0200659 // throw new NotAuthorizedException(StatusCodes.ILLEGAL_ARGUMENT,
660 // "invalid parameter: email", "email");
661 throw new WrappedException(new KustvaktException(user.getId(),
662 StatusCodes.ILLEGAL_ARGUMENT, "email invalid", email),
663 StatusCodes.PASSWORD_RESET_FAILED, email);
664 uritoken = crypto.encodeBase();
665 URIParam param = new URIParam(uritoken,
666 TimeUtils.plusHours(24).getMillis());
667 user.addField(param);
668
669 try {
670 entHandler.updateAccount(user);
671 }catch (KustvaktException e) {
Michael Hanlac113e52016-01-19 15:49:20 +0100672 jlog.error("Error ", e);
Michael Hanl87106d12015-09-14 18:13:51 +0200673 throw new WrappedException(e, StatusCodes.PASSWORD_RESET_FAILED);
674 }
675 return new Object[] { uritoken,
676 new DateTime(param.getUriExpiration()) };
677 }
678
Michael Hanlc2a9f622016-01-28 16:40:06 +0100679 @Override
Michael Hanl5fac8ab2016-01-29 16:33:04 +0100680 public <T extends Userdata> T getUserData(User user, Class<T> clazz)
681 throws WrappedException {
682
683 try {
684 UserDataDbIface<T> dao = UserdataFactory.getDaoInstance(clazz);
Michael Hanl7368aa42016-02-05 18:15:47 +0100685 T data = dao.get(user);
686 if (data == null)
687 throw new WrappedException(user.getId(),
688 StatusCodes.EMPTY_RESULTS, clazz.getSimpleName());
689
690 return data;
Michael Hanl5fac8ab2016-01-29 16:33:04 +0100691 }catch (KustvaktException e) {
692 jlog.error("Error ", e);
693 throw new WrappedException(e, StatusCodes.GET_ACCOUNT_FAILED);
694 }
Michael Hanl4f9002d2016-01-27 23:21:45 +0100695 }
696
697 //todo: cache userdata outside of the user object!
Michael Hanlc2a9f622016-01-28 16:40:06 +0100698 @Override
Michael Hanl5fac8ab2016-01-29 16:33:04 +0100699 public void updateUserData(Userdata data) throws WrappedException {
Michael Hanl87106d12015-09-14 18:13:51 +0200700 try {
Michael Hanl5fac8ab2016-01-29 16:33:04 +0100701 data.validate(this.crypto);
702 UserDataDbIface dao = UserdataFactory
703 .getDaoInstance(data.getClass());
704 dao.update(data);
Michael Hanl87106d12015-09-14 18:13:51 +0200705 }catch (KustvaktException e) {
Michael Hanl5fac8ab2016-01-29 16:33:04 +0100706 jlog.error("Error ", e);
707 throw new WrappedException(e, StatusCodes.UPDATE_ACCOUNT_FAILED);
Michael Hanl87106d12015-09-14 18:13:51 +0200708 }
Michael Hanl87106d12015-09-14 18:13:51 +0200709 }
710
Michael Hanl5fac8ab2016-01-29 16:33:04 +0100711 // public UserDetails getUserDetails(User user) throws KustvaktException {
712 // try {
713 // if (user.getDetails() == null)
714 // user.setDetails(entHandler.getUserDetails(user.getId()));
715 // }catch (KustvaktException e) {
716 // throw new WrappedException(e, StatusCodes.GET_ACCOUNT_FAILED);
717 // }
718 // return user.getDetails();
719 // }
720 //
721 // public UserSettings getUserSettings(User user) throws KustvaktException {
722 // try {
723 // if (user.getSettings() == null)
724 // user.setSettings(entHandler.getUserSettings(user.getId()));
725 // }catch (KustvaktException e) {
726 // throw new WrappedException(e, StatusCodes.GET_ACCOUNT_FAILED);
727 // }
728 // return user.getSettings();
729 // }
Michael Hanl87106d12015-09-14 18:13:51 +0200730
Michael Hanlf21773f2015-10-16 23:02:31 +0200731 private String cache_key(String input) throws KustvaktException {
732 try {
733 return crypto.hash(KEY + "@" + input);
734 }catch (Exception e) {
735 jlog.error("illegal cache key input '{}'", input);
736 throw new KustvaktException(StatusCodes.ILLEGAL_ARGUMENT,
737 "missing or illegal cache key", input);
738 }
Michael Hanl87106d12015-09-14 18:13:51 +0200739 }
740}