From 7b5588bcbe1dd700833c04f660f245ff6b602f7d Mon Sep 17 00:00:00 2001 From: George Kalampokis Date: Mon, 6 Apr 2020 18:16:05 +0300 Subject: [PATCH] Add zenodo DOI access token expiration check up and show proper pop up when the user has either no or expired token --- .../main/java/eu/eudat/controllers/Login.java | 13 ++++++ .../security/ExpiredTokenException.java | 8 ++++ .../eu/eudat/logic/managers/UserManager.java | 15 +++++++ .../app/core/services/auth/auth.service.ts | 9 ++++ dmp-frontend/src/app/ui/dmp/dmp.module.ts | 4 +- .../ui/dmp/overview/dmp-overview.component.ts | 32 ++++++++++++++ dmp-frontend/src/assets/i18n/en.json | 4 ++ .../multiple-choice-dialog.component.html | 30 +++++++++++++ .../multiple-choice-dialog.component.scss | 44 +++++++++++++++++++ .../multiple-choice-dialog.component.ts | 25 +++++++++++ .../multiple-choice-dialog.module.ts | 15 +++++++ 11 files changed, 198 insertions(+), 1 deletion(-) create mode 100644 dmp-backend/web/src/main/java/eu/eudat/exceptions/security/ExpiredTokenException.java create mode 100644 dmp-frontend/src/common/modules/multiple-choice-dialog/multiple-choice-dialog.component.html create mode 100644 dmp-frontend/src/common/modules/multiple-choice-dialog/multiple-choice-dialog.component.scss create mode 100644 dmp-frontend/src/common/modules/multiple-choice-dialog/multiple-choice-dialog.component.ts create mode 100644 dmp-frontend/src/common/modules/multiple-choice-dialog/multiple-choice-dialog.module.ts diff --git a/dmp-backend/web/src/main/java/eu/eudat/controllers/Login.java b/dmp-backend/web/src/main/java/eu/eudat/controllers/Login.java index 16af6f14f..45636e7bb 100644 --- a/dmp-backend/web/src/main/java/eu/eudat/controllers/Login.java +++ b/dmp-backend/web/src/main/java/eu/eudat/controllers/Login.java @@ -1,6 +1,8 @@ package eu.eudat.controllers; +import eu.eudat.exceptions.security.ExpiredTokenException; +import eu.eudat.exceptions.security.NonValidTokenException; import eu.eudat.exceptions.security.NullEmailException; import eu.eudat.logic.managers.UserManager; import eu.eudat.logic.proxy.config.configloaders.ConfigLoader; @@ -40,6 +42,7 @@ import org.springframework.social.oauth1.OAuthToken; import org.springframework.web.bind.annotation.*; import javax.transaction.Transactional; +import java.io.IOException; import java.security.GeneralSecurityException; @@ -150,6 +153,16 @@ public class Login { return ResponseEntity.status(HttpStatus.OK).body(new ResponseItem().payload(this.nonVerifiedUserAuthenticationService.Touch(principal.getToken())).status(ApiMessageCode.NO_MESSAGE)); } + @RequestMapping(method = RequestMethod.GET, value = {"/hasDOIToken"}, consumes = "application/json", produces = "application/json") + public @ResponseBody + ResponseEntity> hasDOIToken(Principal principal) throws NullEmailException { + try { + return ResponseEntity.status(HttpStatus.OK).body(new ResponseItem().payload(this.userManager.isDOITokenValid(principal)).status(ApiMessageCode.NO_MESSAGE)); + } catch (NonValidTokenException | ExpiredTokenException | IOException e) { + return ResponseEntity.status(460).body(new ResponseItem().payload(false).status(ApiMessageCode.ERROR_MESSAGE).message(e.getMessage())); + } + } + @Transactional @RequestMapping(method = RequestMethod.POST, value = {"/logout"}, consumes = "application/json", produces = "application/json") public @ResponseBody diff --git a/dmp-backend/web/src/main/java/eu/eudat/exceptions/security/ExpiredTokenException.java b/dmp-backend/web/src/main/java/eu/eudat/exceptions/security/ExpiredTokenException.java new file mode 100644 index 000000000..dd1de35ea --- /dev/null +++ b/dmp-backend/web/src/main/java/eu/eudat/exceptions/security/ExpiredTokenException.java @@ -0,0 +1,8 @@ +package eu.eudat.exceptions.security; + +public class ExpiredTokenException extends Exception { + + public ExpiredTokenException(String message) { + super(message); + } +} diff --git a/dmp-backend/web/src/main/java/eu/eudat/logic/managers/UserManager.java b/dmp-backend/web/src/main/java/eu/eudat/logic/managers/UserManager.java index 084648523..d2388eec4 100644 --- a/dmp-backend/web/src/main/java/eu/eudat/logic/managers/UserManager.java +++ b/dmp-backend/web/src/main/java/eu/eudat/logic/managers/UserManager.java @@ -7,6 +7,8 @@ import eu.eudat.data.entities.DMP; import eu.eudat.data.entities.UserInfo; import eu.eudat.data.entities.UserRole; import eu.eudat.data.query.items.table.userinfo.UserInfoTableRequestItem; +import eu.eudat.exceptions.security.ExpiredTokenException; +import eu.eudat.exceptions.security.NonValidTokenException; import eu.eudat.exceptions.security.NullEmailException; import eu.eudat.exceptions.security.UnauthorisedException; import eu.eudat.logic.builders.entity.UserRoleBuilder; @@ -29,6 +31,7 @@ import org.w3c.dom.Document; import org.w3c.dom.Element; import java.io.IOException; +import java.time.Instant; import java.util.*; import java.util.stream.Collectors; @@ -112,4 +115,16 @@ public class UserManager { dataTableData.setTotalCount((long) colaborators.size()); return dataTableData; } + + public Boolean isDOITokenValid(Principal principal) throws NonValidTokenException, ExpiredTokenException, IOException { + if (principal.getZenodoToken() != null && !principal.getZenodoToken().isEmpty()) { + if (Instant.now().isBefore(principal.getZenodoDuration())) { + return true; + } + Map settings = Collections.singletonMap("zenodoToken", ""); + this.updateSettings(settings, principal); + throw new ExpiredTokenException("Zenodo Token is expired"); + } + throw new NonValidTokenException("This account has no Zenodo Token"); + } } diff --git a/dmp-frontend/src/app/core/services/auth/auth.service.ts b/dmp-frontend/src/app/core/services/auth/auth.service.ts index a50261ba0..2b9fce248 100644 --- a/dmp-frontend/src/app/core/services/auth/auth.service.ts +++ b/dmp-frontend/src/app/core/services/auth/auth.service.ts @@ -159,4 +159,13 @@ export class AuthService extends BaseService { }) ); } + + public hasDOIToken(): Observable { + this.actionUrl = this.configurationService.server + 'auth/'; + const url = this.actionUrl + '/hasDOIToken'; + const principal = this.current(); + let headers = this.headers; + headers = headers.set('AuthToken', principal.token); + return this.http.get(url, { headers: headers }); + } } diff --git a/dmp-frontend/src/app/ui/dmp/dmp.module.ts b/dmp-frontend/src/app/ui/dmp/dmp.module.ts index c4732a882..0f72ed78f 100644 --- a/dmp-frontend/src/app/ui/dmp/dmp.module.ts +++ b/dmp-frontend/src/app/ui/dmp/dmp.module.ts @@ -30,6 +30,7 @@ import { DmpWizardDatasetListingComponent } from '@app/ui/dmp/wizard/listing/dmp import { CommonFormsModule } from '@common/forms/common-forms.module'; import { FormValidationErrorsDialogModule } from '@common/forms/form-validation-errors-dialog/form-validation-errors-dialog.module'; import { CommonUiModule } from '@common/ui/common-ui.module'; +import { MultipleChoiceDialogModule } from '@common/modules/multiple-choice-dialog/multiple-choice-dialog.module'; @NgModule({ imports: [ @@ -42,7 +43,8 @@ import { CommonUiModule } from '@common/ui/common-ui.module'; AutoCompleteModule, DmpRoutingModule, DmpOverviewModule, - FormValidationErrorsDialogModule + FormValidationErrorsDialogModule, + MultipleChoiceDialogModule ], declarations: [ DmpListingComponent, diff --git a/dmp-frontend/src/app/ui/dmp/overview/dmp-overview.component.ts b/dmp-frontend/src/app/ui/dmp/overview/dmp-overview.component.ts index 98c25aac9..fe787bbe0 100644 --- a/dmp-frontend/src/app/ui/dmp/overview/dmp-overview.component.ts +++ b/dmp-frontend/src/app/ui/dmp/overview/dmp-overview.component.ts @@ -22,6 +22,8 @@ import { Observable, of as observableOf } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; import { Role } from "@app/core/common/enum/role"; import { DmpInvitationDialogComponent } from '../invitation/dmp-invitation.component'; +import { MultipleChoiceDialogModule } from '@common/modules/multiple-choice-dialog/multiple-choice-dialog.module'; +import { MultipleChoiceDialogComponent } from '@common/modules/multiple-choice-dialog/multiple-choice-dialog.component'; @Component({ selector: 'app-dmp-overview', @@ -322,6 +324,14 @@ export class DmpOverviewComponent extends BaseComponent implements OnInit { } getDoi(dmp: DmpOverviewModel) { + this.authentication.hasDOIToken().subscribe(response => { + this.showConfirmationDOIDialog(dmp); + }, error => { + this.showErrorConfirmationDOIDialog(error.error.message, dmp); + }); + } + + showConfirmationDOIDialog(dmp: DmpOverviewModel) { const dialogRef = this.dialog.open(ConfirmationDialogComponent, { maxWidth: '600px', restoreFocus: false, @@ -346,6 +356,28 @@ export class DmpOverviewComponent extends BaseComponent implements OnInit { }); } + showErrorConfirmationDOIDialog(message: string, dmp: DmpOverviewModel) { + const dialogRef = this.dialog.open(MultipleChoiceDialogComponent, { + maxWidth: '600px', + restoreFocus: false, + data: { + message: message, + titles: [ this.language.instant('DMP-OVERVIEW.MULTIPLE-DIALOG.ZENODO-LOGIN'), this.language.instant('DMP-OVERVIEW.MULTIPLE-DIALOG.USE-DEFAULT')] + } + }); + dialogRef.afterClosed().pipe(takeUntil(this._destroyed)).subscribe(result => { + switch (result) { + case 0: + this.authentication.logout(); + this.router.navigate(['/login/external/zenodo']); + break; + case 1: + this.showConfirmationDOIDialog(dmp); + break; + } + }); + } + onDOICallbackSuccess(): void { this.uiNotificationService.snackBarNotification(this.language.instant('DMP-EDITOR.SNACK-BAR.SUCCESSFUL-DOI'), SnackBarNotificationLevel.Success); } diff --git a/dmp-frontend/src/assets/i18n/en.json b/dmp-frontend/src/assets/i18n/en.json index d1db35ce5..8d01e4426 100644 --- a/dmp-frontend/src/assets/i18n/en.json +++ b/dmp-frontend/src/assets/i18n/en.json @@ -525,6 +525,10 @@ "ERROR": { "DELETED-DMP": "The requested DMP is deleted", "FORBIDEN-DMP": "You are not allowed to access this DMP" + }, + "MULTIPLE-DIALOG": { + "ZENODO-LOGIN": "Login with Zenodo", + "USE-DEFAULT": "Use Default Token" } }, "DATASET-LISTING": { diff --git a/dmp-frontend/src/common/modules/multiple-choice-dialog/multiple-choice-dialog.component.html b/dmp-frontend/src/common/modules/multiple-choice-dialog/multiple-choice-dialog.component.html new file mode 100644 index 000000000..87029a992 --- /dev/null +++ b/dmp-frontend/src/common/modules/multiple-choice-dialog/multiple-choice-dialog.component.html @@ -0,0 +1,30 @@ +
+
+
+ {{ data.icon }} +
+
{{ data.warning }}
+
+ close +
+
+
+
{{ data.message }}
+
+ close +
+
+
+
{{ data.privacyPolicyNames }}
+ + {{'GENERAL.CONFIRMATION-DIALOG.ACTIONS.POLICY-AGREE' | translate}} + +
{{'GENERAL.CONFIRMATION-DIALOG.ACTIONS.REQUIRED' | translate}}
+
+
+
+ +
+
+
+
diff --git a/dmp-frontend/src/common/modules/multiple-choice-dialog/multiple-choice-dialog.component.scss b/dmp-frontend/src/common/modules/multiple-choice-dialog/multiple-choice-dialog.component.scss new file mode 100644 index 000000000..1e529f1f5 --- /dev/null +++ b/dmp-frontend/src/common/modules/multiple-choice-dialog/multiple-choice-dialog.component.scss @@ -0,0 +1,44 @@ +.confirmation-dialog { + .confirmation { + padding-bottom: 20px; + } + + .privacy-policy-names { + font-weight: 700; + padding: 1em; + } + + .close-btn { + margin-left: auto; + cursor: pointer; + } + + .warn-text { + color: #f44336; + } + + .cancel { + background-color: #aaaaaa; + color: #ffffff; + } + + .confirm { + background-color: #2cba6c; + color: #ffffff; + } + + .delete { + background-color: #ba2c2c; + color: #ffffff; + } + + .checkbox-privacy { + padding: 0em 1em; + } + + .required-policy { + padding: 0em 1.2em 1em; + font-size: smaller; + color: #f44336; + } + } diff --git a/dmp-frontend/src/common/modules/multiple-choice-dialog/multiple-choice-dialog.component.ts b/dmp-frontend/src/common/modules/multiple-choice-dialog/multiple-choice-dialog.component.ts new file mode 100644 index 000000000..8abce91d7 --- /dev/null +++ b/dmp-frontend/src/common/modules/multiple-choice-dialog/multiple-choice-dialog.component.ts @@ -0,0 +1,25 @@ +import { Component, Inject } from '@angular/core'; +import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material'; + +@Component({ + selector: 'app-multiple-choice-dialog', + templateUrl: './multiple-choice-dialog.component.html', + styleUrls: ['./multiple-choice-dialog.component.scss'] +}) +export class MultipleChoiceDialogComponent { + agreePrivacyPolicyNames = false; + + constructor( + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: any + ) { + } + + close() { + this.dialogRef.close(false); + } + + apply(i: number) { + this.dialogRef.close(i); + } +} diff --git a/dmp-frontend/src/common/modules/multiple-choice-dialog/multiple-choice-dialog.module.ts b/dmp-frontend/src/common/modules/multiple-choice-dialog/multiple-choice-dialog.module.ts new file mode 100644 index 000000000..5f435c90b --- /dev/null +++ b/dmp-frontend/src/common/modules/multiple-choice-dialog/multiple-choice-dialog.module.ts @@ -0,0 +1,15 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { MultipleChoiceDialogComponent } from './multiple-choice-dialog.component'; +import { CommonUiModule } from '@common/ui/common-ui.module'; +import { FormsModule } from '@angular/forms'; + + + +@NgModule({ + imports: [CommonUiModule, FormsModule], + declarations: [MultipleChoiceDialogComponent], + exports: [MultipleChoiceDialogComponent], + entryComponents: [MultipleChoiceDialogComponent] +}) +export class MultipleChoiceDialogModule { }