Skip to content

Commit 9c78020

Browse files
authored
Merge pull request DSpace#2226 from 4Science/DURACOM-133
Supervised Order with OBSERVER permissions can only view the item
2 parents 7a2b1b2 + 2c3329b commit 9c78020

3 files changed

Lines changed: 133 additions & 12 deletions

File tree

src/app/shared/mydspace-actions/workspaceitem/workspaceitem-actions.component.html

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,16 @@
77
<i class="fa fa-info-circle"></i> {{"submission.workspace.generic.view" | translate}}
88
</button>
99

10-
<a class="btn btn-primary mt-1 mb-3" id="{{'edit_' + object.id}}"
10+
<a class="btn btn-primary mt-1 mb-3 edit-btn" id="{{'edit_' + object.id}}"
11+
*ngIf="(canEditItem$ | async)"
1112
ngbTooltip="{{'submission.workflow.generic.edit-help' | translate}}"
1213
[attr.aria-label]="'submission.workflow.generic.edit-help' | translate"
1314
[routerLink]="['/workspaceitems/' + object.id + '/edit']" role="button">
1415
<i class="fa fa-edit"></i> {{'submission.workflow.generic.edit' | translate}}
1516
</a>
1617

1718
<button type="button" id="{{'delete_' + object.id}}" class="btn btn-danger mt-1 mb-3"
19+
*ngIf="(canEditItem$ | async)"
1820
ngbTooltip="{{'submission.workflow.generic.delete-help' | translate}}"
1921
[attr.aria-label]="'submission.workflow.generic.delete-help' | translate"
2022
(click)="$event.preventDefault();confirmDiscard(content)">
@@ -42,4 +44,4 @@ <h4 class="modal-title text-danger">{{'submission.general.discard.confirm.title'
4244
<button type="button" id="delete_confirm" class="btn btn-danger"
4345
(click)="c('ok')">{{'submission.general.discard.confirm.submit' | translate}}</button>
4446
</div>
45-
</ng-template>
47+
</ng-template>

src/app/shared/mydspace-actions/workspaceitem/workspaceitem-actions.component.spec.ts

Lines changed: 92 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { EPerson } from '../../../core/eperson/models/eperson.model';
12
import { ChangeDetectionStrategy, Injector, NO_ERRORS_SCHEMA } from '@angular/core';
23
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
34
import { Router } from '@angular/router';
@@ -24,12 +25,16 @@ import { RequestService } from '../../../core/data/request.service';
2425
import { getMockRequestService } from '../../mocks/request.service.mock';
2526
import { getMockSearchService } from '../../mocks/search-service.mock';
2627
import { SearchService } from '../../../core/shared/search/search.service';
28+
import { AuthService } from '../../../core/auth/auth.service';
29+
import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service';
2730

2831
let component: WorkspaceitemActionsComponent;
2932
let fixture: ComponentFixture<WorkspaceitemActionsComponent>;
3033

3134
let mockObject: WorkspaceItem;
3235
let notificationsServiceStub: NotificationsServiceStub;
36+
let authorizationService;
37+
let authService;
3338

3439
const mockDataService = jasmine.createSpyObj('WorkspaceitemDataService', {
3540
delete: jasmine.createSpy('delete')
@@ -71,10 +76,91 @@ const item = Object.assign(new Item(), {
7176
const rd = createSuccessfulRemoteDataObject(item);
7277
mockObject = Object.assign(new WorkspaceItem(), { item: observableOf(rd), id: '1234', uuid: '1234' });
7378

74-
describe('WorkspaceitemActionsComponent', () => {
75-
beforeEach(waitForAsync(() => {
79+
const ePersonMock: EPerson = Object.assign(new EPerson(), {
80+
handle: null,
81+
netid: null,
82+
lastActive: '2023-04-27T12:15:57.054+00:00',
83+
canLogIn: true,
84+
email: 'dspacedemo+submit@gmail.com',
85+
requireCertificate: false,
86+
selfRegistered: false,
87+
_name: 'dspacedemo+submit@gmail.com',
88+
id: '914955b1-cf2e-4884-8af7-a166aa24cf73',
89+
uuid: '914955b1-cf2e-4884-8af7-a166aa24cf73',
90+
type: 'eperson',
91+
metadata: {
92+
'dspace.agreements.cookies': [
93+
{
94+
uuid: '0a53a0f2-e168-4ed9-b4af-cba9a2d267ca',
95+
language: null,
96+
value:
97+
'{"authentication":true,"preferences":true,"acknowledgement":true,"google-analytics":true}',
98+
place: 0,
99+
authority: null,
100+
confidence: -1,
101+
},
102+
],
103+
'dspace.agreements.end-user': [
104+
{
105+
uuid: '0879e571-6e4a-4efe-af9b-704c755166be',
106+
language: null,
107+
value: 'true',
108+
place: 0,
109+
authority: null,
110+
confidence: -1,
111+
},
112+
],
113+
'eperson.firstname': [
114+
{
115+
uuid: '18052a3e-f19b-49ca-b9f9-ee4cf9c71b86',
116+
language: null,
117+
value: 'Demo',
118+
place: 0,
119+
authority: null,
120+
confidence: -1,
121+
},
122+
],
123+
'eperson.language': [
124+
{
125+
uuid: '98c2abdb-6a6f-4b41-b455-896bcf333ca3',
126+
language: null,
127+
value: 'en',
128+
place: 0,
129+
authority: null,
130+
confidence: -1,
131+
},
132+
],
133+
'eperson.lastname': [
134+
{
135+
uuid: 'df722e70-9497-468d-a92a-4038e7ef2586',
136+
language: null,
137+
value: 'Submitter',
138+
place: 0,
139+
authority: null,
140+
confidence: -1,
141+
},
142+
],
143+
},
144+
_links: {
145+
groups: {
146+
href: 'http://localhost:8080/server/api/eperson/epersons/914955b1-cf2e-4884-8af7-a166aa24cf73/groups',
147+
},
148+
self: {
149+
href: 'http://localhost:8080/server/api/eperson/epersons/914955b1-cf2e-4884-8af7-a166aa24cf73',
150+
},
151+
},
152+
});
76153

77-
TestBed.configureTestingModule({
154+
authService = jasmine.createSpyObj('authService', {
155+
getAuthenticatedUserFromStore: jasmine.createSpy('getAuthenticatedUserFromStore')
156+
});
157+
158+
describe('WorkspaceitemActionsComponent', () => {
159+
beforeEach(waitForAsync(async () => {
160+
authorizationService = jasmine.createSpyObj('authorizationService', {
161+
isAuthorized: observableOf(true)
162+
});
163+
await TestBed.configureTestingModule({
78164
imports: [
79165
NgbModule,
80166
TranslateModule.forRoot({
@@ -92,6 +178,8 @@ describe('WorkspaceitemActionsComponent', () => {
92178
{ provide: WorkspaceitemDataService, useValue: mockDataService },
93179
{ provide: SearchService, useValue: searchService },
94180
{ provide: RequestService, useValue: requestServce },
181+
{ provide: AuthService, useValue: authService },
182+
{ provide: AuthorizationDataService, useValue: authorizationService},
95183
NgbModal
96184
],
97185
schemas: [NO_ERRORS_SCHEMA]
@@ -105,6 +193,7 @@ describe('WorkspaceitemActionsComponent', () => {
105193
component = fixture.componentInstance;
106194
component.object = mockObject;
107195
notificationsServiceStub = TestBed.inject(NotificationsService as any);
196+
(authService.getAuthenticatedUserFromStore as jasmine.Spy).and.returnValue(observableOf(ePersonMock));
108197
fixture.detectChanges();
109198
});
110199

@@ -150,7 +239,6 @@ describe('WorkspaceitemActionsComponent', () => {
150239
confirmBtn.click();
151240

152241
fixture.detectChanges();
153-
154242
fixture.whenStable().then(() => {
155243
done();
156244
});

src/app/shared/mydspace-actions/workspaceitem/workspaceitem-actions.component.ts

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1-
import { Component, Injector, Input } from '@angular/core';
1+
import { AuthorizationDataService } from 'src/app/core/data/feature-authorization/authorization-data.service';
2+
import { AuthService } from '../../../core/auth/auth.service';
3+
import { Item } from '../../../core/shared/item.model';
4+
import { FeatureID } from '../../../core/data/feature-authorization/feature-id';
5+
import { Component, Injector, Input, OnInit } from '@angular/core';
26
import { Router } from '@angular/router';
37

4-
import { BehaviorSubject } from 'rxjs';
8+
import { BehaviorSubject, Observable, switchMap } from 'rxjs';
59
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
610
import { TranslateService } from '@ngx-translate/core';
711

@@ -11,7 +15,7 @@ import { WorkspaceitemDataService } from '../../../core/submission/workspaceitem
1115
import { NotificationsService } from '../../notifications/notifications.service';
1216
import { RequestService } from '../../../core/data/request.service';
1317
import { SearchService } from '../../../core/shared/search/search.service';
14-
import { getFirstCompletedRemoteData } from '../../../core/shared/operators';
18+
import { getFirstCompletedRemoteData, getRemoteDataPayload } from '../../../core/shared/operators';
1519
import { RemoteData } from '../../../core/data/remote-data';
1620
import { NoContent } from '../../../core/shared/NoContent.model';
1721
import { getWorkspaceItemViewRoute } from '../../../workspaceitems-edit-page/workspaceitems-edit-page-routing-paths';
@@ -24,7 +28,7 @@ import { getWorkspaceItemViewRoute } from '../../../workspaceitems-edit-page/wor
2428
styleUrls: ['./workspaceitem-actions.component.scss'],
2529
templateUrl: './workspaceitem-actions.component.html',
2630
})
27-
export class WorkspaceitemActionsComponent extends MyDSpaceActionsComponent<WorkspaceItem, WorkspaceitemDataService> {
31+
export class WorkspaceitemActionsComponent extends MyDSpaceActionsComponent<WorkspaceItem, WorkspaceitemDataService> implements OnInit {
2832

2933
/**
3034
* The workspaceitem object
@@ -37,6 +41,14 @@ export class WorkspaceitemActionsComponent extends MyDSpaceActionsComponent<Work
3741
*/
3842
public processingDelete$ = new BehaviorSubject<boolean>(false);
3943

44+
/**
45+
* A boolean representing if the user can edit the item
46+
* and therefore can delete it as well
47+
* (since the user can discard the item also from the edit page)
48+
* @type {Observable<boolean>}
49+
*/
50+
canEditItem$: Observable<boolean>;
51+
4052
/**
4153
* Initialize instance variables
4254
*
@@ -54,8 +66,12 @@ export class WorkspaceitemActionsComponent extends MyDSpaceActionsComponent<Work
5466
protected notificationsService: NotificationsService,
5567
protected translate: TranslateService,
5668
protected searchService: SearchService,
57-
protected requestService: RequestService) {
69+
protected requestService: RequestService,
70+
private authService: AuthService,
71+
public authorizationService: AuthorizationDataService,
72+
) {
5873
super(WorkspaceItem.type, injector, router, notificationsService, translate, searchService, requestService);
74+
5975
}
6076

6177
/**
@@ -77,6 +93,22 @@ export class WorkspaceitemActionsComponent extends MyDSpaceActionsComponent<Work
7793
);
7894
}
7995

96+
97+
ngOnInit(): void {
98+
const activeEPerson$ = this.authService.getAuthenticatedUserFromStore();
99+
100+
this.canEditItem$ = activeEPerson$.pipe(
101+
switchMap((eperson) => {
102+
return this.object?.item.pipe(
103+
getFirstCompletedRemoteData(),
104+
getRemoteDataPayload(),
105+
switchMap((item: Item) => {
106+
return this.authorizationService.isAuthorized(FeatureID.CanEditItem, item?._links?.self.href, eperson.uuid);
107+
})
108+
) as Observable<boolean>;
109+
}));
110+
}
111+
80112
/**
81113
* Init the target object
82114
*
@@ -92,5 +124,4 @@ export class WorkspaceitemActionsComponent extends MyDSpaceActionsComponent<Work
92124
getWorkspaceItemViewRoute(workspaceItem: WorkspaceItem): string {
93125
return getWorkspaceItemViewRoute(workspaceItem?.id);
94126
}
95-
96127
}

0 commit comments

Comments
 (0)