From a63ae3045e2210bbe0b07cce9a23eb2b428fb37b Mon Sep 17 00:00:00 2001 From: George Kalampokis Date: Wed, 8 Apr 2020 16:36:06 +0300 Subject: [PATCH] When selecting "login with zenodo" it will prompt and login with zenodo, and the access token will be registered to the current user instead of re-login you in with the zenodo account. Also disable Zenodo user login --- .../main/java/eu/eudat/controllers/Login.java | 10 ----- .../main/java/eu/eudat/controllers/Users.java | 22 ++++++++++ .../eu/eudat/logic/managers/UserManager.java | 21 +++++++++- .../eu/eudat/models/data/doi/DOIRequest.java | 25 +++++++++++ dmp-frontend/src/app/app-routing.module.ts | 5 ++- dmp-frontend/src/app/app.module.ts | 4 +- .../app/core/services/auth/auth.service.ts | 9 ---- .../app/core/services/user/user.service.ts | 10 +++++ .../ui/dmp/overview/dmp-overview.component.ts | 38 +++++++++++++++-- .../oauth2-dialog.component.html | 1 + .../oauth2-dialog.component.scss | 0 .../oauth2-dialog/oauth2-dialog.component.ts | 41 +++++++++++++++++++ .../oauth2-dialog/oauth2-dialog.module.ts | 17 ++++++++ .../service/oauth2-dialog.service.ts | 34 +++++++++++++++ dmp-frontend/src/assets/config/config.json | 2 +- 15 files changed, 212 insertions(+), 27 deletions(-) create mode 100644 dmp-backend/web/src/main/java/eu/eudat/models/data/doi/DOIRequest.java create mode 100644 dmp-frontend/src/app/ui/misc/oauth2-dialog/oauth2-dialog.component.html create mode 100644 dmp-frontend/src/app/ui/misc/oauth2-dialog/oauth2-dialog.component.scss create mode 100644 dmp-frontend/src/app/ui/misc/oauth2-dialog/oauth2-dialog.component.ts create mode 100644 dmp-frontend/src/app/ui/misc/oauth2-dialog/oauth2-dialog.module.ts create mode 100644 dmp-frontend/src/app/ui/misc/oauth2-dialog/service/oauth2-dialog.service.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 45636e7bb..d0591ded6 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 @@ -153,16 +153,6 @@ 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/controllers/Users.java b/dmp-backend/web/src/main/java/eu/eudat/controllers/Users.java index 5d7f48319..6f2205075 100644 --- a/dmp-backend/web/src/main/java/eu/eudat/controllers/Users.java +++ b/dmp-backend/web/src/main/java/eu/eudat/controllers/Users.java @@ -1,9 +1,13 @@ package eu.eudat.controllers; 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.logic.managers.UserManager; import eu.eudat.logic.security.claims.ClaimedAuthorities; import eu.eudat.logic.services.ApiContext; +import eu.eudat.models.data.doi.DOIRequest; import eu.eudat.models.data.helpers.common.DataTableData; import eu.eudat.models.data.helpers.responses.ResponseItem; import eu.eudat.models.data.security.Principal; @@ -73,6 +77,24 @@ public class Users extends BaseController { DataTableData dataTable = userManager.getCollaboratorsPaged(userInfoTableRequestItem, principal); return ResponseEntity.status(HttpStatus.OK).body(new ResponseItem>().payload(dataTable).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 = {"/registerDOIToken"}, consumes = "application/json", produces = "application/json") + public @ResponseBody + ResponseEntity> registerDOIToken(@RequestBody DOIRequest doiRequest, Principal principal) throws NullEmailException, IOException { + userManager.registerDOIToken(doiRequest, principal); + return ResponseEntity.status(HttpStatus.OK).body(new ResponseItem().status(ApiMessageCode.NO_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 d2388eec4..064580a7b 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 @@ -13,11 +13,14 @@ import eu.eudat.exceptions.security.NullEmailException; import eu.eudat.exceptions.security.UnauthorisedException; import eu.eudat.logic.builders.entity.UserRoleBuilder; import eu.eudat.logic.builders.model.models.DataTableDataBuilder; +import eu.eudat.logic.security.customproviders.Zenodo.ZenodoCustomProvider; +import eu.eudat.logic.security.validators.zenodo.helpers.ZenodoResponseToken; import eu.eudat.logic.services.ApiContext; import eu.eudat.logic.services.operations.authentication.AuthenticationService; import eu.eudat.logic.utilities.builders.XmlBuilder; import eu.eudat.models.HintedModelFactory; import eu.eudat.models.data.dmp.DataManagementPlan; +import eu.eudat.models.data.doi.DOIRequest; import eu.eudat.models.data.helpers.common.DataTableData; import eu.eudat.models.data.login.Credentials; import eu.eudat.models.data.security.Principal; @@ -26,6 +29,7 @@ import eu.eudat.models.data.userinfo.UserProfile; import eu.eudat.queryable.QueryableList; import org.json.JSONObject; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.env.Environment; import org.springframework.stereotype.Component; import org.w3c.dom.Document; import org.w3c.dom.Element; @@ -39,10 +43,14 @@ import java.util.stream.Collectors; public class UserManager { private ApiContext apiContext; + private ZenodoCustomProvider zenodoCustomProvider; + private Environment environment; @Autowired - public UserManager(ApiContext apiContext) { + public UserManager(ApiContext apiContext, ZenodoCustomProvider zenodoCustomProvider, Environment environment) { this.apiContext = apiContext; + this.zenodoCustomProvider = zenodoCustomProvider; + this.environment = environment; } public eu.eudat.models.data.user.composite.DatasetProfile generateDatasetProfileModel(eu.eudat.data.entities.DatasetProfile profile) { @@ -127,4 +135,15 @@ public class UserManager { } throw new NonValidTokenException("This account has no Zenodo Token"); } + + public void registerDOIToken(DOIRequest doiRequest, Principal principal) throws IOException { + ZenodoResponseToken responseToken = this.zenodoCustomProvider.getAccessToken(doiRequest.getZenodoRequest().getCode() + , this.environment.getProperty("zenodo.login.client_id") + , this.environment.getProperty("zenodo.login.client_secret") + , doiRequest.getRedirectUri()); + Map settings = new HashMap<>(); + settings.put("zenodoToken", responseToken.getAccessToken()); + settings.put("expirationDate", Instant.now().plusSeconds(responseToken.getExpiresIn()).toEpochMilli()); + this.updateSettings(settings, principal); + } } diff --git a/dmp-backend/web/src/main/java/eu/eudat/models/data/doi/DOIRequest.java b/dmp-backend/web/src/main/java/eu/eudat/models/data/doi/DOIRequest.java new file mode 100644 index 000000000..8338a75ac --- /dev/null +++ b/dmp-backend/web/src/main/java/eu/eudat/models/data/doi/DOIRequest.java @@ -0,0 +1,25 @@ +package eu.eudat.models.data.doi; + +import eu.eudat.logic.security.validators.zenodo.helpers.ZenodoRequest; + +public class DOIRequest { + + private ZenodoRequest zenodoRequest; + private String redirectUri; + + public ZenodoRequest getZenodoRequest() { + return zenodoRequest; + } + + public void setZenodoRequest(ZenodoRequest zenodoRequest) { + this.zenodoRequest = zenodoRequest; + } + + public String getRedirectUri() { + return redirectUri; + } + + public void setRedirectUri(String redirectUri) { + this.redirectUri = redirectUri; + } +} diff --git a/dmp-frontend/src/app/app-routing.module.ts b/dmp-frontend/src/app/app-routing.module.ts index 5ac4279d8..3b1758d58 100644 --- a/dmp-frontend/src/app/app-routing.module.ts +++ b/dmp-frontend/src/app/app-routing.module.ts @@ -2,6 +2,8 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { ReloadHelperComponent } from '@app/ui/misc/reload-helper/reload-helper.component'; import { B2AccessLoginComponent } from './ui/auth/login/b2access/b2access-login.component'; +import { Oauth2DialogModule } from './ui/misc/oauth2-dialog/oauth2-dialog.module'; +import { Oauth2DialogComponent } from './ui/misc/oauth2-dialog/oauth2-dialog.component'; const appRoutes: Routes = [ { @@ -219,7 +221,8 @@ const appRoutes: Routes = [ data: { }, }, - { path: 'reload', component: ReloadHelperComponent } + { path: 'reload', component: ReloadHelperComponent }, + { path: 'oauth2', component: Oauth2DialogComponent } ]; @NgModule({ diff --git a/dmp-frontend/src/app/app.module.ts b/dmp-frontend/src/app/app.module.ts index fdb790e30..556d685a1 100644 --- a/dmp-frontend/src/app/app.module.ts +++ b/dmp-frontend/src/app/app.module.ts @@ -30,6 +30,7 @@ import { NgcCookieConsentConfig, NgcCookieConsentModule } from 'ngx-cookieconsen import { TranslateServerLoader } from './core/services/language/server.loader'; import { BaseHttpService } from './core/services/http/base-http.service'; import { ConfigurationService } from './core/services/configuration/configuration.service'; +import { Oauth2DialogModule } from './ui/misc/oauth2-dialog/oauth2-dialog.module'; // AoT requires an exported function for factories export function HttpLoaderFactory(http: HttpClient, appConfig: ConfigurationService) { @@ -97,7 +98,8 @@ const cookieConfig: NgcCookieConsentConfig = { DatasetCreateWizardModule, NavbarModule, SidebarModule, - NgcCookieConsentModule.forRoot(cookieConfig) + NgcCookieConsentModule.forRoot(cookieConfig), + Oauth2DialogModule ], declarations: [ AppComponent, 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 2b9fce248..a50261ba0 100644 --- a/dmp-frontend/src/app/core/services/auth/auth.service.ts +++ b/dmp-frontend/src/app/core/services/auth/auth.service.ts @@ -159,13 +159,4 @@ 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/core/services/user/user.service.ts b/dmp-frontend/src/app/core/services/user/user.service.ts index 21f6381b2..d3ea3f9cc 100644 --- a/dmp-frontend/src/app/core/services/user/user.service.ts +++ b/dmp-frontend/src/app/core/services/user/user.service.ts @@ -47,4 +47,14 @@ export class UserService { getCollaboratorsPaged(dataTableRequest: DataTableRequest): Observable> { return this.http.post>(this.actionUrl + 'getCollaboratorsPaged', JSON.stringify(dataTableRequest), { headers: this.headers }); } + + public hasDOIToken(): Observable { + const url = this.actionUrl + 'hasDOIToken'; + return this.http.get(url, { headers: this.headers }); + } + + public registerDOIToken(code: string, redirectUri: string): Observable { + const url = this.actionUrl + 'registerDOIToken'; + return this.http.post(url, {zenodoRequest: {code: code}, redirectUri: redirectUri}, { headers: this.headers }); + } } 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 fe787bbe0..625f5994a 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 @@ -24,6 +24,11 @@ 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'; +import { ConfigurationService } from '@app/core/services/configuration/configuration.service'; +import { Oauth2DialogComponent } from '@app/ui/misc/oauth2-dialog/oauth2-dialog.component'; +import { Oauth2DialogService } from '@app/ui/misc/oauth2-dialog/service/oauth2-dialog.service'; +import { isNullOrUndefined } from 'util'; +import { UserService } from '@app/core/services/user/user.service'; @Component({ selector: 'app-dmp-overview', @@ -49,7 +54,10 @@ export class DmpOverviewComponent extends BaseComponent implements OnInit { private authentication: AuthService, private dialog: MatDialog, private language: TranslateService, - private uiNotificationService: UiNotificationService + private uiNotificationService: UiNotificationService, + private configurationService: ConfigurationService, + private oauth2DialogService: Oauth2DialogService, + private userService: UserService ) { super(); } @@ -323,8 +331,17 @@ export class DmpOverviewComponent extends BaseComponent implements OnInit { return dmp.doi == null ? true : false; } + getAccessUrl(): string { + const redirectUri = this.configurationService.app + 'oauth2'; + const url = this.configurationService.loginProviders.zenodoConfiguration.oauthUrl + + '?client_id=' + this.configurationService.loginProviders.zenodoConfiguration.clientId + + '&response_type=code&scope=deposit:write+deposit:actions&state=astate&redirect_uri=' + + redirectUri; + return url; + } + getDoi(dmp: DmpOverviewModel) { - this.authentication.hasDOIToken().subscribe(response => { + this.userService.hasDOIToken().subscribe(response => { this.showConfirmationDOIDialog(dmp); }, error => { this.showErrorConfirmationDOIDialog(error.error.message, dmp); @@ -368,8 +385,9 @@ export class DmpOverviewComponent extends BaseComponent implements OnInit { dialogRef.afterClosed().pipe(takeUntil(this._destroyed)).subscribe(result => { switch (result) { case 0: - this.authentication.logout(); - this.router.navigate(['/login/external/zenodo']); + // this.authentication.logout(); + // this.router.navigate(['/login/external/zenodo']); + this.showOauth2Dialog(this.getAccessUrl(), dmp); break; case 1: this.showConfirmationDOIDialog(dmp); @@ -378,6 +396,18 @@ export class DmpOverviewComponent extends BaseComponent implements OnInit { }); } + showOauth2Dialog(url: string, dmp: DmpOverviewModel) { + this.oauth2DialogService.login(url) + .pipe(takeUntil(this._destroyed)) + .subscribe(code => { + if (!isNullOrUndefined(code)) { + this.userService.registerDOIToken(code, this.configurationService.app + 'oauth2') + .pipe(takeUntil(this._destroyed)) + .subscribe(() => this.showConfirmationDOIDialog(dmp)); + } + }); + } + onDOICallbackSuccess(): void { this.uiNotificationService.snackBarNotification(this.language.instant('DMP-EDITOR.SNACK-BAR.SUCCESSFUL-DOI'), SnackBarNotificationLevel.Success); } diff --git a/dmp-frontend/src/app/ui/misc/oauth2-dialog/oauth2-dialog.component.html b/dmp-frontend/src/app/ui/misc/oauth2-dialog/oauth2-dialog.component.html new file mode 100644 index 000000000..a9478084f --- /dev/null +++ b/dmp-frontend/src/app/ui/misc/oauth2-dialog/oauth2-dialog.component.html @@ -0,0 +1 @@ +

oauth2-dialog works!

diff --git a/dmp-frontend/src/app/ui/misc/oauth2-dialog/oauth2-dialog.component.scss b/dmp-frontend/src/app/ui/misc/oauth2-dialog/oauth2-dialog.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/dmp-frontend/src/app/ui/misc/oauth2-dialog/oauth2-dialog.component.ts b/dmp-frontend/src/app/ui/misc/oauth2-dialog/oauth2-dialog.component.ts new file mode 100644 index 000000000..7d9c2f6aa --- /dev/null +++ b/dmp-frontend/src/app/ui/misc/oauth2-dialog/oauth2-dialog.component.ts @@ -0,0 +1,41 @@ +import { Component, OnInit, Inject } from '@angular/core'; +import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material'; +import { ActivatedRoute, Params } from '@angular/router'; +import { BaseComponent } from '@common/base/base.component'; +import { takeUntil } from 'rxjs/operators'; +import { Oauth2DialogService } from './service/oauth2-dialog.service'; + +@Component({ + selector: 'app-oauth2-dialog', + templateUrl: './oauth2-dialog.component.html', + styleUrls: ['./oauth2-dialog.component.scss'] +}) +export class Oauth2DialogComponent extends BaseComponent implements OnInit{ + + constructor( + private route: ActivatedRoute, + private oauth2dialogService: Oauth2DialogService + ) { + super(); + } + + + ngOnInit(): void { + this.route.queryParams.pipe(takeUntil(this._destroyed)) + .subscribe((params: Params) => { + const url = params['url']; + if (!params['code']) { this.loadUrl(url) } else { this.sendCode(params['code']); } + }); + } + + private loadUrl(url: string ) { + console.log(url); + window.location.href = url; + } + + private sendCode(code: string) { + localStorage.setItem('oauthCode', code); + window.close(); + } + +} diff --git a/dmp-frontend/src/app/ui/misc/oauth2-dialog/oauth2-dialog.module.ts b/dmp-frontend/src/app/ui/misc/oauth2-dialog/oauth2-dialog.module.ts new file mode 100644 index 000000000..5bdefcec4 --- /dev/null +++ b/dmp-frontend/src/app/ui/misc/oauth2-dialog/oauth2-dialog.module.ts @@ -0,0 +1,17 @@ +import { NgModule } from '@angular/core'; + +import { Oauth2DialogComponent } from './oauth2-dialog.component'; +import { CommonUiModule } from '@common/ui/common-ui.module'; +import { Oauth2DialogService } from './service/oauth2-dialog.service'; + + +@NgModule({ + declarations: [Oauth2DialogComponent], + imports: [ + CommonUiModule + ], + providers: [ + Oauth2DialogService + ] +}) +export class Oauth2DialogModule { } diff --git a/dmp-frontend/src/app/ui/misc/oauth2-dialog/service/oauth2-dialog.service.ts b/dmp-frontend/src/app/ui/misc/oauth2-dialog/service/oauth2-dialog.service.ts new file mode 100644 index 000000000..5fb27e334 --- /dev/null +++ b/dmp-frontend/src/app/ui/misc/oauth2-dialog/service/oauth2-dialog.service.ts @@ -0,0 +1,34 @@ +import { Injectable, Inject } from '@angular/core'; +import { BaseService } from '@common/base/base.service'; +import { BehaviorSubject, Observable, interval } from 'rxjs'; +import { ConfigurationService } from '@app/core/services/configuration/configuration.service'; +import { isNullOrUndefined } from 'util'; +import { takeUntil } from 'rxjs/operators'; + +@Injectable() +export class Oauth2DialogService extends BaseService{ + + private code: BehaviorSubject = new BehaviorSubject(undefined); + + constructor(private configurationService: ConfigurationService) { + super(); + } + + public registerCode(code: string) { + this.code.next(code); + } + + public login(url: string): Observable { + const windows = window.open(this.configurationService.app + 'oauth2?url=' + encodeURIComponent(url) ,'', 'height=500px,width=500px'); + const sub = interval(300).pipe(takeUntil(this._destroyed)).subscribe(() => { + if (windows.closed) { + const oauthCode = localStorage.getItem('oauthCode'); + localStorage.removeItem('oauthCode'); + this.code.next(oauthCode); + sub.unsubscribe(); + } + }); + return this.code.asObservable(); + } + +} diff --git a/dmp-frontend/src/assets/config/config.json b/dmp-frontend/src/assets/config/config.json index 1aac1c5cd..1c32e5676 100644 --- a/dmp-frontend/src/assets/config/config.json +++ b/dmp-frontend/src/assets/config/config.json @@ -8,7 +8,7 @@ }, "defaultCulture": "en-US", "loginProviders": { - "enabled": [1, 2, 3, 4, 5, 6, 7, 8, 10], + "enabled": [1, 2, 3, 4, 5, 6, 7, 8], "facebookConfiguration": { "clientId": "" }, "googleConfiguration": { "clientId": "" }, "linkedInConfiguration": {