78243: edit item page fine-grained permission checks

This commit is contained in:
Kristof De Langhe
2021-04-08 17:54:27 +02:00
parent 3d51110217
commit a2e00bbd9f
32 changed files with 402 additions and 100 deletions

View File

@@ -4,7 +4,7 @@ import { Collection } from '../core/shared/collection.model';
import { CollectionPageResolver } from './collection-page.resolver'; import { CollectionPageResolver } from './collection-page.resolver';
import { AuthorizationDataService } from '../core/data/feature-authorization/authorization-data.service'; import { AuthorizationDataService } from '../core/data/feature-authorization/authorization-data.service';
import { Observable, of as observableOf } from 'rxjs'; import { Observable, of as observableOf } from 'rxjs';
import { DsoPageFeatureGuard } from '../core/data/feature-authorization/feature-authorization-guard/dso-page-feature.guard'; import { DsoPageSingleFeatureGuard } from '../core/data/feature-authorization/feature-authorization-guard/dso-page-single-feature.guard';
import { FeatureID } from '../core/data/feature-authorization/feature-id'; import { FeatureID } from '../core/data/feature-authorization/feature-id';
import { AuthService } from '../core/auth/auth.service'; import { AuthService } from '../core/auth/auth.service';
@@ -14,7 +14,7 @@ import { AuthService } from '../core/auth/auth.service';
/** /**
* Guard for preventing unauthorized access to certain {@link Collection} pages requiring administrator rights * Guard for preventing unauthorized access to certain {@link Collection} pages requiring administrator rights
*/ */
export class CollectionPageAdministratorGuard extends DsoPageFeatureGuard<Collection> { export class CollectionPageAdministratorGuard extends DsoPageSingleFeatureGuard<Collection> {
constructor(protected resolver: CollectionPageResolver, constructor(protected resolver: CollectionPageResolver,
protected authorizationService: AuthorizationDataService, protected authorizationService: AuthorizationDataService,
protected router: Router, protected router: Router,

View File

@@ -4,7 +4,7 @@ import { Community } from '../core/shared/community.model';
import { CommunityPageResolver } from './community-page.resolver'; import { CommunityPageResolver } from './community-page.resolver';
import { AuthorizationDataService } from '../core/data/feature-authorization/authorization-data.service'; import { AuthorizationDataService } from '../core/data/feature-authorization/authorization-data.service';
import { Observable, of as observableOf } from 'rxjs'; import { Observable, of as observableOf } from 'rxjs';
import { DsoPageFeatureGuard } from '../core/data/feature-authorization/feature-authorization-guard/dso-page-feature.guard'; import { DsoPageSingleFeatureGuard } from '../core/data/feature-authorization/feature-authorization-guard/dso-page-single-feature.guard';
import { FeatureID } from '../core/data/feature-authorization/feature-id'; import { FeatureID } from '../core/data/feature-authorization/feature-id';
import { AuthService } from '../core/auth/auth.service'; import { AuthService } from '../core/auth/auth.service';
@@ -14,7 +14,7 @@ import { AuthService } from '../core/auth/auth.service';
/** /**
* Guard for preventing unauthorized access to certain {@link Community} pages requiring administrator rights * Guard for preventing unauthorized access to certain {@link Community} pages requiring administrator rights
*/ */
export class CommunityPageAdministratorGuard extends DsoPageFeatureGuard<Community> { export class CommunityPageAdministratorGuard extends DsoPageSingleFeatureGuard<Community> {
constructor(protected resolver: CommunityPageResolver, constructor(protected resolver: CommunityPageResolver,
protected authorizationService: AuthorizationDataService, protected authorizationService: AuthorizationDataService,
protected router: Router, protected router: Router,

View File

@@ -31,8 +31,13 @@ import {
} from './edit-item-page.routing-paths'; } from './edit-item-page.routing-paths';
import { ItemPageReinstateGuard } from './item-page-reinstate.guard'; import { ItemPageReinstateGuard } from './item-page-reinstate.guard';
import { ItemPageWithdrawGuard } from './item-page-withdraw.guard'; import { ItemPageWithdrawGuard } from './item-page-withdraw.guard';
import { ItemPageEditMetadataGuard } from '../item-page-edit-metadata.guard'; import { ItemPageMetadataGuard } from './item-page-metadata.guard';
import { ItemPageAdministratorGuard } from '../item-page-administrator.guard'; import { ItemPageAdministratorGuard } from '../item-page-administrator.guard';
import { ItemPageStatusGuard } from './item-page-status.guard';
import { ItemPageBitstreamsGuard } from './item-page-bitstreams.guard';
import { ItemPageRelationshipsGuard } from './item-page-relationships.guard';
import { ItemPageVersionHistoryGuard } from './item-page-version-history.guard';
import { ItemPageCollectionMapperGuard } from './item-page-collection-mapper.guard';
/** /**
* Routing module that handles the routing for the Edit Item page administrator functionality * Routing module that handles the routing for the Edit Item page administrator functionality
@@ -60,25 +65,25 @@ import { ItemPageAdministratorGuard } from '../item-page-administrator.guard';
path: 'status', path: 'status',
component: ItemStatusComponent, component: ItemStatusComponent,
data: { title: 'item.edit.tabs.status.title', showBreadcrumbs: true }, data: { title: 'item.edit.tabs.status.title', showBreadcrumbs: true },
canActivate: [ItemPageAdministratorGuard] canActivate: [ItemPageStatusGuard]
}, },
{ {
path: 'bitstreams', path: 'bitstreams',
component: ItemBitstreamsComponent, component: ItemBitstreamsComponent,
data: { title: 'item.edit.tabs.bitstreams.title', showBreadcrumbs: true }, data: { title: 'item.edit.tabs.bitstreams.title', showBreadcrumbs: true },
canActivate: [ItemPageAdministratorGuard] canActivate: [ItemPageBitstreamsGuard]
}, },
{ {
path: 'metadata', path: 'metadata',
component: ItemMetadataComponent, component: ItemMetadataComponent,
data: { title: 'item.edit.tabs.metadata.title', showBreadcrumbs: true }, data: { title: 'item.edit.tabs.metadata.title', showBreadcrumbs: true },
canActivate: [ItemPageEditMetadataGuard] canActivate: [ItemPageMetadataGuard]
}, },
{ {
path: 'relationships', path: 'relationships',
component: ItemRelationshipsComponent, component: ItemRelationshipsComponent,
data: { title: 'item.edit.tabs.relationships.title', showBreadcrumbs: true }, data: { title: 'item.edit.tabs.relationships.title', showBreadcrumbs: true },
canActivate: [ItemPageEditMetadataGuard] canActivate: [ItemPageRelationshipsGuard]
}, },
/* TODO - uncomment & fix when view page exists /* TODO - uncomment & fix when view page exists
{ {
@@ -96,13 +101,13 @@ import { ItemPageAdministratorGuard } from '../item-page-administrator.guard';
path: 'versionhistory', path: 'versionhistory',
component: ItemVersionHistoryComponent, component: ItemVersionHistoryComponent,
data: { title: 'item.edit.tabs.versionhistory.title', showBreadcrumbs: true }, data: { title: 'item.edit.tabs.versionhistory.title', showBreadcrumbs: true },
canActivate: [ItemPageAdministratorGuard] canActivate: [ItemPageVersionHistoryGuard]
}, },
{ {
path: 'mapper', path: 'mapper',
component: ItemCollectionMapperComponent, component: ItemCollectionMapperComponent,
data: { title: 'item.edit.tabs.item-mapper.title', showBreadcrumbs: true }, data: { title: 'item.edit.tabs.item-mapper.title', showBreadcrumbs: true },
canActivate: [ItemPageAdministratorGuard] canActivate: [ItemPageCollectionMapperGuard]
} }
] ]
}, },
@@ -175,7 +180,12 @@ import { ItemPageAdministratorGuard } from '../item-page-administrator.guard';
ItemPageReinstateGuard, ItemPageReinstateGuard,
ItemPageWithdrawGuard, ItemPageWithdrawGuard,
ItemPageAdministratorGuard, ItemPageAdministratorGuard,
ItemPageEditMetadataGuard, ItemPageMetadataGuard,
ItemPageStatusGuard,
ItemPageBitstreamsGuard,
ItemPageRelationshipsGuard,
ItemPageVersionHistoryGuard,
ItemPageCollectionMapperGuard,
] ]
}) })
export class EditItemPageRoutingModule { export class EditItemPageRoutingModule {

View File

@@ -3,13 +3,15 @@
{{'item.edit.tabs.status.buttons.' + operation.operationKey + '.label' | translate}} {{'item.edit.tabs.status.buttons.' + operation.operationKey + '.label' | translate}}
</span> </span>
</div> </div>
<div *ngIf="!operation.disabled" class="col-9 float-left action-button"> <div class="col-9 float-left action-button">
<a class="btn btn-outline-primary" [routerLink]="operation.operationUrl"> <span *ngIf="operation.authorized">
<button class="btn btn-outline-primary" [disabled]="operation.disabled" [routerLink]="operation.operationUrl">
{{'item.edit.tabs.status.buttons.' + operation.operationKey + '.button' | translate}} {{'item.edit.tabs.status.buttons.' + operation.operationKey + '.button' | translate}}
</a> </button>
</div> </span>
<div *ngIf="operation.disabled" class="col-9 float-left action-button"> <span *ngIf="!operation.authorized" [ngbTooltip]="'item.edit.tabs.status.buttons.unauthorized' | translate">
<span class="btn btn-danger"> <button class="btn btn-outline-primary" [disabled]="true">
{{'item.edit.tabs.status.buttons.' + operation.operationKey + '.button' | translate}} {{'item.edit.tabs.status.buttons.' + operation.operationKey + '.button' | translate}}
</button>
</span> </span>
</div> </div>

View File

@@ -1,3 +1,5 @@
import { FeatureID } from '../../../core/data/feature-authorization/feature-id';
/** /**
* Represents an item operation used on the edit item page with a key, an operation URL to which will be navigated * Represents an item operation used on the edit item page with a key, an operation URL to which will be navigated
* when performing the action and an option to disable the operation. * when performing the action and an option to disable the operation.
@@ -7,11 +9,15 @@ export class ItemOperation {
operationKey: string; operationKey: string;
operationUrl: string; operationUrl: string;
disabled: boolean; disabled: boolean;
authorized: boolean;
featureID: FeatureID;
constructor(operationKey: string, operationUrl: string) { constructor(operationKey: string, operationUrl: string, featureID?: FeatureID, disabled = false, authorized = true) {
this.operationKey = operationKey; this.operationKey = operationKey;
this.operationUrl = operationUrl; this.operationUrl = operationUrl;
this.setDisabled(false); this.featureID = featureID;
this.authorized = authorized;
this.setDisabled(disabled);
} }
/** /**

View File

@@ -0,0 +1,31 @@
import { Injectable } from '@angular/core';
import { DsoPageSingleFeatureGuard } from '../../core/data/feature-authorization/feature-authorization-guard/dso-page-single-feature.guard';
import { Item } from '../../core/shared/item.model';
import { ItemPageResolver } from '../item-page.resolver';
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router';
import { Observable, of as observableOf } from 'rxjs';
import { FeatureID } from '../../core/data/feature-authorization/feature-id';
import { AuthService } from '../../core/auth/auth.service';
@Injectable({
providedIn: 'root'
})
/**
* Guard for preventing unauthorized access to certain {@link Item} pages requiring manage bitstreams rights
*/
export class ItemPageBitstreamsGuard extends DsoPageSingleFeatureGuard<Item> {
constructor(protected resolver: ItemPageResolver,
protected authorizationService: AuthorizationDataService,
protected router: Router,
protected authService: AuthService) {
super(resolver, authorizationService, router, authService);
}
/**
* Check manage bitstreams authorization rights
*/
getFeatureID(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<FeatureID> {
return observableOf(FeatureID.CanManageBitstreams);
}
}

View File

@@ -0,0 +1,31 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router';
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
import { ItemPageResolver } from '../item-page.resolver';
import { Item } from '../../core/shared/item.model';
import { DsoPageSingleFeatureGuard } from '../../core/data/feature-authorization/feature-authorization-guard/dso-page-single-feature.guard';
import { Observable, of as observableOf } from 'rxjs';
import { FeatureID } from '../../core/data/feature-authorization/feature-id';
import { AuthService } from '../../core/auth/auth.service';
@Injectable({
providedIn: 'root'
})
/**
* Guard for preventing unauthorized access to certain {@link Item} pages requiring manage mappings rights
*/
export class ItemPageCollectionMapperGuard extends DsoPageSingleFeatureGuard<Item> {
constructor(protected resolver: ItemPageResolver,
protected authorizationService: AuthorizationDataService,
protected router: Router,
protected authService: AuthService) {
super(resolver, authorizationService, router, authService);
}
/**
* Check manage mappings authorization rights
*/
getFeatureID(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<FeatureID> {
return observableOf(FeatureID.CanManageMappings);
}
}

View File

@@ -1,12 +1,12 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router'; import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router';
import { AuthorizationDataService } from '../core/data/feature-authorization/authorization-data.service'; import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
import { ItemPageResolver } from './item-page.resolver'; import { ItemPageResolver } from '../item-page.resolver';
import { Item } from '../core/shared/item.model'; import { Item } from '../../core/shared/item.model';
import { DsoPageFeatureGuard } from '../core/data/feature-authorization/feature-authorization-guard/dso-page-feature.guard'; import { DsoPageSingleFeatureGuard } from '../../core/data/feature-authorization/feature-authorization-guard/dso-page-single-feature.guard';
import { Observable, of as observableOf } from 'rxjs'; import { Observable, of as observableOf } from 'rxjs';
import { FeatureID } from '../core/data/feature-authorization/feature-id'; import { FeatureID } from '../../core/data/feature-authorization/feature-id';
import { AuthService } from '../core/auth/auth.service'; import { AuthService } from '../../core/auth/auth.service';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@@ -14,7 +14,7 @@ import { AuthService } from '../core/auth/auth.service';
/** /**
* Guard for preventing unauthorized access to certain {@link Item} pages requiring edit metadata rights * Guard for preventing unauthorized access to certain {@link Item} pages requiring edit metadata rights
*/ */
export class ItemPageEditMetadataGuard extends DsoPageFeatureGuard<Item> { export class ItemPageMetadataGuard extends DsoPageSingleFeatureGuard<Item> {
constructor(protected resolver: ItemPageResolver, constructor(protected resolver: ItemPageResolver,
protected authorizationService: AuthorizationDataService, protected authorizationService: AuthorizationDataService,
protected router: Router, protected router: Router,

View File

@@ -1,5 +1,5 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { DsoPageFeatureGuard } from '../../core/data/feature-authorization/feature-authorization-guard/dso-page-feature.guard'; import { DsoPageSingleFeatureGuard } from '../../core/data/feature-authorization/feature-authorization-guard/dso-page-single-feature.guard';
import { Item } from '../../core/shared/item.model'; import { Item } from '../../core/shared/item.model';
import { ItemPageResolver } from '../item-page.resolver'; import { ItemPageResolver } from '../item-page.resolver';
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service'; import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
@@ -14,7 +14,7 @@ import { AuthService } from '../../core/auth/auth.service';
/** /**
* Guard for preventing unauthorized access to certain {@link Item} pages requiring reinstate rights * Guard for preventing unauthorized access to certain {@link Item} pages requiring reinstate rights
*/ */
export class ItemPageReinstateGuard extends DsoPageFeatureGuard<Item> { export class ItemPageReinstateGuard extends DsoPageSingleFeatureGuard<Item> {
constructor(protected resolver: ItemPageResolver, constructor(protected resolver: ItemPageResolver,
protected authorizationService: AuthorizationDataService, protected authorizationService: AuthorizationDataService,
protected router: Router, protected router: Router,

View File

@@ -0,0 +1,31 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router';
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
import { ItemPageResolver } from '../item-page.resolver';
import { Item } from '../../core/shared/item.model';
import { DsoPageSingleFeatureGuard } from '../../core/data/feature-authorization/feature-authorization-guard/dso-page-single-feature.guard';
import { Observable, of as observableOf } from 'rxjs';
import { FeatureID } from '../../core/data/feature-authorization/feature-id';
import { AuthService } from '../../core/auth/auth.service';
@Injectable({
providedIn: 'root'
})
/**
* Guard for preventing unauthorized access to certain {@link Item} pages requiring manage relationships rights
*/
export class ItemPageRelationshipsGuard extends DsoPageSingleFeatureGuard<Item> {
constructor(protected resolver: ItemPageResolver,
protected authorizationService: AuthorizationDataService,
protected router: Router,
protected authService: AuthService) {
super(resolver, authorizationService, router, authService);
}
/**
* Check manage relationships authorization rights
*/
getFeatureID(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<FeatureID> {
return observableOf(FeatureID.CanManageRelationships);
}
}

View File

@@ -0,0 +1,32 @@
import { Injectable } from '@angular/core';
import { Item } from '../../core/shared/item.model';
import { ItemPageResolver } from '../item-page.resolver';
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router';
import { Observable, of as observableOf } from 'rxjs';
import { FeatureID } from '../../core/data/feature-authorization/feature-id';
import { AuthService } from '../../core/auth/auth.service';
import { DsoPageSomeFeatureGuard } from '../../core/data/feature-authorization/feature-authorization-guard/dso-page-some-feature.guard';
@Injectable({
providedIn: 'root'
})
/**
* Guard for preventing unauthorized access to certain {@link Item} pages requiring any of the rights required for
* the status page
*/
export class ItemPageStatusGuard extends DsoPageSomeFeatureGuard<Item> {
constructor(protected resolver: ItemPageResolver,
protected authorizationService: AuthorizationDataService,
protected router: Router,
protected authService: AuthService) {
super(resolver, authorizationService, router, authService);
}
/**
* Check authorization rights
*/
getFeatureIDs(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<FeatureID[]> {
return observableOf([FeatureID.CanManageMappings, FeatureID.WithdrawItem, FeatureID.ReinstateItem, FeatureID.CanManagePolicies, FeatureID.CanMakePrivate, FeatureID.CanDelete, FeatureID.CanMove]);
}
}

View File

@@ -0,0 +1,31 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router';
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
import { ItemPageResolver } from '../item-page.resolver';
import { Item } from '../../core/shared/item.model';
import { DsoPageSingleFeatureGuard } from '../../core/data/feature-authorization/feature-authorization-guard/dso-page-single-feature.guard';
import { Observable, of as observableOf } from 'rxjs';
import { FeatureID } from '../../core/data/feature-authorization/feature-id';
import { AuthService } from '../../core/auth/auth.service';
@Injectable({
providedIn: 'root'
})
/**
* Guard for preventing unauthorized access to certain {@link Item} pages requiring manage versions rights
*/
export class ItemPageVersionHistoryGuard extends DsoPageSingleFeatureGuard<Item> {
constructor(protected resolver: ItemPageResolver,
protected authorizationService: AuthorizationDataService,
protected router: Router,
protected authService: AuthService) {
super(resolver, authorizationService, router, authService);
}
/**
* Check manage versions authorization rights
*/
getFeatureID(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<FeatureID> {
return observableOf(FeatureID.CanManageVersions);
}
}

View File

@@ -1,4 +1,4 @@
import { DsoPageFeatureGuard } from '../../core/data/feature-authorization/feature-authorization-guard/dso-page-feature.guard'; import { DsoPageSingleFeatureGuard } from '../../core/data/feature-authorization/feature-authorization-guard/dso-page-single-feature.guard';
import { Item } from '../../core/shared/item.model'; import { Item } from '../../core/shared/item.model';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { ItemPageResolver } from '../item-page.resolver'; import { ItemPageResolver } from '../item-page.resolver';
@@ -14,7 +14,7 @@ import { AuthService } from '../../core/auth/auth.service';
/** /**
* Guard for preventing unauthorized access to certain {@link Item} pages requiring withdraw rights * Guard for preventing unauthorized access to certain {@link Item} pages requiring withdraw rights
*/ */
export class ItemPageWithdrawGuard extends DsoPageFeatureGuard<Item> { export class ItemPageWithdrawGuard extends DsoPageSingleFeatureGuard<Item> {
constructor(protected resolver: ItemPageResolver, constructor(protected resolver: ItemPageResolver,
protected authorizationService: AuthorizationDataService, protected authorizationService: AuthorizationDataService,
protected router: Router, protected router: Router,

View File

@@ -4,7 +4,7 @@ import { Item } from '../../../core/shared/item.model';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { ItemOperation } from '../item-operation/itemOperation.model'; import { ItemOperation } from '../item-operation/itemOperation.model';
import { distinctUntilChanged, first, map } from 'rxjs/operators'; import { distinctUntilChanged, first, map } from 'rxjs/operators';
import { BehaviorSubject, Observable } from 'rxjs'; import { BehaviorSubject, combineLatest as observableCombineLatest, Observable, of } from 'rxjs';
import { RemoteData } from '../../../core/data/remote-data'; import { RemoteData } from '../../../core/data/remote-data';
import { getItemEditRoute, getItemPageRoute } from '../../item-page-routing-paths'; import { getItemEditRoute, getItemPageRoute } from '../../item-page-routing-paths';
import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service'; import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service';
@@ -78,42 +78,33 @@ export class ItemStatusComponent implements OnInit {
The value is supposed to be a href for the button The value is supposed to be a href for the button
*/ */
const operations = []; const operations = [];
operations.push(new ItemOperation('authorizations', this.getCurrentUrl(item) + '/authorizations')); operations.push(new ItemOperation('authorizations', this.getCurrentUrl(item) + '/authorizations', FeatureID.CanManagePolicies, true));
operations.push(new ItemOperation('mappedCollections', this.getCurrentUrl(item) + '/mapper')); operations.push(new ItemOperation('mappedCollections', this.getCurrentUrl(item) + '/mapper', FeatureID.CanManageMappings, true));
operations.push(undefined); if (item.isWithdrawn) {
// Store the index of the "withdraw" or "reinstate" operation, because it's added asynchronously operations.push(new ItemOperation('reinstate', this.getCurrentUrl(item) + '/reinstate', FeatureID.ReinstateItem, true));
const indexOfWithdrawReinstate = operations.length - 1;
if (item.isDiscoverable) {
operations.push(new ItemOperation('private', this.getCurrentUrl(item) + '/private'));
} else { } else {
operations.push(new ItemOperation('public', this.getCurrentUrl(item) + '/public')); operations.push(new ItemOperation('reinstate', this.getCurrentUrl(item) + '/reinstate', FeatureID.WithdrawItem, true));
} }
operations.push(new ItemOperation('delete', this.getCurrentUrl(item) + '/delete')); if (item.isDiscoverable) {
operations.push(new ItemOperation('move', this.getCurrentUrl(item) + '/move')); operations.push(new ItemOperation('private', this.getCurrentUrl(item) + '/private', FeatureID.CanMakePrivate, true));
} else {
operations.push(new ItemOperation('public', this.getCurrentUrl(item) + '/public', FeatureID.CanMakePrivate, true));
}
operations.push(new ItemOperation('delete', this.getCurrentUrl(item) + '/delete', FeatureID.CanDelete, true));
operations.push(new ItemOperation('move', this.getCurrentUrl(item) + '/move', FeatureID.CanMove, true));
this.operations$.next(operations); this.operations$.next(operations);
if (item.isWithdrawn) { observableCombineLatest(operations.map((operation) => {
this.authorizationService.isAuthorized(FeatureID.ReinstateItem, item.self).pipe(distinctUntilChanged()).subscribe((authorized) => { if (hasValue(operation.featureID)) {
const newOperations = [...this.operations$.value]; return this.authorizationService.isAuthorized(operation.featureID, item.self).pipe(
if (authorized) { distinctUntilChanged(),
newOperations[indexOfWithdrawReinstate] = new ItemOperation('reinstate', this.getCurrentUrl(item) + '/reinstate'); map((authorized) => new ItemOperation(operation.operationKey, operation.operationUrl, operation.featureID, !authorized, authorized))
);
} else { } else {
newOperations[indexOfWithdrawReinstate] = undefined; return of(operation);
}
this.operations$.next(newOperations);
});
} else {
this.authorizationService.isAuthorized(FeatureID.WithdrawItem, item.self).pipe(distinctUntilChanged()).subscribe((authorized) => {
const newOperations = [...this.operations$.value];
if (authorized) {
newOperations[indexOfWithdrawReinstate] = new ItemOperation('withdraw', this.getCurrentUrl(item) + '/withdraw');
} else {
newOperations[indexOfWithdrawReinstate] = undefined;
}
this.operations$.next(newOperations);
});
} }
})).subscribe((ops) => this.operations$.next(ops));
}); });
this.itemPageRoute$ = this.itemRD$.pipe( this.itemPageRoute$ = this.itemRD$.pipe(
getAllSucceededRemoteDataPayload(), getAllSucceededRemoteDataPayload(),

View File

@@ -3,7 +3,7 @@ import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/ro
import { AuthorizationDataService } from '../core/data/feature-authorization/authorization-data.service'; import { AuthorizationDataService } from '../core/data/feature-authorization/authorization-data.service';
import { ItemPageResolver } from './item-page.resolver'; import { ItemPageResolver } from './item-page.resolver';
import { Item } from '../core/shared/item.model'; import { Item } from '../core/shared/item.model';
import { DsoPageFeatureGuard } from '../core/data/feature-authorization/feature-authorization-guard/dso-page-feature.guard'; import { DsoPageSingleFeatureGuard } from '../core/data/feature-authorization/feature-authorization-guard/dso-page-single-feature.guard';
import { Observable, of as observableOf } from 'rxjs'; import { Observable, of as observableOf } from 'rxjs';
import { FeatureID } from '../core/data/feature-authorization/feature-id'; import { FeatureID } from '../core/data/feature-authorization/feature-id';
import { AuthService } from '../core/auth/auth.service'; import { AuthService } from '../core/auth/auth.service';
@@ -14,7 +14,7 @@ import { AuthService } from '../core/auth/auth.service';
/** /**
* Guard for preventing unauthorized access to certain {@link Item} pages requiring administrator rights * Guard for preventing unauthorized access to certain {@link Item} pages requiring administrator rights
*/ */
export class ItemPageAdministratorGuard extends DsoPageFeatureGuard<Item> { export class ItemPageAdministratorGuard extends DsoPageSingleFeatureGuard<Item> {
constructor(protected resolver: ItemPageResolver, constructor(protected resolver: ItemPageResolver,
protected authorizationService: AuthorizationDataService, protected authorizationService: AuthorizationDataService,
protected router: Router, protected router: Router,

View File

@@ -1,5 +1,5 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { FeatureAuthorizationGuard } from './feature-authorization.guard'; import { SingleFeatureAuthorizationGuard } from './single-feature-authorization.guard';
import { AuthorizationDataService } from '../authorization-data.service'; import { AuthorizationDataService } from '../authorization-data.service';
import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router'; import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router';
import { AuthService } from '../../../auth/auth.service'; import { AuthService } from '../../../auth/auth.service';
@@ -13,7 +13,7 @@ import { FeatureID } from '../feature-id';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class CollectionAdministratorGuard extends FeatureAuthorizationGuard { export class CollectionAdministratorGuard extends SingleFeatureAuthorizationGuard {
constructor(protected authorizationService: AuthorizationDataService, protected router: Router, protected authService: AuthService) { constructor(protected authorizationService: AuthorizationDataService, protected router: Router, protected authService: AuthService) {
super(authorizationService, router, authService); super(authorizationService, router, authService);
} }

View File

@@ -1,5 +1,5 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { FeatureAuthorizationGuard } from './feature-authorization.guard'; import { SingleFeatureAuthorizationGuard } from './single-feature-authorization.guard';
import { AuthorizationDataService } from '../authorization-data.service'; import { AuthorizationDataService } from '../authorization-data.service';
import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router'; import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router';
import { AuthService } from '../../../auth/auth.service'; import { AuthService } from '../../../auth/auth.service';
@@ -13,7 +13,7 @@ import { FeatureID } from '../feature-id';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class CommunityAdministratorGuard extends FeatureAuthorizationGuard { export class CommunityAdministratorGuard extends SingleFeatureAuthorizationGuard {
constructor(protected authorizationService: AuthorizationDataService, protected router: Router, protected authService: AuthService) { constructor(protected authorizationService: AuthorizationDataService, protected router: Router, protected authService: AuthService) {
super(authorizationService, router, authService); super(authorizationService, router, authService);
} }

View File

@@ -4,14 +4,14 @@ import { RemoteData } from '../../remote-data';
import { Observable, of as observableOf } from 'rxjs'; import { Observable, of as observableOf } from 'rxjs';
import { createSuccessfulRemoteDataObject$ } from '../../../../shared/remote-data.utils'; import { createSuccessfulRemoteDataObject$ } from '../../../../shared/remote-data.utils';
import { DSpaceObject } from '../../../shared/dspace-object.model'; import { DSpaceObject } from '../../../shared/dspace-object.model';
import { DsoPageFeatureGuard } from './dso-page-feature.guard'; import { DsoPageSingleFeatureGuard } from './dso-page-single-feature.guard';
import { FeatureID } from '../feature-id'; import { FeatureID } from '../feature-id';
import { AuthService } from '../../../auth/auth.service'; import { AuthService } from '../../../auth/auth.service';
/** /**
* Test implementation of abstract class DsoPageAdministratorGuard * Test implementation of abstract class DsoPageAdministratorGuard
*/ */
class DsoPageFeatureGuardImpl extends DsoPageFeatureGuard<any> { class DsoPageFeatureGuardImpl extends DsoPageSingleFeatureGuard<any> {
constructor(protected resolver: Resolve<RemoteData<any>>, constructor(protected resolver: Resolve<RemoteData<any>>,
protected authorizationService: AuthorizationDataService, protected authorizationService: AuthorizationDataService,
protected router: Router, protected router: Router,
@@ -26,7 +26,7 @@ class DsoPageFeatureGuardImpl extends DsoPageFeatureGuard<any> {
} }
describe('DsoPageAdministratorGuard', () => { describe('DsoPageAdministratorGuard', () => {
let guard: DsoPageFeatureGuard<any>; let guard: DsoPageSingleFeatureGuard<any>;
let authorizationService: AuthorizationDataService; let authorizationService: AuthorizationDataService;
let router: Router; let router: Router;
let authService: AuthService; let authService: AuthService;

View File

@@ -0,0 +1,27 @@
import { DSpaceObject } from '../../../shared/dspace-object.model';
import { DsoPageSomeFeatureGuard } from './dso-page-some-feature.guard';
import { ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { FeatureID } from '../feature-id';
import { map } from 'rxjs/operators';
import { Observable } from 'rxjs';
/**
* Abstract Guard for preventing unauthorized access to {@link DSpaceObject} pages that require rights for a specific feature
* This guard utilizes a resolver to retrieve the relevant object to check authorizations for
*/
export abstract class DsoPageSingleFeatureGuard<T extends DSpaceObject> extends DsoPageSomeFeatureGuard<T> {
/**
* The features to check authorization for
*/
getFeatureIDs(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<FeatureID[]> {
return this.getFeatureID(route, state).pipe(
map((featureID) => [featureID]),
);
}
/**
* The type of feature to check authorization for
* Override this method to define a feature
*/
abstract getFeatureID(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<FeatureID>;
}

View File

@@ -5,15 +5,15 @@ import { Observable } from 'rxjs';
import { getAllSucceededRemoteDataPayload } from '../../../shared/operators'; import { getAllSucceededRemoteDataPayload } from '../../../shared/operators';
import { map } from 'rxjs/operators'; import { map } from 'rxjs/operators';
import { DSpaceObject } from '../../../shared/dspace-object.model'; import { DSpaceObject } from '../../../shared/dspace-object.model';
import { FeatureAuthorizationGuard } from './feature-authorization.guard';
import { AuthService } from '../../../auth/auth.service'; import { AuthService } from '../../../auth/auth.service';
import { hasNoValue, hasValue } from '../../../../shared/empty.util'; import { hasNoValue, hasValue } from '../../../../shared/empty.util';
import { SomeFeatureAuthorizationGuard } from './some-feature-authorization.guard';
/** /**
* Abstract Guard for preventing unauthorized access to {@link DSpaceObject} pages that require rights for a specific feature * Abstract Guard for preventing unauthorized access to {@link DSpaceObject} pages that require rights for a specific feature
* This guard utilizes a resolver to retrieve the relevant object to check authorizations for * This guard utilizes a resolver to retrieve the relevant object to check authorizations for
*/ */
export abstract class DsoPageFeatureGuard<T extends DSpaceObject> extends FeatureAuthorizationGuard { export abstract class DsoPageSomeFeatureGuard<T extends DSpaceObject> extends SomeFeatureAuthorizationGuard {
constructor(protected resolver: Resolve<RemoteData<T>>, constructor(protected resolver: Resolve<RemoteData<T>>,
protected authorizationService: AuthorizationDataService, protected authorizationService: AuthorizationDataService,
protected router: Router, protected router: Router,

View File

@@ -1,5 +1,5 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { FeatureAuthorizationGuard } from './feature-authorization.guard'; import { SingleFeatureAuthorizationGuard } from './single-feature-authorization.guard';
import { AuthorizationDataService } from '../authorization-data.service'; import { AuthorizationDataService } from '../authorization-data.service';
import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router'; import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router';
import { AuthService } from '../../../auth/auth.service'; import { AuthService } from '../../../auth/auth.service';
@@ -13,7 +13,7 @@ import { FeatureID } from '../feature-id';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class GroupAdministratorGuard extends FeatureAuthorizationGuard { export class GroupAdministratorGuard extends SingleFeatureAuthorizationGuard {
constructor(protected authorizationService: AuthorizationDataService, protected router: Router, protected authService: AuthService) { constructor(protected authorizationService: AuthorizationDataService, protected router: Router, protected authService: AuthService) {
super(authorizationService, router, authService); super(authorizationService, router, authService);
} }

View File

@@ -1,4 +1,4 @@
import { FeatureAuthorizationGuard } from './feature-authorization.guard'; import { SingleFeatureAuthorizationGuard } from './single-feature-authorization.guard';
import { AuthorizationDataService } from '../authorization-data.service'; import { AuthorizationDataService } from '../authorization-data.service';
import { FeatureID } from '../feature-id'; import { FeatureID } from '../feature-id';
import { Observable, of as observableOf } from 'rxjs'; import { Observable, of as observableOf } from 'rxjs';
@@ -9,7 +9,7 @@ import { AuthService } from '../../../auth/auth.service';
* Test implementation of abstract class FeatureAuthorizationGuard * Test implementation of abstract class FeatureAuthorizationGuard
* Provide the return values of the overwritten getters as constructor arguments * Provide the return values of the overwritten getters as constructor arguments
*/ */
class FeatureAuthorizationGuardImpl extends FeatureAuthorizationGuard { class FeatureAuthorizationGuardImpl extends SingleFeatureAuthorizationGuard {
constructor(protected authorizationService: AuthorizationDataService, constructor(protected authorizationService: AuthorizationDataService,
protected router: Router, protected router: Router,
protected authService: AuthService, protected authService: AuthService,
@@ -33,7 +33,7 @@ class FeatureAuthorizationGuardImpl extends FeatureAuthorizationGuard {
} }
describe('FeatureAuthorizationGuard', () => { describe('FeatureAuthorizationGuard', () => {
let guard: FeatureAuthorizationGuard; let guard: SingleFeatureAuthorizationGuard;
let authorizationService: AuthorizationDataService; let authorizationService: AuthorizationDataService;
let router: Router; let router: Router;
let authService: AuthService; let authService: AuthService;

View File

@@ -0,0 +1,27 @@
import { ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { FeatureID } from '../feature-id';
import { Observable } from 'rxjs';
import { map} from 'rxjs/operators';
import { SomeFeatureAuthorizationGuard } from './some-feature-authorization.guard';
/**
* Abstract Guard for preventing unauthorized activating and loading of routes when a user
* doesn't have authorized rights on a specific feature and/or object.
* Override the desired getters in the parent class for checking specific authorization on a feature and/or object.
*/
export abstract class SingleFeatureAuthorizationGuard extends SomeFeatureAuthorizationGuard {
/**
* The features to check authorization for
*/
getFeatureIDs(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<FeatureID[]> {
return this.getFeatureID(route, state).pipe(
map((featureID) => [featureID]),
);
}
/**
* The type of feature to check authorization for
* Override this method to define a feature
*/
abstract getFeatureID(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<FeatureID>;
}

View File

@@ -1,5 +1,5 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { FeatureAuthorizationGuard } from './feature-authorization.guard'; import { SingleFeatureAuthorizationGuard } from './single-feature-authorization.guard';
import { FeatureID } from '../feature-id'; import { FeatureID } from '../feature-id';
import { AuthorizationDataService } from '../authorization-data.service'; import { AuthorizationDataService } from '../authorization-data.service';
import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router'; import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router';
@@ -13,7 +13,7 @@ import { AuthService } from '../../../auth/auth.service';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class SiteAdministratorGuard extends FeatureAuthorizationGuard { export class SiteAdministratorGuard extends SingleFeatureAuthorizationGuard {
constructor(protected authorizationService: AuthorizationDataService, protected router: Router, protected authService: AuthService) { constructor(protected authorizationService: AuthorizationDataService, protected router: Router, protected authService: AuthService) {
super(authorizationService, router, authService); super(authorizationService, router, authService);
} }

View File

@@ -1,4 +1,4 @@
import { FeatureAuthorizationGuard } from './feature-authorization.guard'; import { SingleFeatureAuthorizationGuard } from './single-feature-authorization.guard';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { AuthorizationDataService } from '../authorization-data.service'; import { AuthorizationDataService } from '../authorization-data.service';
import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router'; import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router';
@@ -13,7 +13,7 @@ import { AuthService } from '../../../auth/auth.service';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class SiteRegisterGuard extends FeatureAuthorizationGuard { export class SiteRegisterGuard extends SingleFeatureAuthorizationGuard {
constructor(protected authorizationService: AuthorizationDataService, protected router: Router, protected authService: AuthService) { constructor(protected authorizationService: AuthorizationDataService, protected router: Router, protected authService: AuthService) {
super(authorizationService, router, authService); super(authorizationService, router, authService);
} }

View File

@@ -2,16 +2,16 @@ import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot, UrlTr
import { AuthorizationDataService } from '../authorization-data.service'; import { AuthorizationDataService } from '../authorization-data.service';
import { FeatureID } from '../feature-id'; import { FeatureID } from '../feature-id';
import { combineLatest as observableCombineLatest, Observable, of as observableOf } from 'rxjs'; import { combineLatest as observableCombineLatest, Observable, of as observableOf } from 'rxjs';
import { returnForbiddenUrlTreeOrLoginOnFalse } from '../../../shared/operators'; import { returnForbiddenUrlTreeOrLoginOnAllFalse } from '../../../shared/operators';
import { switchMap } from 'rxjs/operators'; import { switchMap } from 'rxjs/operators';
import { AuthService } from '../../../auth/auth.service'; import { AuthService } from '../../../auth/auth.service';
/** /**
* Abstract Guard for preventing unauthorized activating and loading of routes when a user * Abstract Guard for preventing unauthorized activating and loading of routes when a user
* doesn't have authorized rights on a specific feature and/or object. * doesn't have authorized rights on any of the specified features and/or object.
* Override the desired getters in the parent class for checking specific authorization on a feature and/or object. * Override the desired getters in the parent class for checking specific authorization on a list of features and/or object.
*/ */
export abstract class FeatureAuthorizationGuard implements CanActivate { export abstract class SomeFeatureAuthorizationGuard implements CanActivate {
constructor(protected authorizationService: AuthorizationDataService, constructor(protected authorizationService: AuthorizationDataService,
protected router: Router, protected router: Router,
protected authService: AuthService) { protected authService: AuthService) {
@@ -22,17 +22,19 @@ export abstract class FeatureAuthorizationGuard implements CanActivate {
* Redirect the user to the unauthorized page when he/she's not authorized for the given feature * Redirect the user to the unauthorized page when he/she's not authorized for the given feature
*/ */
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean | UrlTree> { canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean | UrlTree> {
return observableCombineLatest(this.getFeatureID(route, state), this.getObjectUrl(route, state), this.getEPersonUuid(route, state)).pipe( return observableCombineLatest(this.getFeatureIDs(route, state), this.getObjectUrl(route, state), this.getEPersonUuid(route, state)).pipe(
switchMap(([featureID, objectUrl, ePersonUuid]) => this.authorizationService.isAuthorized(featureID, objectUrl, ePersonUuid)), switchMap(([featureIDs, objectUrl, ePersonUuid]) =>
returnForbiddenUrlTreeOrLoginOnFalse(this.router, this.authService, state.url) observableCombineLatest(...featureIDs.map((featureID) => this.authorizationService.isAuthorized(featureID, objectUrl, ePersonUuid)))
),
returnForbiddenUrlTreeOrLoginOnAllFalse(this.router, this.authService, state.url)
); );
} }
/** /**
* The type of feature to check authorization for * The features to check authorization for
* Override this method to define a feature * Override this method to define a list of features
*/ */
abstract getFeatureID(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<FeatureID>; abstract getFeatureIDs(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<FeatureID[]>;
/** /**
* The URL of the object to check if the user has authorized rights for * The URL of the object to check if the user has authorized rights for

View File

@@ -12,4 +12,11 @@ export enum FeatureID {
CanManageGroups = 'canManageGroups', CanManageGroups = 'canManageGroups',
IsCollectionAdmin = 'isCollectionAdmin', IsCollectionAdmin = 'isCollectionAdmin',
IsCommunityAdmin = 'isCommunityAdmin', IsCommunityAdmin = 'isCommunityAdmin',
CanManageVersions = 'canManageVersions',
CanManageBitstreams = 'canManageBitstreams',
CanManageRelationships = 'canManageRelationships',
CanManageMappings = 'canManageMappings',
CanManagePolicies = 'canManagePolicies',
CanMakePrivate = 'canMakePrivate',
CanMove = 'canMove',
} }

View File

@@ -201,10 +201,23 @@ export const redirectOn4xx = (router: Router, authService: AuthService) =>
*/ */
export const returnForbiddenUrlTreeOrLoginOnFalse = (router: Router, authService: AuthService, redirectUrl: string) => export const returnForbiddenUrlTreeOrLoginOnFalse = (router: Router, authService: AuthService, redirectUrl: string) =>
(source: Observable<boolean>): Observable<boolean | UrlTree> => (source: Observable<boolean>): Observable<boolean | UrlTree> =>
source.pipe(
map((authorized) => [authorized]),
returnForbiddenUrlTreeOrLoginOnAllFalse(router, authService, redirectUrl),
);
/**
* Operator that returns a UrlTree to a forbidden page or the login page when the booleans received are all false
* @param router The router used to navigate to a forbidden page
* @param authService The AuthService used to determine whether or not the user is logged in
* @param redirectUrl The URL to redirect back to after logging in
*/
export const returnForbiddenUrlTreeOrLoginOnAllFalse = (router: Router, authService: AuthService, redirectUrl: string) =>
(source: Observable<boolean[]>): Observable<boolean | UrlTree> =>
observableCombineLatest(source, authService.isAuthenticated()).pipe( observableCombineLatest(source, authService.isAuthenticated()).pipe(
map(([authorized, authenticated]: [boolean, boolean]) => { map(([authorizedList, authenticated]: [boolean[], boolean]) => {
if (authorized) { if (authorizedList.indexOf(true) > -1) {
return authorized; return true;
} else { } else {
if (authenticated) { if (authenticated) {
return router.parseUrl(getForbiddenRoute()); return router.parseUrl(getForbiddenRoute());

View File

@@ -97,8 +97,14 @@ export class EndpointMockingRestService extends DspaceRestService {
* the mock response if there is one, undefined otherwise * the mock response if there is one, undefined otherwise
*/ */
private getMockData(urlStr: string): any { private getMockData(urlStr: string): any {
let key;
if (this.mockResponseMap.has(urlStr)) {
key = urlStr;
} else {
// didn't find an exact match for the url, try to match only the endpoint without namespace and parameters
const url = new URL(urlStr); const url = new URL(urlStr);
const key = url.pathname.slice(environment.rest.nameSpace.length); key = url.pathname.slice(environment.rest.nameSpace.length);
}
if (this.mockResponseMap.has(key)) { if (this.mockResponseMap.has(key)) {
// parse and stringify to clone the object to ensure that any changes made // parse and stringify to clone the object to ensure that any changes made
// to it afterwards don't affect future calls // to it afterwards don't affect future calls

View File

@@ -0,0 +1,51 @@
{
"_embedded": {
"authorizations": [
{
"id": "cd824a61-95be-4e16-bccd-51fea26707d0_canManageBitstreams_core.item_96715576-3748-4761-ad45-001646632963",
"type": "authorization",
"_links": {
"eperson": {
"href": "https://api7.dspace.org/server/api/authz/authorizations/cd824a61-95be-4e16-bccd-51fea26707d0_canManageBitstreams_core.item_96715576-3748-4761-ad45-001646632963/eperson"
},
"feature": {
"href": "https://api7.dspace.org/server/api/authz/authorizations/cd824a61-95be-4e16-bccd-51fea26707d0_canManageBitstreams_core.item_96715576-3748-4761-ad45-001646632963/feature"
},
"object": {
"href": "https://api7.dspace.org/server/api/authz/authorizations/cd824a61-95be-4e16-bccd-51fea26707d0_canManageBitstreams_core.item_96715576-3748-4761-ad45-001646632963/object"
},
"self": {
"href": "https://api7.dspace.org/server/api/authz/authorizations/cd824a61-95be-4e16-bccd-51fea26707d0_canManageBitstreams_core.item_96715576-3748-4761-ad45-001646632963"
}
},
"_embedded": {
"feature": {
"id": "canManageBitstreams",
"description": "It can be used to verify if the bitstreams of the specified objects can be managed",
"type": "feature",
"resourcetypes": [
"core.item",
"core.bundle"
],
"_links": {
"self": {
"href": "https://api7.dspace.org/server/api/authz/features/canManageBitstreams"
}
}
}
}
}
]
},
"_links": {
"self": {
"href": "https://api7.dspace.org/server/api/authz/authorizations/search/object?uri=https://api7.dspace.org/server/api/core/items/96715576-3748-4761-ad45-001646632963&feature=canManageBitstreams"
}
},
"page": {
"size": 20,
"totalElements": 1,
"totalPages": 1,
"number": 0
}
}

View File

@@ -2,6 +2,7 @@ import { InjectionToken } from '@angular/core';
// import mockSubmissionResponse from './mock-submission-response.json'; // import mockSubmissionResponse from './mock-submission-response.json';
// import mockPublicationResponse from './mock-publication-response.json'; // import mockPublicationResponse from './mock-publication-response.json';
// import mockUntypedItemResponse from './mock-untyped-item-response.json'; // import mockUntypedItemResponse from './mock-untyped-item-response.json';
import mockFeatureItemCanManageBitstreamsResponse from './mock-feature-item-can-manage-bitstreams-response.json';
export class ResponseMapMock extends Map<string, any> {} export class ResponseMapMock extends Map<string, any> {}
@@ -16,4 +17,5 @@ export const mockResponseMap: ResponseMapMock = new Map([
// [ '/config/submissionforms/traditionalpageone', mockSubmissionResponse ] // [ '/config/submissionforms/traditionalpageone', mockSubmissionResponse ]
// [ '/api/pid/find', mockPublicationResponse ], // [ '/api/pid/find', mockPublicationResponse ],
// [ '/api/pid/find', mockUntypedItemResponse ], // [ '/api/pid/find', mockUntypedItemResponse ],
[ 'https://api7.dspace.org/server/api/authz/authorizations/search/object?uri=https://api7.dspace.org/server/api/core/items/96715576-3748-4761-ad45-001646632963&feature=canManageBitstreams&embed=feature', mockFeatureItemCanManageBitstreamsResponse ],
]); ]);

View File

@@ -1761,6 +1761,8 @@
"item.edit.tabs.status.buttons.reinstate.label": "Reinstate item into the repository", "item.edit.tabs.status.buttons.reinstate.label": "Reinstate item into the repository",
"item.edit.tabs.status.buttons.unauthorized": "You don't have permission to perform this action",
"item.edit.tabs.status.buttons.withdraw.button": "Withdraw...", "item.edit.tabs.status.buttons.withdraw.button": "Withdraw...",
"item.edit.tabs.status.buttons.withdraw.label": "Withdraw item from the repository", "item.edit.tabs.status.buttons.withdraw.label": "Withdraw item from the repository",