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;
|
||||
|
||||
|
||||
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<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
|
||||
@RequestMapping(method = RequestMethod.POST, value = {"/logout"}, consumes = "application/json", produces = "application/json")
|
||||
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.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<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 { 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,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -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