Made User Merge logic functional

This commit is contained in:
George Kalampokis 2020-10-27 18:06:58 +02:00
parent eeca52d3ff
commit ff169ae806
19 changed files with 144 additions and 26 deletions

View File

@ -26,6 +26,7 @@ public class UserInfoDaoImpl extends DatabaseAccess<UserInfo> implements UserInf
@Override
public QueryableList<UserInfo> getWithCriteria(UserInfoCriteria criteria) {
QueryableList<UserInfo> users = this.getDatabaseService().getQueryable(UserInfo.class);
users.where(((builder, root) -> builder.equal(root.get("userStatus"), 0)));
if (criteria.getAppRoles() != null && !criteria.getAppRoles().isEmpty())
users.where((builder, root) -> root.join("userRoles").get("role").in(criteria.getAppRoles()));
if (criteria.getLike() != null)

View File

@ -35,6 +35,9 @@ public class UserInfo implements DataEntity<UserInfo, UUID> {
@Column(name = "usertype", nullable = false)
private Short usertype; // 0 internal, 1 external
@Column(name = "userstatus", nullable = false)
private Short userStatus; // 0 active, 1 inactive
@Column(name = "verified_email", nullable = true)
private Boolean verified_email = null;
@ -187,6 +190,14 @@ public class UserInfo implements DataEntity<UserInfo, UUID> {
this.notifications = notifications;
}
public Short getUserStatus() {
return userStatus;
}
public void setUserStatus(Short userStatus) {
this.userStatus = userStatus;
}
@Override
public void update(UserInfo entity) {
this.name = entity.getName();
@ -194,6 +205,7 @@ public class UserInfo implements DataEntity<UserInfo, UUID> {
this.additionalinfo = entity.getAdditionalinfo();
this.lastloggedin = entity.getLastloggedin();
this.userRoles = entity.getUserRoles();
this.userStatus = entity.getUserStatus();
}
@Override

View File

@ -51,7 +51,7 @@ public class EmailMergeConfirmation {
public @ResponseBody
ResponseEntity sendConfirmatioEmail(@RequestBody UserMergeRequestModel requestModel, Principal principal) {
try {
this.emailConfirmationManager.sendConfirmationEmail(requestModel.getEmail(), principal, requestModel.getUserId());
this.emailConfirmationManager.sendConfirmationEmail(requestModel.getEmail(), principal, requestModel.getUserId(), requestModel.getProvider());
return ResponseEntity.status(HttpStatus.OK).body(new ResponseItem().status(ApiMessageCode.SUCCESS_MESSAGE));
} catch (Exception ex) {
if (ex instanceof HasConfirmedEmailException) {

View File

@ -40,6 +40,8 @@ public class UserInfoBuilder extends Builder<UserInfo> {
private Set<UserRole> userRoles = new HashSet<>();
private Short userStatus;
public UserInfoBuilder id(UUID id) {
this.id = id;
return this;
@ -100,6 +102,11 @@ public class UserInfoBuilder extends Builder<UserInfo> {
return this;
}
public UserInfoBuilder userStatus(Short userStatus) {
this.userStatus = userStatus;
return this;
}
@Override
public UserInfo build() {
UserInfo userInfo = new UserInfo();
@ -115,6 +122,7 @@ public class UserInfoBuilder extends Builder<UserInfo> {
userInfo.setCredentials(credentials);
userInfo.setDmps(dmps);
userInfo.setVerified_email(verified_email);
userInfo.setUserStatus(userStatus);
return userInfo;
}
}

View File

@ -22,10 +22,9 @@ import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import javax.transaction.Transactional;
import java.io.IOException;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import java.util.*;
@Component
public class MergeEmailConfirmationManager {
@ -39,6 +38,7 @@ public class MergeEmailConfirmationManager {
this.databaseRepository = apiContext.getOperationsContext().getDatabaseRepository();
}
@Transactional
public void confirmEmail(String token) throws TokenExpiredException, HasConfirmedEmailException {
EmailConfirmation loginConfirmationEmail = apiContext.getOperationsContext()
.getDatabaseRepository().getLoginConfirmationEmailDao().asQueryable()
@ -51,13 +51,14 @@ public class MergeEmailConfirmationManager {
.where((builder, root) -> builder.equal(root.get("id"), loginConfirmationEmail.getUserId())).getSingle();
try {
UUID otherUserId = new ObjectMapper().readValue(loginConfirmationEmail.getData(), UUID.class);
Map<String, Object> map = new ObjectMapper().readValue(loginConfirmationEmail.getData(), HashMap.class);
UUID otherUserId = UUID.fromString((String) map.get("userId"));
UserInfo user = databaseRepository.getUserInfoDao().asQueryable()
.where((builder, root) -> builder.equal(root.get("id"), loginConfirmationEmail.getUserId())).getSingle();
.where((builder, root) -> builder.equal(root.get("id"), otherUserId)).getSingle();
// Checks if mail is used by another user. If it is, merges the new the old.
mergeNewUserToOld(user, userToBeMerged);
expireUserToken(user);
mergeNewUserToOld(user, userToBeMerged, Integer.valueOf((String) map.get("provider")));
expireUserToken(userToBeMerged);
loginConfirmationEmail.setIsConfirmed(true);
databaseRepository.getLoginConfirmationEmailDao().createOrUpdate(loginConfirmationEmail);
} catch (Exception e) {
@ -65,7 +66,7 @@ public class MergeEmailConfirmationManager {
}
}
public void sendConfirmationEmail(String email, Principal principal, UUID userId) throws HasConfirmedEmailException {
public void sendConfirmationEmail(String email, Principal principal, UUID userId, Integer provider) throws HasConfirmedEmailException {
UserInfo user = apiContext.getOperationsContext().getDatabaseRepository().getUserInfoDao().find(principal.getId());
apiContext.getUtilitiesService().getConfirmationEmailService().createMergeConfirmationEmail(
@ -73,12 +74,14 @@ public class MergeEmailConfirmationManager {
apiContext.getUtilitiesService().getMailService(),
email,
userId,
principal
principal,
provider
);
}
private void mergeNewUserToOld(UserInfo newUser, UserInfo oldUser) {
Credential credential = databaseRepository.getCredentialDao().asQueryable().where((builder, root) -> builder.equal(root.get("userInfo"), oldUser)).getSingle();
@Transactional
private void mergeNewUserToOld(UserInfo newUser, UserInfo oldUser, Integer provider) {
Credential credential = databaseRepository.getCredentialDao().asQueryable().where((builder, root) -> builder.and(builder.equal(root.get("userInfo"), oldUser), builder.equal(root.get("provider"), provider))).getSingle();
credential.setUserInfo(newUser);
databaseRepository.getCredentialDao().createOrUpdate(credential);
List<UserDMP> userDmps = databaseRepository.getUserDmpDao().asQueryable().where((builder, root) -> builder.equal(root.get("user"), oldUser)).toList();
@ -86,14 +89,15 @@ public class MergeEmailConfirmationManager {
userDmp.setUser(newUser);
databaseRepository.getUserDmpDao().createOrUpdate(userDmp);
});
oldUser.setUserStatus((short)1);
databaseRepository.getUserInfoDao().createOrUpdate(oldUser);
}
private void expireUserToken(UserInfo user) {
UserToken userToken = databaseRepository.getUserTokenDao().asQueryable()
.where((builder, root) -> builder.equal(root.get("user"), user))
.orderBy((builder, root) -> builder.desc(root.get("issuedAt")))
.take(1)
.getSingle();
.take(1).toList().get(0);
userToken.setExpiresAt(new Date());
databaseRepository.getUserTokenDao().createOrUpdate(userToken);
}

View File

@ -54,7 +54,7 @@ public abstract class AbstractAuthenticationService implements AuthenticationSer
UserInfo userInfo = this.apiContext.getOperationsContext().getBuilderFactory().getBuilder(UserInfoBuilder.class)
.name(username).email(environment.getProperty("autouser.root.email")).created(new Date())
.lastloggedin(new Date()).authorization_level((short) 1).usertype((short) 1)
.lastloggedin(new Date()).authorization_level((short) 1).usertype((short) 1).userStatus((short)0)
.build();
userInfo = this.apiContext.getOperationsContext().getDatabaseRepository().getUserInfoDao().createOrUpdate(userInfo);
@ -111,14 +111,20 @@ public abstract class AbstractAuthenticationService implements AuthenticationSer
public Principal Touch(LoginProviderUser profile) throws NullEmailException {
UserInfo userInfo = apiContext.getOperationsContext().getDatabaseRepository().getUserInfoDao().asQueryable().withHint("userInfo").where((builder, root) -> builder.equal(root.get("email"), profile.getEmail())).getSingleOrDefault();
UserInfo userInfo;// = apiContext.getOperationsContext().getDatabaseRepository().getUserInfoDao().asQueryable().withHint("userInfo").where((builder, root) -> builder.and(builder.equal(root.get("email"), profile.getEmail()), builder.equal(root.get("userStatus"), 0))).getSingleOrDefault();
if (userInfo == null) {
//if (userInfo == null) {
Optional<Credential> optionalCredential = Optional.ofNullable(apiContext.getOperationsContext().getDatabaseRepository().getCredentialDao()
.asQueryable().withHint("credentialUserInfo")
.where((builder, root) -> builder.and(builder.equal(root.get("provider"), profile.getProvider().getValue()), builder.equal(root.get("externalId"), profile.getId())))
.getSingleOrDefault());
userInfo = optionalCredential.map(Credential::getUserInfo).orElse(null);
if (userInfo.getUserStatus() == 1) {
userInfo = null;
}
//}
if (userInfo == null) {
userInfo = apiContext.getOperationsContext().getDatabaseRepository().getUserInfoDao().asQueryable().withHint("userInfo").where((builder, root) -> builder.and(builder.equal(root.get("email"), profile.getEmail()), builder.equal(root.get("userStatus"), 0))).getSingleOrDefault();
}
final Credential credential = this.apiContext.getOperationsContext().getBuilderFactory().getBuilder(CredentialBuilder.class)
@ -140,7 +146,7 @@ public abstract class AbstractAuthenticationService implements AuthenticationSer
+ "\", \"expirationDate\": \"" + Instant.now().plusSeconds((profile.getZenodoExpire() != null ? profile.getZenodoExpire(): 0)).toEpochMilli()
+ "\", \"zenodoRefresh\": \"" + profile.getZenodoRefresh()
+ (profile.getProvider() == TokenValidatorFactoryImpl.LoginProvider.ZENODO ? "\", \"zenodoEmail\": \"" + profile.getEmail() : "") +"\"}}")
.authorization_level((short) 1).usertype((short) 1)
.authorization_level((short) 1).usertype((short) 1).userStatus((short)0)
.build();
userInfo = apiContext.getOperationsContext().getDatabaseRepository().getUserInfoDao().createOrUpdate(userInfo);

View File

@ -10,7 +10,7 @@ import java.util.concurrent.CompletableFuture;
public interface ConfirmationEmailService {
public void createConfirmationEmail(EmailConfirmationDao loginConfirmationEmailDao, MailService mailService, String email, UUID userId);
public void createMergeConfirmationEmail(EmailConfirmationDao loginConfirmationEmailDao, MailService mailService, String email, UUID userId, Principal principal);
public void createMergeConfirmationEmail(EmailConfirmationDao loginConfirmationEmailDao, MailService mailService, String email, UUID userId, Principal principal, Integer provider);
public CompletableFuture sentConfirmationEmail(EmailConfirmation confirmationEmail, MailService mailService);

View File

@ -12,6 +12,8 @@ import org.springframework.core.env.Environment;
import org.springframework.stereotype.Service;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
@ -102,7 +104,7 @@ public class ConfirmationEmailServiceImpl implements ConfirmationEmailService {
@Override
public void createMergeConfirmationEmail(EmailConfirmationDao loginConfirmationEmailDao, MailService mailService,
String email, UUID userId, Principal principal) {
String email, UUID userId, Principal principal, Integer provider) {
EmailConfirmation confirmationEmail = new EmailConfirmation();
confirmationEmail.setEmail(email);
confirmationEmail.setExpiresAt(Date
@ -113,7 +115,10 @@ public class ConfirmationEmailServiceImpl implements ConfirmationEmailService {
);
confirmationEmail.setUserId(userId);
try {
confirmationEmail.setData(new ObjectMapper().writeValueAsString(principal.getId()));
Map<String, Object> map = new HashMap<>();
map.put("userId", principal.getId());
map.put("provider", provider.toString());
confirmationEmail.setData(new ObjectMapper().writeValueAsString(map));
} catch (JsonProcessingException e) {
logger.error(e.getMessage(), e);
}

View File

@ -271,7 +271,7 @@
<table border="0" cellpadding="0" cellspacing="0">
<tbody>
<tr>
<td> <a href="{host}/confirmation/{confirmationToken}" target="_blank">Confirm Merge Request</a> </td>
<td> <a href="{host}/merge/confirmation/{confirmationToken}" target="_blank">Confirm Merge Request</a> </td>
</tr>
</tbody>
</table>

View File

@ -1,4 +1,4 @@
INSERT INTO public."UserInfo"(email, authorization_level, usertype, name, created, additionalinfo) VALUES ('fake@email.org', 1, 1, :'ADMIN_USERNAME', now(), '{}');
INSERT INTO public."UserInfo"(email, authorization_level, usertype, userstatus, name, created, additionalinfo) VALUES ('fake@email.org', 1, 1, 0, :'ADMIN_USERNAME', now(), '{}');
INSERT INTO public."Credential" VALUES (uuid_generate_v4(), 0, 5, :'ADMIN_USERNAME', :'ADMIN_PASSWORD', now(), now(), (SELECT public."UserInfo"."id" FROM public."UserInfo" WHERE name = :'ADMIN_USERNAME'), 'dmp');
@ -20,4 +20,4 @@ UPDATE public."DMP"
UPDATE public."DatasetProfile"
SET "Language"='en';
INSERT INTO public."DBVersion" VALUES ('DMPDB', '00.00.005', '2020-06-03 11:40:00.000000+03', now(), 'Remove user association table');
INSERT INTO public."DBVersion" VALUES ('DMPDB', '00.00.006', '2020-10-27 13:40:00.000000+03', now(), 'Add userstatus on UserInfo table');

View File

@ -825,6 +825,7 @@ CREATE TABLE public."UserInfo" (
email character varying(250),
authorization_level smallint NOT NULL,
usertype smallint NOT NULL,
userstatus smallint NOT NULL,
verified_email boolean,
name character varying(250),
created timestamp(6) with time zone,

View File

@ -0,0 +1,14 @@
DO $$DECLARE
this_version CONSTANT varchar := '00.00.007';
BEGIN
PERFORM * FROM "DBVersion" WHERE version = this_version;
IF FOUND THEN RETURN; END IF;
ALTER TABLE public."UserInfo" ADD COLUMN userstatus smallint;
UPDATE public."UserInfo" SET userstatus=0;
ALTER TABLE public."UserInfo" ALTER COLUMN userstatus SET NOT NULL;
INSERT INTO public."DBVersion" VALUES ('DMPDB', '00.00.006', '2020-10-27 13:40:00.000000+03', now(), 'Add userstatus on UserInfo table');
END$$;

View File

@ -1,4 +1,5 @@
export class UserMergeRequestModel {
userId: String;
email: String;
provider: number;
}

View File

@ -412,7 +412,7 @@ export class LoginComponent extends BaseComponent implements OnInit, AfterViewIn
this.authService.mergeLogin(loginInfo)
.pipe(takeUntil(this._destroyed))
.subscribe(
res => this.mergeLoginService.setRequest({email: res.payload.email, userId: res.payload.id})
res => this.mergeLoginService.setRequest({email: res.payload.email, userId: res.payload.id, provider: loginInfo.provider})
);
} else {
this.authService.login(loginInfo)

View File

@ -13,6 +13,7 @@ import { LoginService } from '@app/ui/auth/login/utilities/login.service';
import { Oauth2DialogModule } from '@app/ui/misc/oauth2-dialog/oauth2-dialog.module';
import { CommonFormsModule } from '@common/forms/common-forms.module';
import { CommonUiModule } from '@common/ui/common-ui.module';
import { MergeEmailConfirmation } from './merge-email-confirmation/merge-email-confirmation.component';
import { MergeLoginService } from './utilities/merge-login.service';
import { ZenodoLoginComponent } from './zenodo-login/zenodo-login.component';
@ -32,7 +33,8 @@ import { ZenodoLoginComponent } from './zenodo-login/zenodo-login.component';
EmailConfirmation,
OpenAireLoginComponent,
ConfigurableLoginComponent,
ZenodoLoginComponent
ZenodoLoginComponent,
MergeEmailConfirmation
],
exports: [
LoginComponent

View File

@ -10,6 +10,7 @@ import { OpenAireLoginComponent } from "./openaire-login/openaire-login.componen
import { ConfigurableLoginComponent } from "./configurable-login/configurable-login.component";
import { ZenodoLoginComponent } from './zenodo-login/zenodo-login.component';
import { Oauth2DialogComponent } from '@app/ui/misc/oauth2-dialog/oauth2-dialog.component';
import { MergeEmailConfirmation } from './merge-email-confirmation/merge-email-confirmation.component';
const routes: Routes = [
{ path: '', component: LoginComponent },
@ -18,6 +19,7 @@ const routes: Routes = [
{ path: 'external/orcid', component: Oauth2DialogComponent },
{ path: 'external/b2access', component: Oauth2DialogComponent },
{ path: 'confirmation/:token', component: EmailConfirmation },
{ path: 'merge/confirmation/:token', component: MergeEmailConfirmation },
{ path: 'confirmation', component: EmailConfirmation },
{ path: 'openaire', component: Oauth2DialogComponent},
{ path: 'configurable/:id', component: ConfigurableLoginComponent},

View File

@ -0,0 +1,61 @@
import { Component, OnInit } from "@angular/core";
import { FormControl } from '@angular/forms';
import { ActivatedRoute, Router } from "@angular/router";
import { EmailConfirmationService } from '@app/core/services/email-confirmation/email-confirmation.service';
import { MergeEmailConfirmationService } from '@app/core/services/merge-email-confirmation/merge-email-confirmation.service';
import { SnackBarNotificationLevel, UiNotificationService } from '@app/core/services/notification/ui-notification-service';
import { BaseComponent } from '@common/base/base.component';
import { TranslateService } from '@ngx-translate/core';
import { takeUntil } from "rxjs/operators";
@Component({
selector: 'app-email-confirmation-component',
templateUrl: './merge-email-confirmation.component.html'
})
export class MergeEmailConfirmation extends BaseComponent implements OnInit {
public emailFormControl = new FormControl('');
public showForm: boolean = false;
public mailSent: boolean = false;
constructor(
private emailConfirmationService: MergeEmailConfirmationService,
private route: ActivatedRoute,
private router: Router,
private language: TranslateService,
private uiNotificationService: UiNotificationService
) { super(); }
ngOnInit() {
this.route.params
.pipe(takeUntil(this._destroyed))
.subscribe(params => {
const token = params['token']
if (token != null) {
this.showForm = false;
this.emailConfirmationService.emailConfirmation(token)
.pipe(takeUntil(this._destroyed))
.subscribe(
result => this.onCallbackEmailConfirmationSuccess(),
error => this.onCallbackError(error)
)
} else {
this.showForm = true;
}
});
}
onCallbackEmailConfirmationSuccess() {
this.router.navigate(['home']);
}
onCallbackError(error: any) {
if (error.status === 302) {
this.uiNotificationService.snackBarNotification(this.language.instant('EMAIL-CONFIRMATION.EMAIL-FOUND'), SnackBarNotificationLevel.Warning);
this.router.navigate(['home']);
} else {
this.uiNotificationService.snackBarNotification(this.language.instant('EMAIL-CONFIRMATION.EXPIRED-EMAIL'), SnackBarNotificationLevel.Error);
this.router.navigate(['login']);
}
}
}