Skip to content

Commit 729a076

Browse files
Merge pull request #544 from Heigvd/dev
Terms of service / Data policy + mandatory user firstname / lastname
2 parents 931460d + 400b612 commit 729a076

File tree

32 files changed

+3040
-2115
lines changed

32 files changed

+3040
-2115
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* The coLAB project
3+
* Copyright (C) 2021-2023 AlbaSim, MEI, HEIG-VD, HES-SO
4+
*
5+
* Licensed under the MIT License
6+
*/
7+
package ch.colabproject.colab.generator.model.annotations;
8+
9+
import java.lang.annotation.ElementType;
10+
import java.lang.annotation.Retention;
11+
import java.lang.annotation.RetentionPolicy;
12+
import java.lang.annotation.Target;
13+
14+
/**
15+
* Depict REST classes or methods which are available when user consent is not required.
16+
* <p>
17+
* If a class is annotated, all methods are impacted
18+
*
19+
*
20+
* @author mikkelvestergaard
21+
*/
22+
@Target({ElementType.METHOD})
23+
@Retention(RetentionPolicy.RUNTIME)
24+
public @interface ConsentNotRequired {
25+
26+
}

colab-api/src/main/java/ch/colabproject/colab/api/controller/user/UserManager.java

Lines changed: 79 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -136,8 +136,7 @@ public User assertAndGetUser(Long userId) {
136136
* @return the user or null if user not exist or current user has not right to read the user
137137
*/
138138
public User getUserById(Long id) {
139-
User user = userDao.findUser(id);
140-
return user;
139+
return userDao.findUser(id);
141140
}
142141

143142
/**
@@ -160,13 +159,13 @@ public List<User> getUsersForProject(Long projectId) {
160159
* LocalAccount of the user is used.
161160
* <p>
162161
* In case no LocalAccount has been found, authentication method with random parameters is
163-
* returned. Such parameters may be used by clients to create brand new account. This behavior
162+
* returned. Such parameters may be used by clients to create brand-new account. This behavior
164163
* prevents to easy account existence leaks.
165164
*
166165
* @param identifier {@link LocalAccount } email address or {@link User} username
167166
*
168167
* @return authentication method to use to authentication as email owner or new random one which
169-
* can be use to create a brand new localAccount
168+
* can be used to create a brand new localAccount
170169
*
171170
* @throws HttpErrorMessage badRequest if there is no identifier
172171
*/
@@ -180,8 +179,8 @@ public AuthMethod getAuthenticationMethod(String identifier) {
180179

181180
if (account != null) {
182181
return new AuthMethod(account.getCurrentClientHashMethod(),
183-
account.getClientSalt(),
184-
account.getNextClientHashMethod(), account.getNewClientSalt());
182+
account.getClientSalt(),
183+
account.getNextClientHashMethod(), account.getNewClientSalt());
185184
} else {
186185
// no account found, return random method
187186
// TODO: store it in a tmp cache
@@ -201,90 +200,90 @@ public AuthMethod getAuthenticationMethod(String identifier) {
201200
*/
202201
public AuthMethod getDefaultRandomAuthenticationMethod() {
203202
return new AuthMethod(Helper.getDefaultHashMethod(), Helper
204-
.generateHexSalt(SALT_LENGTH), null, null);
203+
.generateHexSalt(SALT_LENGTH), null, null);
205204
}
206205

207206
/**
208-
* Create a brand new user, which can authenticate with a {@link LocalAccount}.First,
209-
* plainPassword will be hashed as any client should do. Then the
210-
* {@link #signup(ch.colabproject.colab.api.model.user.SignUpInfo) signup} method is called.
207+
* {@link #createAdminUser(String, String, String)} within a brand-new transaction.
211208
*
212209
* @param username username
213210
* @param email email address
214211
* @param plainPassword plain text password
215212
*
216-
* @return a brand new user
217-
*
218-
* @throws HttpErrorMessage if username is already taken
219-
*/
220-
public User createUser(String username, String email, String plainPassword) {
221-
AuthMethod method = getDefaultRandomAuthenticationMethod();
222-
SignUpInfo signUpinfo = new SignUpInfo();
223-
224-
signUpinfo.setUsername(username);
225-
signUpinfo.setEmail(email);
226-
signUpinfo.setHashMethod(method.getMandatoryMethod());
227-
228-
signUpinfo.setSalt(method.getSalt());
229-
230-
byte[] hash = method.getMandatoryMethod().hash(plainPassword, method.getSalt());
231-
232-
signUpinfo.setHash(Helper.bytesToHex(hash));
233-
234-
return this.signup(signUpinfo);
235-
}
236-
237-
/**
238-
* {@link #createAdminUser(java.lang.String, java.lang.String, java.lang.String)
239-
* createAdminUser} within a brand new transaction.
240-
*
241-
* @param username username
242-
* @param email email address
243-
* @param plainPassword plain text password
244-
*
245-
* @return a brand new user
213+
* @return a brand-new user to rule them all
246214
*/
247215
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
248216
public User createAdminUserTx(String username, String email, String plainPassword) {
249217
return this.createAdminUser(username, email, plainPassword);
250218
}
251219

252220
/**
253-
* Create a brand new admin user, which can authenticate with a {@link LocalAccount}. First,
254-
* plainPassword will be hashed as any client should do. Then the
255-
* {@link #signup(ch.colabproject.colab.api.model.user.SignUpInfo) signup} method is called.
221+
* Create a brand-new admin user, which can authenticate with a {@link LocalAccount}.
222+
* <p>
223+
* First create the user and the local account, then authenticate and grant admin rights.
256224
*
257225
* @param username username
258226
* @param email email address
259227
* @param plainPassword plain text password
260228
*
261-
* @return a brand new user
229+
* @return a brand-new user to rule them all
262230
*
263231
* @throws HttpErrorMessage if username is already taken
264232
*/
265-
public User createAdminUser(String username, String email, String plainPassword) {
266-
User admin = this.createUser(username, email, plainPassword);
233+
private User createAdminUser(String username, String email, String plainPassword) {
234+
User admin = this.createUserWithLocalAccount(username, username /* username is also used as firstname */, email, plainPassword);
235+
267236
LocalAccount account = (LocalAccount) admin.getAccounts().get(0);
268237

269238
AuthInfo authInfo = new AuthInfo();
270239
authInfo.setIdentifier(username);
271240
authInfo.setMandatoryHash(
272-
Helper.bytesToHex(
273-
account.getCurrentClientHashMethod().hash(
274-
plainPassword,
275-
account.getClientSalt())));
241+
Helper.bytesToHex(
242+
account.getCurrentClientHashMethod().hash(
243+
plainPassword,
244+
account.getClientSalt())));
276245
this.authenticate(authInfo);
277246

278247
this.grantAdminRight(admin.getId());
279248
return admin;
280249
}
281250

251+
/**
252+
* Create a brand-new user, which can authenticate with a {@link LocalAccount}.First,
253+
* plainPassword will be hashed as any client should do. Then the
254+
* {@link #signup(SignUpInfo) signup} method is called.
255+
*
256+
* @param username username
257+
* @param firstname first name
258+
* @param email email address
259+
* @param plainPassword plain text password
260+
*
261+
* @return a brand-new user
262+
*
263+
* @throws HttpErrorMessage if username is already taken
264+
*/
265+
private User createUserWithLocalAccount(String username, String firstname, String email, String plainPassword) {
266+
AuthMethod method = getDefaultRandomAuthenticationMethod();
267+
byte[] hash = method.getMandatoryMethod().hash(plainPassword, method.getSalt());
268+
269+
SignUpInfo signUpInfo = new SignUpInfo();
270+
271+
signUpInfo.setUsername(username);
272+
signUpInfo.setFirstname(firstname);
273+
signUpInfo.setEmail(email);
274+
signUpInfo.setHashMethod(method.getMandatoryMethod());
275+
signUpInfo.setSalt(method.getSalt());
276+
signUpInfo.setHash(Helper.bytesToHex(hash));
277+
278+
return this.signup(signUpInfo);
279+
}
280+
282281
/**
283282
* Create a new user with local account. An e-mail will be sent to user to verify its account
284283
*
285284
* @param signup all info to create a new account
286285
*
287-
* @return brand new user embedding an LocalAccount
286+
* @return brand-new user embedding an LocalAccount
288287
*
289288
* @throws HttpErrorMessage if username is already taken
290289
*/
@@ -315,6 +314,10 @@ public User signup(SignUpInfo signup) {
315314
account.setUser(user);
316315

317316
user.setUsername(signup.getUsername());
317+
user.setFirstname(signup.getFirstname());
318+
user.setLastname(signup.getLastname());
319+
user.setAffiliation(signup.getAffiliation());
320+
user.setAgreedTime(OffsetDateTime.now());
318321

319322
validationManager.assertValid(user);
320323
validationManager.assertValid(account);
@@ -363,20 +366,20 @@ public User authenticate(AuthInfo authInfo) {
363366

364367
AuthenticationFailure aa = sessionManager.getAuthenticationAttempt(account);
365368
if (aa != null) {
366-
logger.warn("Attmpt: {}", aa.getCounter());
369+
logger.warn("Attempt: {}", aa.getCounter());
367370
if (aa.getCounter() >= AUTHENTICATION_ATTEMPT_MAX) {
368371
// max number of failed attempts reached
369372
OffsetDateTime lastAttempt = aa.getTimestamp();
370373
OffsetDateTime delay = lastAttempt
371-
.plusSeconds(AUTHENTICATION_ATTEMPT_RESET_DELAY_SEC);
374+
.plusSeconds(AUTHENTICATION_ATTEMPT_RESET_DELAY_SEC);
372375
if (OffsetDateTime.now().isAfter(delay)) {
373376
// delay has been reached, user may try again
374377
sessionManager.resetAuthenticationAttemptHistory(account);
375378
} else {
376379
// user have to wait some time before any new attempt
377380
logger.warn(
378-
"Account {} reached the max number of failed authentication",
379-
account);
381+
"Account {} reached the max number of failed authentication",
382+
account);
380383
throw HttpErrorMessage.tooManyAttempts();
381384
}
382385
}
@@ -394,7 +397,7 @@ public User authenticate(AuthInfo authInfo) {
394397

395398
// should rotate client method ?
396399
if (account.getNextClientHashMethod() != null
397-
&& authInfo.getOptionalHash() != null) {
400+
&& authInfo.getOptionalHash() != null) {
398401
// rotate method
399402
account.setClientSalt(account.getNewClientSalt());
400403
account.setNewClientSalt(null);
@@ -470,7 +473,7 @@ public void updatePassword(AuthInfo authInfo) {
470473
* hash given client-side hash to dbHash and store it
471474
*
472475
* @param account account to update the hash in
473-
* @param password hash (ie account.clientMethod.hash(clientSalt + plain_password))
476+
* @param hash hash (ie account.clientMethod.hash(clientSalt + plain_password))
474477
*/
475478
private void shadowHash(LocalAccount account, String hash) {
476479
// use a new salt
@@ -498,9 +501,7 @@ public void forceLogout(Long sessionId) {
498501
HttpSession currentSession = requestManager.getHttpSession();
499502
if (httpSession != null) {
500503
if (!httpSession.equals(currentSession)) {
501-
requestManager.sudo(() -> {
502-
sessionManager.deleteHttpSession(httpSession);
503-
});
504+
requestManager.sudo(() -> sessionManager.deleteHttpSession(httpSession));
504505
} else {
505506
throw HttpErrorMessage.badRequest();
506507
}
@@ -510,7 +511,7 @@ public void forceLogout(Long sessionId) {
510511
/**
511512
* Grant admin right to a user.
512513
*
513-
* @param user user who will became an admin
514+
* @param user user who will become an admin
514515
*/
515516
public void grantAdminRight(User user) {
516517
user.setAdmin(true);
@@ -519,7 +520,7 @@ public void grantAdminRight(User user) {
519520
/**
520521
* Grant admin right to a user.
521522
*
522-
* @param id id of user who will became an admin
523+
* @param id id of user who will become an admin
523524
*/
524525
public void grantAdminRight(Long id) {
525526
this.grantAdminRight(userDao.findUser(id));
@@ -598,8 +599,8 @@ public LocalAccount findLocalAccountByIdentifier(String identifier) {
598599
// User found, as authenticationMethod is only available for LocalAccount,
599600
// try to find one
600601
Optional<Account> optAccount = user.getAccounts().stream()
601-
.filter(a -> a instanceof LocalAccount)
602-
.findFirst();
602+
.filter(a -> a instanceof LocalAccount)
603+
.findFirst();
603604
if (optAccount.isPresent()) {
604605
account = (LocalAccount) optAccount.get();
605606
}
@@ -635,7 +636,7 @@ public void requestPasswordReset(String email) {
635636
* @throws ColabMergeException if something went wrong
636637
*/
637638
public LocalAccount updateLocalAccountEmailAddress(LocalAccount account)
638-
throws ColabMergeException {
639+
throws ColabMergeException {
639640
logger.debug("Update LocalAccount email address: {}", account);
640641
LocalAccount managedAccount = (LocalAccount) accountDao.findAccount(account.getId());
641642

@@ -655,7 +656,7 @@ public LocalAccount updateLocalAccountEmailAddress(LocalAccount account)
655656
tokenManager.requestEmailAddressVerification(account, false);
656657
} catch (Exception e) {
657658
// address already used, do not send any email to this address
658-
logger.error("Execption", e);
659+
logger.error("Exception", e);
659660
}
660661
}
661662

@@ -671,15 +672,24 @@ public void setLocalAccountAsVerified(LocalAccount account) {
671672
account.setVerified(Boolean.TRUE);
672673
}
673674

675+
/**
676+
* Update the user agreedTime to now
677+
*
678+
* @param userId id of the user to update
679+
*/
680+
public void updateUserAgreedTime(Long userId) {
681+
User user = assertAndGetUser(userId);
682+
OffsetDateTime now = OffsetDateTime.now();
683+
user.setAgreedTime(now);
684+
}
685+
674686
/**
675687
* Get all session linked to the current user
676688
*
677689
* @return list of all active sessions
678690
*/
679691
public List<HttpSession> getCurrentUserActiveHttpSessions() {
680692
return requestManager.getCurrentUser().getAccounts().stream()
681-
.flatMap(account -> {
682-
return account.getHttpSessions().stream();
683-
}).collect(Collectors.toList());
693+
.flatMap(account -> account.getHttpSessions().stream()).collect(Collectors.toList());
684694
}
685695
}

0 commit comments

Comments
 (0)