Add zenodo DOI access token expiration check up and show proper pop up when the user has either no or expired token
This commit is contained in:
parent
07da2278cb
commit
bdcbc5553b
|
@ -1,6 +1,8 @@
|
||||||
package eu.eudat.controllers;
|
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.exceptions.security.NullEmailException;
|
||||||
import eu.eudat.logic.managers.UserManager;
|
import eu.eudat.logic.managers.UserManager;
|
||||||
import eu.eudat.logic.proxy.config.configloaders.ConfigLoader;
|
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 org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import javax.transaction.Transactional;
|
import javax.transaction.Transactional;
|
||||||
|
import java.io.IOException;
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
|
|
||||||
|
|
||||||
|
@ -150,6 +153,16 @@ public class Login {
|
||||||
return ResponseEntity.status(HttpStatus.OK).body(new ResponseItem<Principal>().payload(this.nonVerifiedUserAuthenticationService.Touch(principal.getToken())).status(ApiMessageCode.NO_MESSAGE));
|
return ResponseEntity.status(HttpStatus.OK).body(new ResponseItem<Principal>().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<ResponseItem<Boolean>> hasDOIToken(Principal principal) throws NullEmailException {
|
||||||
|
try {
|
||||||
|
return ResponseEntity.status(HttpStatus.OK).body(new ResponseItem<Boolean>().payload(this.userManager.isDOITokenValid(principal)).status(ApiMessageCode.NO_MESSAGE));
|
||||||
|
} catch (NonValidTokenException | ExpiredTokenException | IOException e) {
|
||||||
|
return ResponseEntity.status(460).body(new ResponseItem<Boolean>().payload(false).status(ApiMessageCode.ERROR_MESSAGE).message(e.getMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
@RequestMapping(method = RequestMethod.POST, value = {"/logout"}, consumes = "application/json", produces = "application/json")
|
@RequestMapping(method = RequestMethod.POST, value = {"/logout"}, consumes = "application/json", produces = "application/json")
|
||||||
public @ResponseBody
|
public @ResponseBody
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
package eu.eudat.exceptions.security;
|
||||||
|
|
||||||
|
public class ExpiredTokenException extends Exception {
|
||||||
|
|
||||||
|
public ExpiredTokenException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,6 +7,8 @@ import eu.eudat.data.entities.DMP;
|
||||||
import eu.eudat.data.entities.UserInfo;
|
import eu.eudat.data.entities.UserInfo;
|
||||||
import eu.eudat.data.entities.UserRole;
|
import eu.eudat.data.entities.UserRole;
|
||||||
import eu.eudat.data.query.items.table.userinfo.UserInfoTableRequestItem;
|
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.NullEmailException;
|
||||||
import eu.eudat.exceptions.security.UnauthorisedException;
|
import eu.eudat.exceptions.security.UnauthorisedException;
|
||||||
import eu.eudat.logic.builders.entity.UserRoleBuilder;
|
import eu.eudat.logic.builders.entity.UserRoleBuilder;
|
||||||
|
@ -29,6 +31,7 @@ import org.w3c.dom.Document;
|
||||||
import org.w3c.dom.Element;
|
import org.w3c.dom.Element;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.time.Instant;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@ -112,4 +115,16 @@ public class UserManager {
|
||||||
dataTableData.setTotalCount((long) colaborators.size());
|
dataTableData.setTotalCount((long) colaborators.size());
|
||||||
return dataTableData;
|
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<String, Object> settings = Collections.singletonMap("zenodoToken", "");
|
||||||
|
this.updateSettings(settings, principal);
|
||||||
|
throw new ExpiredTokenException("Zenodo Token is expired");
|
||||||
|
}
|
||||||
|
throw new NonValidTokenException("This account has no Zenodo Token");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -159,4 +159,13 @@ export class AuthService extends BaseService {
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public hasDOIToken(): Observable<any> {
|
||||||
|
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 });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,7 @@ import { DmpWizardDatasetListingComponent } from '@app/ui/dmp/wizard/listing/dmp
|
||||||
import { CommonFormsModule } from '@common/forms/common-forms.module';
|
import { CommonFormsModule } from '@common/forms/common-forms.module';
|
||||||
import { FormValidationErrorsDialogModule } from '@common/forms/form-validation-errors-dialog/form-validation-errors-dialog.module';
|
import { FormValidationErrorsDialogModule } from '@common/forms/form-validation-errors-dialog/form-validation-errors-dialog.module';
|
||||||
import { CommonUiModule } from '@common/ui/common-ui.module';
|
import { CommonUiModule } from '@common/ui/common-ui.module';
|
||||||
|
import { MultipleChoiceDialogModule } from '@common/modules/multiple-choice-dialog/multiple-choice-dialog.module';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
|
@ -42,7 +43,8 @@ import { CommonUiModule } from '@common/ui/common-ui.module';
|
||||||
AutoCompleteModule,
|
AutoCompleteModule,
|
||||||
DmpRoutingModule,
|
DmpRoutingModule,
|
||||||
DmpOverviewModule,
|
DmpOverviewModule,
|
||||||
FormValidationErrorsDialogModule
|
FormValidationErrorsDialogModule,
|
||||||
|
MultipleChoiceDialogModule
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
DmpListingComponent,
|
DmpListingComponent,
|
||||||
|
|
|
@ -22,6 +22,8 @@ import { Observable, of as observableOf } from 'rxjs';
|
||||||
import { takeUntil } from 'rxjs/operators';
|
import { takeUntil } from 'rxjs/operators';
|
||||||
import { Role } from "@app/core/common/enum/role";
|
import { Role } from "@app/core/common/enum/role";
|
||||||
import { DmpInvitationDialogComponent } from '../invitation/dmp-invitation.component';
|
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({
|
@Component({
|
||||||
selector: 'app-dmp-overview',
|
selector: 'app-dmp-overview',
|
||||||
|
@ -322,6 +324,14 @@ export class DmpOverviewComponent extends BaseComponent implements OnInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
getDoi(dmp: DmpOverviewModel) {
|
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, {
|
const dialogRef = this.dialog.open(ConfirmationDialogComponent, {
|
||||||
maxWidth: '600px',
|
maxWidth: '600px',
|
||||||
restoreFocus: false,
|
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 {
|
onDOICallbackSuccess(): void {
|
||||||
this.uiNotificationService.snackBarNotification(this.language.instant('DMP-EDITOR.SNACK-BAR.SUCCESSFUL-DOI'), SnackBarNotificationLevel.Success);
|
this.uiNotificationService.snackBarNotification(this.language.instant('DMP-EDITOR.SNACK-BAR.SUCCESSFUL-DOI'), SnackBarNotificationLevel.Success);
|
||||||
}
|
}
|
||||||
|
|
|
@ -525,6 +525,10 @@
|
||||||
"ERROR": {
|
"ERROR": {
|
||||||
"DELETED-DMP": "The requested DMP is deleted",
|
"DELETED-DMP": "The requested DMP is deleted",
|
||||||
"FORBIDEN-DMP": "You are not allowed to access this DMP"
|
"FORBIDEN-DMP": "You are not allowed to access this DMP"
|
||||||
|
},
|
||||||
|
"MULTIPLE-DIALOG": {
|
||||||
|
"ZENODO-LOGIN": "Login with Zenodo",
|
||||||
|
"USE-DEFAULT": "Use Default Token"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"DATASET-LISTING": {
|
"DATASET-LISTING": {
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
<div class="confirmation-dialog">
|
||||||
|
<div *ngIf="data.icon" class="row d-flex flex-row">
|
||||||
|
<div class="col-auto close-btn justify-content-start">
|
||||||
|
<mat-icon color="warn">{{ data.icon }}</mat-icon>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="data.warning" class="col justify-content-center warn-text">{{ data.warning }}</div>
|
||||||
|
<div class="col-auto close-btn justify-content-end" (click)="close()">
|
||||||
|
<mat-icon>close</mat-icon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row d-flex flex-row">
|
||||||
|
<div class="col message pb-4 pl-3">{{ data.message }}</div>
|
||||||
|
<div *ngIf="!data.icon" class="col-auto close-btn justify-content-end" (click)="close()">
|
||||||
|
<mat-icon>close</mat-icon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="data.privacyPolicyNames" class="row d-flex flex-col">
|
||||||
|
<div class="col-12 privacy-policy-names">{{ data.privacyPolicyNames }}</div>
|
||||||
|
<mat-checkbox [(ngModel)]="agreePrivacyPolicyNames" required class="checkbox-privacy">
|
||||||
|
{{'GENERAL.CONFIRMATION-DIALOG.ACTIONS.POLICY-AGREE' | translate}}
|
||||||
|
</mat-checkbox>
|
||||||
|
<div *ngIf="!agreePrivacyPolicyNames" class="required-policy">{{'GENERAL.CONFIRMATION-DIALOG.ACTIONS.REQUIRED' | translate}}</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col"></div>
|
||||||
|
<span *ngFor="let title of data.titles; let i = index">
|
||||||
|
<div class="col-auto"><button mat-raised-button type="button" (click)="apply(i)" class="confirm">{{ title }}</button></div>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<MultipleChoiceDialogComponent>,
|
||||||
|
@Inject(MAT_DIALOG_DATA) public data: any
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
close() {
|
||||||
|
this.dialogRef.close(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
apply(i: number) {
|
||||||
|
this.dialogRef.close(i);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 { }
|
Loading…
Reference in New Issue