deposit auth changes

This commit is contained in:
Efstratios Giannopoulos 2024-05-21 12:10:08 +03:00
parent 04b3016fa8
commit dadef8297c
15 changed files with 90 additions and 150 deletions

View File

@ -1,12 +1,9 @@
package org.opencdmp.model.persist.deposit;
import org.opencdmp.commons.validation.BaseValidator;
import gr.cite.tools.validation.specification.Specification;
import org.opencdmp.commons.validation.BaseValidator;
import org.opencdmp.convention.ConventionService;
import org.opencdmp.errorcode.ErrorThesaurusProperties;
import org.opencdmp.model.persist.ReferencePersist;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Scope;
@ -27,14 +24,14 @@ public class DepositAuthenticateRequest {
public static final String _code = "code";
public String getRepositoryId() {
return repositoryId;
return this.repositoryId;
}
public void setRepositoryId(String repositoryId) {
this.repositoryId = repositoryId;
}
public String getCode() {
return code;
return this.code;
}
public void setCode(String code) {
this.code = code;
@ -63,10 +60,10 @@ public class DepositAuthenticateRequest {
return Arrays.asList(
this.spec()
.must(() -> !this.isEmpty(item.getRepositoryId()))
.failOn(DepositAuthenticateRequest._repositoryId).failWith(messageSource.getMessage("Validation_Required", new Object[]{DepositAuthenticateRequest._repositoryId}, LocaleContextHolder.getLocale())),
.failOn(DepositAuthenticateRequest._repositoryId).failWith(this.messageSource.getMessage("Validation_Required", new Object[]{DepositAuthenticateRequest._repositoryId}, LocaleContextHolder.getLocale())),
this.spec()
.must(() -> !this.isEmpty(item.getCode()))
.failOn(DepositAuthenticateRequest._code).failWith(messageSource.getMessage("Validation_Required", new Object[]{DepositAuthenticateRequest._code}, LocaleContextHolder.getLocale()))
.failOn(DepositAuthenticateRequest._code).failWith(this.messageSource.getMessage("Validation_Required", new Object[]{DepositAuthenticateRequest._code}, LocaleContextHolder.getLocale()))
);
}
}

View File

@ -25,7 +25,7 @@ public class DepositRequest {
public static final String _dmpId = "dmpId";
private String accessToken;
private String authorizationCode;
private BaseFieldSet project;
@ -45,12 +45,12 @@ public class DepositRequest {
this.dmpId = dmpId;
}
public String getAccessToken() {
return this.accessToken;
public String getAuthorizationCode() {
return this.authorizationCode;
}
public void setAccessToken(String accessToken) {
this.accessToken = accessToken;
public void setAuthorizationCode(String authorizationCode) {
this.authorizationCode = authorizationCode;
}
public BaseFieldSet getProject() {

View File

@ -29,7 +29,7 @@ public class DepositClientImpl implements DepositClient {
@Override
public String authenticate(String code) {
logger.debug(new MapLogEntry("code"));
return this.depositClient.get().uri("/authenticate/", uriBuilder -> uriBuilder.queryParam("authToken", code).build()).exchangeToMono(mono -> mono.statusCode().isError() ? mono.createException().flatMap(Mono::error) : mono.bodyToMono(String.class)).block();
return this.depositClient.get().uri("/authenticate", uriBuilder -> uriBuilder.queryParam("authToken", code).build()).exchangeToMono(mono -> mono.statusCode().isError() ? mono.createException().flatMap(Mono::error) : mono.bodyToMono(String.class)).block();
}
@Override

View File

@ -1,10 +1,8 @@
package org.opencdmp.service.deposit;
import org.opencdmp.model.EntityDoi;
import org.opencdmp.model.persist.deposit.DepositAuthenticateRequest;
import org.opencdmp.model.persist.deposit.DepositRequest;
import org.opencdmp.model.deposit.DepositConfiguration;
import gr.cite.tools.fieldset.FieldSet;
import org.opencdmp.model.EntityDoi;
import org.opencdmp.model.persist.deposit.DepositRequest;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
@ -21,6 +19,4 @@ public interface DepositService {
EntityDoi deposit(DepositRequest dmpDepositModel) throws Exception;
String getLogo(String repositoryId) throws InvalidApplicationException, InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException;
String authenticate(DepositAuthenticateRequest model) throws InvalidApplicationException, InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException;
}

View File

@ -282,12 +282,20 @@ public class DepositServiceImpl implements DepositService {
DepositSourceEntity source = this.getDepositSources().stream().filter(depositSource -> depositSource.getRepositoryId().equals(dmpDepositModel.getRepositoryId())).findFirst().orElse(null);
if (source == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{dmpDepositModel.getRepositoryId(), DepositSourceEntity.class.getSimpleName()}, LocaleContextHolder.getLocale()));
String accessToken = null;
if (!this.conventionService.isNullOrEmpty(dmpDepositModel.getAuthorizationCode())) {
DepositAuthenticateRequest authenticateRequest = new DepositAuthenticateRequest();
authenticateRequest.setRepositoryId(dmpDepositModel.getRepositoryId());
authenticateRequest.setCode(dmpDepositModel.getAuthorizationCode());
accessToken = this.authenticate(authenticateRequest);
}
org.opencdmp.model.file.FileEnvelope pdfFile = this.fileTransformerService.exportDmp(dmpEntity.getId(), source.getPdfTransformerId(),"pdf");
org.opencdmp.model.file.FileEnvelope rda = this.fileTransformerService.exportDmp(dmpEntity.getId(), source.getRdaTransformerId(),"json");
FileEnvelopeModel pdfEnvelope = new FileEnvelopeModel();
FileEnvelopeModel jsonEnvelope = new FileEnvelopeModel();
pdfEnvelope.setFilename(pdfFile.getFilename());
jsonEnvelope.setMimeType("application/pdf");
jsonEnvelope.setFilename(rda.getFilename());
@ -299,14 +307,14 @@ public class DepositServiceImpl implements DepositService {
pdfEnvelope.setFileRef(this.addFileToSharedStorage(pdfFile));
jsonEnvelope.setFileRef(this.addFileToSharedStorage(rda));
}
//GK: Fifth Transform them to the DepositModel
DmpModel depositModel = this.builderFactory.builder(DmpCommonModelBuilder.class).useSharedStorage(depositClient.getConfiguration().isUseSharedStorage()).authorize(AuthorizationFlags.OwnerOrDmpAssociatedOrPermission)
.setRepositoryId(dmpDepositModel.getRepositoryId()).setPdfFile(pdfEnvelope).setRdaJsonFile(jsonEnvelope).build(dmpEntity);
//GK: Sixth Perform the deposit
String doi = depositClient.deposit(depositModel, dmpDepositModel.getAccessToken());
String doi = depositClient.deposit(depositModel, accessToken);
//GK: Something has gone wrong return null
if (doi.isEmpty()) return null;
//GK: doi is fine store it in database
@ -378,8 +386,7 @@ public class DepositServiceImpl implements DepositService {
return depositClient.getLogo();
}
@Override
public String authenticate(DepositAuthenticateRequest model) throws InvalidApplicationException, InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
private String authenticate(DepositAuthenticateRequest model) throws InvalidApplicationException, InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
this.authorizationService.authorizeForce(Permission.BrowseDeposit, Permission.DeferredAffiliation);
DepositClient depositClient = this.getDepositClient(model.getRepositoryId());

View File

@ -12,7 +12,6 @@ import org.opencdmp.model.EntityDoi;
import org.opencdmp.model.censorship.EntityDoiCensor;
import org.opencdmp.model.censorship.deposit.DepositConfigurationCensor;
import org.opencdmp.model.deposit.DepositConfiguration;
import org.opencdmp.model.persist.deposit.DepositAuthenticateRequest;
import org.opencdmp.model.persist.deposit.DepositRequest;
import org.opencdmp.service.deposit.DepositService;
import org.slf4j.LoggerFactory;
@ -71,19 +70,6 @@ public class DepositController {
return model;
}
@PostMapping("/get-access-token")
@ValidationFilterAnnotation(validator = DepositAuthenticateRequest.DepositAuthenticateRequestValidator.ValidatorName, argumentName = "model")
public String getAccessToken(@RequestBody DepositAuthenticateRequest model) throws InvalidApplicationException, InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
logger.debug(new MapLogEntry("get access token" + DepositAuthenticateRequest.class.getSimpleName()).And("model", model));
String accessToken = this.depositService.authenticate(model);
this.auditService.track(AuditableAction.Deposit_GetAccessToken, Map.ofEntries(
new AbstractMap.SimpleEntry<String, Object>("model", model)
));
return accessToken;
}
@PostMapping("/deposit")
@Transactional
@ValidationFilterAnnotation(validator = DepositRequest.DepositRequestValidator.ValidatorName, argumentName = "model")

View File

@ -366,9 +366,13 @@ const appRoutes: Routes = [
},
{ path: 'logout', loadChildren: () => import('./ui/auth/logout/logout.module').then(m => m.LogoutModule) },
{ path: 'reload', component: ReloadHelperComponent },
{ path: 'oauth2', component: DepositOauth2DialogComponent },
{ path: 'login/external/zenodo', component: DepositOauth2DialogComponent },
{ path: 'deposit/oauth2', loadChildren: () => import('./ui/misc/deposit-oauth2-dialog/deposit-oauth2-dialog.module').then(m => m.DepositOauth2DialogModule) }
{
path: 'deposit/oauth2',
loadChildren: () => import('./ui/misc/deposit-oauth2-dialog/deposit-oauth2-dialog.module').then(m => m.DepositOauth2DialogModule),
data: {
showOnlyRouterOutlet: true
}
}
];
@NgModule({

View File

@ -1,4 +1,4 @@
<div class="wrapper" *ngIf="!onlySplash">
<div class="wrapper" *ngIf="!onlySplash && !showOnlyRouterOutlet">
<app-navbar (sidebarToggled)="sidenav.toggle(); toggleNavbar($event);"></app-navbar>
<mat-sidenav-container fullscreen class="main-container">
<mat-sidenav #sidenav mode="side" opened class="sidenav" [fixedInViewport]="true" [fixedTopGap]="80">
@ -22,10 +22,10 @@
</div>
</div> -->
</div>
<app-notification *ngIf="!onlySplash"></app-notification>
<router-outlet *ngIf="onlySplash"></router-outlet>
<app-notification *ngIf="!onlySplash && !showOnlyRouterOutlet"></app-notification>
<router-outlet *ngIf="onlySplash || showOnlyRouterOutlet"></router-outlet>
<ngx-guided-tour
<ngx-guided-tour *ngIf="!showOnlyRouterOutlet"
[skipText]="'DASHBOARD.TOUR-GUIDE.LEAVE-TOUR'| translate"
[nextText]="'DASHBOARD.TOUR-GUIDE.GOT-IT'| translate"
></ngx-guided-tour>

View File

@ -42,6 +42,7 @@ export class AppComponent implements OnInit, AfterViewInit {
helpContentEnabled: boolean;
private statusChangeSubscription: Subscription;
onlySplash = true;
showOnlyRouterOutlet = false;
@ViewChild('sidenav') sidenav:MatSidenav;
@ -147,6 +148,22 @@ export class AppComponent implements OnInit, AfterViewInit {
while (child.firstChild) {
child = child.firstChild;
}
if (child.snapshot.data && child.snapshot.data.showOnlyRouterOutlet) {
this.showOnlyRouterOutlet = true;
this.ccService.getConfig().enabled = false;
this.ccService.destroy();
this.ccService.init(this.ccService.getConfig());
} else {
this.showOnlyRouterOutlet = false;
if (this.cookieService.get("cookiesConsent") == "true") {
this.ccService.getConfig().enabled = false;
} else {
this.ccService.getConfig().enabled = true;
}
this.ccService.destroy();
this.ccService.init(this.ccService.getConfig());
}
const usePrefix = child.snapshot.data['usePrefix'] ?? true;
if (child.snapshot.data['getFromTitleService']) {
return { title: this.titleService.getTitle(), usePrefix: usePrefix };

View File

@ -3,7 +3,7 @@ import { Guid } from "@common/types/guid";
export class DepositRequest {
repositoryId: string;
dmpId: Guid;
accessToken: String;
authorizationCode: String;
project: DepositRequestFields;
}
@ -14,4 +14,4 @@ export interface DepositRequestFields {
export class DepositAuthenticateRequest {
repositoryId: string;
code: string;
}
}

View File

@ -75,13 +75,13 @@ export class DmpDepositDropdown extends BaseComponent implements OnInit {
dialogRef.afterClosed().pipe(takeUntil(this._destroyed)).subscribe(result => {
switch (result) {
case 0:
this.showOauth2Dialog(this.depositOauth2DialogService.getLoginUrl(repo, this.dmp?.id), repo, this.dmp);
this.showOauth2Dialog(this.depositOauth2DialogService.getLoginUrl(repo), repo, this.dmp);
break;
case 1:
const depositRequest: DepositRequest = {
repositoryId: repo.repositoryId,
dmpId: this.dmp.id,
accessToken: null,
authorizationCode: null,
project: this.EntityDoiFields()
};
this.depositRepositoriesService.deposit(depositRequest)
@ -99,7 +99,7 @@ export class DmpDepositDropdown extends BaseComponent implements OnInit {
const depositRequest: DepositRequest = {
repositoryId: repo.repositoryId,
dmpId: this.dmp.id,
accessToken: null,
authorizationCode: null,
project: this.EntityDoiFields()
};
this.depositRepositoriesService.deposit(depositRequest)
@ -123,22 +123,21 @@ export class DmpDepositDropdown extends BaseComponent implements OnInit {
showOauth2Dialog(url: string, repo: DepositConfiguration, dmp: Dmp) {
this.depositOauth2DialogService.login(url)
.pipe(takeUntil(this._destroyed))
.subscribe(token => {
if (token !== undefined) {
console.log(token);
// const depositRequest: DepositRequest = {
// repositoryId: repo.repositoryId,
// dmpId: dmp.id,
// accessToken: token,
// project: this.EntityDoiFields()
// };
// this.depositRepositoriesService.deposit(depositRequest)
// .pipe(takeUntil(this._destroyed))
// .subscribe(doi => {
// this.onDOICallbackSuccess();
// this.outputRepos.push(doi);
// this.outputReposEmitter.emit(this.outputRepos);
// }, error => this.onDOICallbackError(error));
.subscribe(code => {
if (code !== undefined) {
const depositRequest: DepositRequest = {
repositoryId: repo.repositoryId,
dmpId: dmp.id,
authorizationCode: code,
project: this.EntityDoiFields()
};
this.depositRepositoriesService.deposit(depositRequest)
.pipe(takeUntil(this._destroyed))
.subscribe(doi => {
this.onDOICallbackSuccess();
this.outputRepos.push(doi);
this.outputReposEmitter.emit(this.outputRepos);
}, error => this.onDOICallbackError(error));
this.oauthLock = true;
} else {
this.oauthLock = false;

View File

@ -4,18 +4,18 @@ import { DepositOauth2DialogComponent } from './deposit-oauth2-dialog.component'
const routes: Routes = [
{
path: ':id/:entityId',
path: '',
component: DepositOauth2DialogComponent,
data: {
breadcrumb: true
},
showOnlyRouterOutlet: true
}
},
{
path: 'code-callaback',
component: DepositOauth2DialogComponent,
data: {
breadcrumb: true
},
showOnlyRouterOutlet: true
}
},
];

View File

@ -1 +1 @@
<p>oauth2-dialog works!</p>
<p>{{error}}</p>

View File

@ -1,16 +1,7 @@
import { Component, OnInit, Inject } from '@angular/core';
import { ActivatedRoute, ParamMap, Params } from '@angular/router';
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Params } from '@angular/router';
import { BaseComponent } from '@common/base/base.component';
import { takeUntil } from 'rxjs/operators';
import { DepositOauth2DialogService } from './service/deposit-oauth2-dialog.service';
import { DepositService } from '@app/core/services/deposit/deposit.service';
import { DepositConfiguration } from '@app/core/model/deposit/deposit-configuration';
import { nameof } from 'ts-simple-nameof';
import { HttpErrorResponse } from '@angular/common/http';
import { HttpError, HttpErrorHandlingService } from '@common/modules/errors/error-handling/http-error-handling.service';
import { SnackBarNotificationLevel, UiNotificationService } from '@app/core/services/notification/ui-notification-service';
import { DepositAuthenticateRequest, DepositRequest, DepositRequestFields } from '@app/core/model/deposit/deposit-request';
import { EntityDoi } from '@app/core/model/entity-doi/entity-doi';
@Component({
selector: 'app-deposit-oauth2-dialog',
@ -20,11 +11,7 @@ import { EntityDoi } from '@app/core/model/entity-doi/entity-doi';
export class DepositOauth2DialogComponent extends BaseComponent implements OnInit{
constructor(
private route: ActivatedRoute,
private depositRepositoriesService: DepositService,
private uiNotificationService: UiNotificationService,
private httpErrorHandlingService: HttpErrorHandlingService,
private depositOauth2DialogService: DepositOauth2DialogService
private route: ActivatedRoute
) {
super();
}
@ -32,42 +19,14 @@ export class DepositOauth2DialogComponent extends BaseComponent implements OnIni
error: string = null;
ngOnInit(): void {
this.route.paramMap.pipe(takeUntil(this._destroyed)).subscribe((paramMap: ParamMap) => {
let itemId = paramMap.get('id');
let entityId = paramMap.get('entityId');
if (itemId && entityId) {
this.depositRepositoriesService.getRepository(itemId, [
nameof<DepositConfiguration>(x => x.depositType),
nameof<DepositConfiguration>(x => x.repositoryId),
nameof<DepositConfiguration>(x => x.repositoryAuthorizationUrl),
nameof<DepositConfiguration>(x => x.repositoryRecordUrl),
nameof<DepositConfiguration>(x => x.repositoryClientId),
nameof<DepositConfiguration>(x => x.hasLogo),
nameof<DepositConfiguration>(x => x.redirectUri)
])
.pipe(takeUntil(this._destroyed))
.subscribe(
repo => {
this.loadUrl(this.depositOauth2DialogService.getLoginUrl(repo, entityId));
},
error => this.onCallbackError(error));
} else {
this.error = 'Repository id required'
}
});
this.error = null;
this.route.queryParams.pipe(takeUntil(this._destroyed))
.subscribe((params: Params) => {
console.log(params)
if (params['url']) {
this.loadUrl(params['url'])
} else if (params['code']) {
if (!params['state']) {
this.error = 'State required'
} else {
this.getAccessToken(params['code'], params['state']);
}
localStorage.setItem('repositoryOauthCode', params['code']);
window.close();
} else {
this.error = 'Repository id required'
}
@ -77,27 +36,4 @@ export class DepositOauth2DialogComponent extends BaseComponent implements OnIni
private loadUrl(url: string ) {
window.location.href = url;
}
private getAccessToken(code: string, state: string) {
const decoded = decodeURIComponent(state);
const stateParsed = JSON.parse(atob(decoded));
if (!stateParsed) this.error = 'State required';
if (!stateParsed.repositoryId) this.error = 'State repository id required';
if (!stateParsed.entity) this.error = 'State entity required';
const depositAuthenticateRequest: DepositAuthenticateRequest = {
repositoryId: stateParsed.repositoryId,
code: code
};
this.depositRepositoriesService.getAccessToken(depositAuthenticateRequest)
.pipe(takeUntil(this._destroyed))
.subscribe(token => {
localStorage.setItem('repositoryOauthToken', token);
}, error => this.onCallbackError(error));
}
onCallbackError(errorResponse: HttpErrorResponse) {
const error: HttpError = this.httpErrorHandlingService.getError(errorResponse);
this.uiNotificationService.snackBarNotification(error.getMessagesString(), SnackBarNotificationLevel.Warning);
}
}

View File

@ -2,7 +2,6 @@ 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 '@app/utilities/enhancers/utils';
import { takeUntil } from 'rxjs/operators';
import { DepositConfiguration } from '@app/core/model/deposit/deposit-configuration';
@ -15,21 +14,20 @@ export class DepositOauth2DialogService extends BaseService{
super();
}
public getLoginUrl(repo: DepositConfiguration, entityId) {
public getLoginUrl(repo: DepositConfiguration) {
return repo.repositoryAuthorizationUrl + '?client_id=' + repo.repositoryClientId
+ '&state=' + encodeURIComponent(btoa(JSON.stringify({ repositoryId: repo.repositoryId, entity: entityId })))
+ '&response_type=code&scope=deposit:write+deposit:actions+user:email&redirect_uri='
+ repo.redirectUri
}
public login(url: string): Observable<any> {
const windows = window.open(this.configurationService.app + 'oauth2?url=' + encodeURIComponent(url) ,'', `height=500px,width=500px,top=${(window.screen.height / 2) - 200}px,left=${(window.screen.width / 2) - 200}px`);
const windows = window.open(this.configurationService.app + 'deposit/oauth2?url=' + encodeURIComponent(url) ,'', `height=500px,width=500px,top=${(window.screen.height / 2) - 200}px,left=${(window.screen.width / 2) - 200}px`);
const sub = interval(300).pipe(takeUntil(this._destroyed)).subscribe(() => {
if (windows.closed) {
let oauthCode;
if (localStorage.getItem('repositoryOauthToken')) {
oauthCode = localStorage.getItem('repositoryOauthToken');
localStorage.removeItem('repositoryOauthToken');
if (localStorage.getItem('repositoryOauthCode')) {
oauthCode = localStorage.getItem('repositoryOauthCode');
localStorage.removeItem('repositoryOauthCode');
}
this.code.next(oauthCode);
this.code.next(undefined);