mirror of
https://github.com/Alfresco/alfresco-content-app.git
synced 2026-09-09 18:02:54 +00:00
[ACS-10409] Fix permission checking logic in library navigation (#4824)
* [ACS-10409] Fix permission checking logic in library navigation * [ACS-10409] cr fix
This commit is contained in:
+8
-1
@@ -36,6 +36,7 @@ import { LibraryEffects } from '../../store/effects';
|
|||||||
import { NodeEntry } from '@alfresco/js-api';
|
import { NodeEntry } from '@alfresco/js-api';
|
||||||
import { getTitleElementText } from '../../testing/test-utils';
|
import { getTitleElementText } from '../../testing/test-utils';
|
||||||
import { MatSnackBarModule } from '@angular/material/snack-bar';
|
import { MatSnackBarModule } from '@angular/material/snack-bar';
|
||||||
|
import { SiteEntry } from '@alfresco/js-api/typings';
|
||||||
|
|
||||||
describe('FavoriteLibrariesComponent', () => {
|
describe('FavoriteLibrariesComponent', () => {
|
||||||
let fixture: ComponentFixture<FavoriteLibrariesComponent>;
|
let fixture: ComponentFixture<FavoriteLibrariesComponent>;
|
||||||
@@ -126,7 +127,13 @@ describe('FavoriteLibrariesComponent', () => {
|
|||||||
|
|
||||||
it('does not navigate when id is not passed', () => {
|
it('does not navigate when id is not passed', () => {
|
||||||
spyOn(router, 'navigate').and.stub();
|
spyOn(router, 'navigate').and.stub();
|
||||||
component.navigateTo({ entry: { guid: 'guid' } } as any);
|
component.navigateTo({
|
||||||
|
entry: {
|
||||||
|
guid: 'test-guid',
|
||||||
|
visibility: 'PUBLIC',
|
||||||
|
role: 'SiteConsumer'
|
||||||
|
}
|
||||||
|
} as SiteEntry);
|
||||||
|
|
||||||
expect(router.navigate).toHaveBeenCalledWith(['favorite/libraries', 'libraryId']);
|
expect(router.navigate).toHaveBeenCalledWith(['favorite/libraries', 'libraryId']);
|
||||||
});
|
});
|
||||||
|
|||||||
+1
-1
@@ -104,7 +104,7 @@ export class FavoriteLibrariesComponent extends PageComponent implements OnInit
|
|||||||
|
|
||||||
navigateTo(node: SiteEntry) {
|
navigateTo(node: SiteEntry) {
|
||||||
if (node?.entry?.guid) {
|
if (node?.entry?.guid) {
|
||||||
this.store.dispatch(new NavigateLibraryAction(node.entry.guid, 'favorite/libraries'));
|
this.store.dispatch(new NavigateLibraryAction(node.entry, 'favorite/libraries'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ export class LibrariesComponent extends PageComponent implements OnInit {
|
|||||||
|
|
||||||
navigateTo(node: SiteEntry) {
|
navigateTo(node: SiteEntry) {
|
||||||
if (node?.entry?.guid) {
|
if (node?.entry?.guid) {
|
||||||
this.store.dispatch(new NavigateLibraryAction(node.entry.guid));
|
this.store.dispatch(new NavigateLibraryAction(node.entry));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -168,7 +168,7 @@ export class SearchLibrariesResultsComponent extends PageComponent implements On
|
|||||||
|
|
||||||
navigateTo(node: SiteEntry) {
|
navigateTo(node: SiteEntry) {
|
||||||
if (node?.entry?.guid) {
|
if (node?.entry?.guid) {
|
||||||
this.store.dispatch(new NavigateLibraryAction(node.entry.guid));
|
this.store.dispatch(new NavigateLibraryAction(node.entry));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import { NotificationService } from '@alfresco/adf-core';
|
|||||||
import { provideEffects } from '@ngrx/effects';
|
import { provideEffects } from '@ngrx/effects';
|
||||||
import { LibraryEffects } from './library.effects';
|
import { LibraryEffects } from './library.effects';
|
||||||
import { AppTestingModule } from '../../testing/app-testing.module';
|
import { AppTestingModule } from '../../testing/app-testing.module';
|
||||||
import { NodeEntry } from '@alfresco/js-api';
|
import { NodeEntry, Site } from '@alfresco/js-api';
|
||||||
|
|
||||||
describe('LibraryEffects', () => {
|
describe('LibraryEffects', () => {
|
||||||
let store: Store<AppStore>;
|
let store: Store<AppStore>;
|
||||||
@@ -48,31 +48,106 @@ describe('LibraryEffects', () => {
|
|||||||
describe('navigateLibrary$', () => {
|
describe('navigateLibrary$', () => {
|
||||||
let notificationService: NotificationService;
|
let notificationService: NotificationService;
|
||||||
let node$: Subject<NodeEntry>;
|
let node$: Subject<NodeEntry>;
|
||||||
|
let admin$: Subject<boolean>;
|
||||||
|
let site: Site;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
node$ = new Subject<NodeEntry>();
|
node$ = new Subject<NodeEntry>();
|
||||||
|
admin$ = new Subject<boolean>();
|
||||||
|
site = { guid: 'site-guid', visibility: 'PUBLIC', role: 'SiteConsumer', id: 'site-id', title: 'Title' };
|
||||||
spyOn(TestBed.inject(ContentApiService), 'getNode').and.returnValue(node$);
|
spyOn(TestBed.inject(ContentApiService), 'getNode').and.returnValue(node$);
|
||||||
notificationService = TestBed.inject(NotificationService);
|
notificationService = TestBed.inject(NotificationService);
|
||||||
spyOn(notificationService, 'showError');
|
spyOn(notificationService, 'showError');
|
||||||
|
spyOn(store, 'dispatch').and.callThrough();
|
||||||
|
spyOn(store, 'select').and.returnValue(admin$);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should display library no permission warning if user does not have permission', () => {
|
it('should display library no permission warning if user does not have permission', () => {
|
||||||
spyOn(notificationService, 'showWarning');
|
spyOn(notificationService, 'showWarning');
|
||||||
store.dispatch(new NavigateLibraryAction('libraryId'));
|
store.dispatch(new NavigateLibraryAction(site));
|
||||||
|
admin$.next(false);
|
||||||
node$.error(new HttpErrorResponse({ status: 403 }));
|
node$.error(new HttpErrorResponse({ status: 403 }));
|
||||||
expect(notificationService.showWarning).toHaveBeenCalledWith('APP.BROWSE.LIBRARIES.LIBRARY_NO_PERMISSIONS_WARNING');
|
expect(notificationService.showWarning).toHaveBeenCalledWith('APP.BROWSE.LIBRARIES.LIBRARY_NO_PERMISSIONS_WARNING');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should display library not found error if library does not exist', () => {
|
it('should display library not found error if library does not exist', () => {
|
||||||
store.dispatch(new NavigateLibraryAction('libraryId'));
|
store.dispatch(new NavigateLibraryAction(site));
|
||||||
|
admin$.next(false);
|
||||||
node$.error(new HttpErrorResponse({ status: 404 }));
|
node$.error(new HttpErrorResponse({ status: 404 }));
|
||||||
expect(notificationService.showError).toHaveBeenCalledWith('APP.BROWSE.LIBRARIES.ERRORS.LIBRARY_NOT_FOUND');
|
expect(notificationService.showError).toHaveBeenCalledWith('APP.BROWSE.LIBRARIES.ERRORS.LIBRARY_NOT_FOUND');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should display generic library loading error if there is different problem than missing permissions or absence of library', () => {
|
it('should display generic library loading error if there is different problem than missing permissions or absence of library', () => {
|
||||||
store.dispatch(new NavigateLibraryAction('libraryId'));
|
store.dispatch(new NavigateLibraryAction(site));
|
||||||
|
admin$.next(false);
|
||||||
node$.error(new HttpErrorResponse({ status: 500 }));
|
node$.error(new HttpErrorResponse({ status: 500 }));
|
||||||
expect(notificationService.showError).toHaveBeenCalledWith('APP.BROWSE.LIBRARIES.ERRORS.LIBRARY_LOADING_ERROR');
|
expect(notificationService.showError).toHaveBeenCalledWith('APP.BROWSE.LIBRARIES.ERRORS.LIBRARY_LOADING_ERROR');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should show error when non-admin user without site role tries to access private site', () => {
|
||||||
|
store.dispatch(new NavigateLibraryAction({ ...site, visibility: 'PRIVATE', role: null }));
|
||||||
|
admin$.next(false);
|
||||||
|
|
||||||
|
expect(store.dispatch).not.toHaveBeenCalledWith(
|
||||||
|
jasmine.objectContaining({
|
||||||
|
type: 'NAVIGATE_ROUTE'
|
||||||
|
})
|
||||||
|
);
|
||||||
|
expect(notificationService.showError).toHaveBeenCalledWith('APP.BROWSE.LIBRARIES.LIBRARY_NO_PERMISSIONS_WARNING');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow admin user to navigate to private site without role', () => {
|
||||||
|
store.dispatch(new NavigateLibraryAction({ ...site, visibility: 'PRIVATE', role: null }));
|
||||||
|
admin$.next(true);
|
||||||
|
node$.next({ entry: { id: 'private-doc-lib-id' } } as NodeEntry);
|
||||||
|
|
||||||
|
expect(store.dispatch).toHaveBeenCalledWith(
|
||||||
|
jasmine.objectContaining({
|
||||||
|
type: 'NAVIGATE_ROUTE',
|
||||||
|
payload: ['libraries', 'private-doc-lib-id']
|
||||||
|
})
|
||||||
|
);
|
||||||
|
expect(notificationService.showError).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow navigation to public site for non-admin user without a role', () => {
|
||||||
|
store.dispatch(new NavigateLibraryAction({ ...site, role: null }));
|
||||||
|
admin$.next(false);
|
||||||
|
node$.next({ entry: { id: 'public-doc-lib-id' } } as NodeEntry);
|
||||||
|
|
||||||
|
expect(store.dispatch).toHaveBeenCalledWith(
|
||||||
|
jasmine.objectContaining({
|
||||||
|
type: 'NAVIGATE_ROUTE',
|
||||||
|
payload: ['libraries', 'public-doc-lib-id']
|
||||||
|
})
|
||||||
|
);
|
||||||
|
expect(notificationService.showError).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow navigation to private site for non-admin user with a role', () => {
|
||||||
|
store.dispatch(new NavigateLibraryAction({ ...site, visibility: 'PRIVATE' }));
|
||||||
|
admin$.next(false);
|
||||||
|
node$.next({ entry: { id: 'role-doc-lib-id' } } as NodeEntry);
|
||||||
|
|
||||||
|
expect(store.dispatch).toHaveBeenCalledWith(
|
||||||
|
jasmine.objectContaining({
|
||||||
|
type: 'NAVIGATE_ROUTE',
|
||||||
|
payload: ['libraries', 'role-doc-lib-id']
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use custom route if provided', () => {
|
||||||
|
store.dispatch(new NavigateLibraryAction(site, 'custom-route'));
|
||||||
|
admin$.next(false);
|
||||||
|
node$.next({ entry: { id: 'doc-lib-id' } } as NodeEntry);
|
||||||
|
|
||||||
|
expect(store.dispatch).toHaveBeenCalledWith(
|
||||||
|
jasmine.objectContaining({
|
||||||
|
type: 'NAVIGATE_ROUTE',
|
||||||
|
payload: ['custom-route', 'doc-lib-id']
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -31,12 +31,13 @@ import {
|
|||||||
LibraryActionTypes,
|
LibraryActionTypes,
|
||||||
NavigateLibraryAction,
|
NavigateLibraryAction,
|
||||||
NavigateRouteAction,
|
NavigateRouteAction,
|
||||||
UpdateLibraryAction
|
UpdateLibraryAction,
|
||||||
|
isAdmin
|
||||||
} from '@alfresco/aca-shared/store';
|
} from '@alfresco/aca-shared/store';
|
||||||
import { inject, Injectable } from '@angular/core';
|
import { inject, Injectable } from '@angular/core';
|
||||||
import { Actions, createEffect, ofType } from '@ngrx/effects';
|
import { Actions, createEffect, ofType } from '@ngrx/effects';
|
||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
import { map, mergeMap, take } from 'rxjs/operators';
|
import { map, mergeMap, take, tap } from 'rxjs/operators';
|
||||||
import { ContentApiService } from '@alfresco/aca-shared';
|
import { ContentApiService } from '@alfresco/aca-shared';
|
||||||
import { ContentManagementService } from '../../services/content-management.service';
|
import { ContentManagementService } from '../../services/content-management.service';
|
||||||
import { NotificationService } from '@alfresco/adf-core';
|
import { NotificationService } from '@alfresco/adf-core';
|
||||||
@@ -99,45 +100,60 @@ export class LibraryEffects {
|
|||||||
this.actions$.pipe(
|
this.actions$.pipe(
|
||||||
ofType<CreateLibraryAction>(LibraryActionTypes.Create),
|
ofType<CreateLibraryAction>(LibraryActionTypes.Create),
|
||||||
mergeMap(() => this.content.createLibrary()),
|
mergeMap(() => this.content.createLibrary()),
|
||||||
map((libraryId) => new NavigateLibraryAction(libraryId))
|
tap((libraryId) => this.navigateToLibraryById(libraryId))
|
||||||
),
|
),
|
||||||
{ dispatch: true }
|
{ dispatch: false }
|
||||||
);
|
);
|
||||||
|
|
||||||
navigateLibrary$ = createEffect(
|
navigateLibrary$ = createEffect(
|
||||||
() =>
|
() =>
|
||||||
this.actions$.pipe(
|
this.actions$.pipe(
|
||||||
ofType<NavigateLibraryAction>(LibraryActionTypes.Navigate),
|
ofType<NavigateLibraryAction>(LibraryActionTypes.Navigate),
|
||||||
map((action) => {
|
tap((action) => {
|
||||||
const libraryId = action.payload;
|
const payload = action.payload;
|
||||||
if (libraryId) {
|
if (payload && 'guid' in payload) {
|
||||||
this.contentApi
|
this.store
|
||||||
.getNode(libraryId, { relativePath: '/documentLibrary' })
|
.select(isAdmin)
|
||||||
.pipe(map((node) => node.entry.id))
|
.pipe(take(1))
|
||||||
.subscribe(
|
.subscribe((isUserAdmin) => {
|
||||||
(id) => {
|
if (!isUserAdmin && payload.visibility !== 'PUBLIC' && !payload.role) {
|
||||||
const route = action.route ? action.route : 'libraries';
|
this.notificationService.showError('APP.BROWSE.LIBRARIES.LIBRARY_NO_PERMISSIONS_WARNING');
|
||||||
this.store.dispatch(new NavigateRouteAction([route, id]));
|
} else {
|
||||||
},
|
this.navigateToLibraryById(payload.guid, action.route);
|
||||||
(error: HttpErrorResponse) => {
|
|
||||||
switch (error.status) {
|
|
||||||
case 403:
|
|
||||||
this.notificationService.showWarning('APP.BROWSE.LIBRARIES.LIBRARY_NO_PERMISSIONS_WARNING');
|
|
||||||
break;
|
|
||||||
case 404:
|
|
||||||
this.notificationService.showError('APP.BROWSE.LIBRARIES.ERRORS.LIBRARY_NOT_FOUND');
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
this.notificationService.showError('APP.BROWSE.LIBRARIES.ERRORS.LIBRARY_LOADING_ERROR');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
{ dispatch: false }
|
{ dispatch: false }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
private navigateToLibraryById(libraryId: string, route = 'libraries'): void {
|
||||||
|
this.contentApi
|
||||||
|
.getNode(libraryId, { relativePath: '/documentLibrary' })
|
||||||
|
.pipe(
|
||||||
|
map((node) => node.entry.id),
|
||||||
|
take(1)
|
||||||
|
)
|
||||||
|
.subscribe({
|
||||||
|
next: (id) => {
|
||||||
|
this.store.dispatch(new NavigateRouteAction([route, id]));
|
||||||
|
},
|
||||||
|
error: (error: HttpErrorResponse) => {
|
||||||
|
switch (error.status) {
|
||||||
|
case 403:
|
||||||
|
this.notificationService.showWarning('APP.BROWSE.LIBRARIES.LIBRARY_NO_PERMISSIONS_WARNING');
|
||||||
|
break;
|
||||||
|
case 404:
|
||||||
|
this.notificationService.showError('APP.BROWSE.LIBRARIES.ERRORS.LIBRARY_NOT_FOUND');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this.notificationService.showError('APP.BROWSE.LIBRARIES.ERRORS.LIBRARY_LOADING_ERROR');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
updateLibrary$ = createEffect(
|
updateLibrary$ = createEffect(
|
||||||
() =>
|
() =>
|
||||||
this.actions$.pipe(
|
this.actions$.pipe(
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Action } from '@ngrx/store';
|
import { Action } from '@ngrx/store';
|
||||||
import { SiteBodyCreate } from '@alfresco/js-api';
|
import { Site, SiteBodyCreate } from '@alfresco/js-api';
|
||||||
import { ModalConfiguration } from '../models/modal-configuration';
|
import { ModalConfiguration } from '../models/modal-configuration';
|
||||||
|
|
||||||
export enum LibraryActionTypes {
|
export enum LibraryActionTypes {
|
||||||
@@ -48,7 +48,7 @@ export class NavigateLibraryAction implements Action {
|
|||||||
readonly type = LibraryActionTypes.Navigate;
|
readonly type = LibraryActionTypes.Navigate;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public payload?: string,
|
public payload?: Site,
|
||||||
public route?: string
|
public route?: string
|
||||||
) {}
|
) {}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user