From dad38ef8adf2f3b0e92f8b7bcd9bbee1c9ef9ff5 Mon Sep 17 00:00:00 2001 From: bram-atmire Date: Tue, 9 Jun 2026 16:19:23 +0200 Subject: [PATCH] Make advanced attachment element visibility configurable (checksum admin-only by default) Add an optional 'visibility' property (public | admin) to each advancedAttachmentRendering metadata element. Elements marked 'admin' are only rendered for site administrators; omitting an element hides it from everyone. The checksum attribute now defaults to admin-only so anonymous and regular users no longer see it on the item page. Refs DSpace/dspace-angular#5822 --- config/config.example.yml | 5 ++++ .../bitstream-attachment.component.html | 2 +- .../bitstream-attachment.component.spec.ts | 28 +++++++++++++++++++ .../bitstream-attachment.component.ts | 15 ++++++++++ .../advanced-attachment-rendering.config.ts | 20 +++++++++++++ src/config/default-app-config.ts | 6 +++- src/environments/environment.test.ts | 6 +++- 7 files changed, 79 insertions(+), 3 deletions(-) diff --git a/config/config.example.yml b/config/config.example.yml index 6f6c67d1ae6..9057ddee96f 100644 --- a/config/config.example.yml +++ b/config/config.example.yml @@ -738,6 +738,10 @@ layout: showDownloadLinkAsAttachment: false # Configuration for advanced attachment rendering in item pages. This controls how files are displayed when showDownloadLinkAsAttachment is enabled. # Defines which metadata/attributes to display for bitstream attachments. + # Each element may set an optional "visibility" controlling who can see it: + # public (default when omitted) - visible to everyone, including anonymous users + # admin - visible only to site administrators + # To hide an element from everyone, remove it from the metadata list entirely. advancedAttachmentRendering: # Metadata and attributes to display for each attachment metadata: @@ -756,6 +760,7 @@ layout: type: attribute - name: checksum type: attribute + visibility: admin # Configuration for customization of search results searchResults: diff --git a/src/app/shared/bitstream-attachment/bitstream-attachment.component.html b/src/app/shared/bitstream-attachment/bitstream-attachment.component.html index 47ee3b47051..0c3736ae978 100644 --- a/src/app/shared/bitstream-attachment/bitstream-attachment.component.html +++ b/src/app/shared/bitstream-attachment/bitstream-attachment.component.html @@ -19,7 +19,7 @@
- @for (attachmentConf of metadataConfig; track $index) { + @for (attachmentConf of (visibleMetadataConfig$ | async); track $index) { @if (attachment.firstMetadataValue(attachmentConf.name) || attachmentConf.type === AdvancedAttachmentElementType.Attribute) {
{{ 'layout.advanced-attachment.' + attachmentConf.name | translate }} diff --git a/src/app/shared/bitstream-attachment/bitstream-attachment.component.spec.ts b/src/app/shared/bitstream-attachment/bitstream-attachment.component.spec.ts index 63a7d59820b..833f2f4b9b7 100644 --- a/src/app/shared/bitstream-attachment/bitstream-attachment.component.spec.ts +++ b/src/app/shared/bitstream-attachment/bitstream-attachment.component.spec.ts @@ -6,6 +6,7 @@ import { import { provideRouter } from '@angular/router'; import { APP_CONFIG } from '@dspace/config/app-config.interface'; import { BitstreamDataService } from '@dspace/core/data/bitstream-data.service'; +import { AuthorizationDataService } from '@dspace/core/data/feature-authorization/authorization-data.service'; import { LocaleService } from '@dspace/core/locale/locale.service'; import { Bitstream } from '@dspace/core/shared/bitstream.model'; import { Item } from '@dspace/core/shared/item.model'; @@ -42,8 +43,13 @@ describe('BitstreamAttachmentComponent', () => { getCurrentLanguageCode: of('en'), getLanguageCodeList: of(languageList), }); + let authorizationService: jasmine.SpyObj; beforeEach(async () => { + authorizationService = jasmine.createSpyObj('AuthorizationDataService', { + isAuthorized: of(true), + }); + await TestBed.configureTestingModule({ imports: [ BitstreamAttachmentComponent, @@ -60,6 +66,7 @@ describe('BitstreamAttachmentComponent', () => { { provide: BitstreamDataService, useValue: {} }, { provide: APP_CONFIG, useValue: environment }, { provide: LocaleService, useValue: mockLocaleService }, + { provide: AuthorizationDataService, useValue: authorizationService }, ], schemas: [NO_ERRORS_SCHEMA], }) @@ -112,4 +119,25 @@ describe('BitstreamAttachmentComponent', () => { const badge = fixture.nativeElement.querySelector('.badge.bg-primary'); expect(badge).toBeNull(); }); + + it('should display admin-only elements (checksum) for administrators', () => { + authorizationService.isAuthorized.and.returnValue(of(true)); + component.ngOnInit(); + fixture.detectChanges(); + expect(fixture.nativeElement.querySelector('[data-test="checksum"]')).toBeTruthy(); + }); + + it('should hide admin-only elements (checksum) from non-administrators', () => { + authorizationService.isAuthorized.and.returnValue(of(false)); + component.ngOnInit(); + fixture.detectChanges(); + expect(fixture.nativeElement.querySelector('[data-test="checksum"]')).toBeNull(); + }); + + it('should always display public elements (format) regardless of admin status', () => { + authorizationService.isAuthorized.and.returnValue(of(false)); + component.ngOnInit(); + fixture.detectChanges(); + expect(fixture.nativeElement.querySelector('[data-test="format"]')).toBeTruthy(); + }); }); diff --git a/src/app/shared/bitstream-attachment/bitstream-attachment.component.ts b/src/app/shared/bitstream-attachment/bitstream-attachment.component.ts index 64abe115d97..d42eae811a1 100644 --- a/src/app/shared/bitstream-attachment/bitstream-attachment.component.ts +++ b/src/app/shared/bitstream-attachment/bitstream-attachment.component.ts @@ -14,6 +14,7 @@ import { } from '@angular/router'; import { AdvancedAttachmentElementType, + AdvancedAttachmentVisibility, AttachmentMetadataConfig, } from '@dspace/config/advanced-attachment-rendering.config'; import { @@ -21,6 +22,8 @@ import { AppConfig, } from '@dspace/config/app-config.interface'; import { BitstreamDataService } from '@dspace/core/data/bitstream-data.service'; +import { AuthorizationDataService } from '@dspace/core/data/feature-authorization/authorization-data.service'; +import { FeatureID } from '@dspace/core/data/feature-authorization/feature-id'; import { RemoteData } from '@dspace/core/data/remote-data'; import { Bitstream, @@ -69,6 +72,12 @@ export class BitstreamAttachmentComponent implements OnInit { */ metadataConfig: AttachmentMetadataConfig[]; + /** + * The configured attachment elements that the current user is allowed to see. + * Elements with visibility "admin" are only emitted for site administrators. + */ + visibleMetadataConfig$: Observable; + /** * All item providers to show buttons of */ @@ -120,6 +129,7 @@ export class BitstreamAttachmentComponent implements OnInit { protected readonly translateService: TranslateService, protected readonly router: Router, protected readonly route: ActivatedRoute, + protected readonly authorizationService: AuthorizationDataService, @Inject(APP_CONFIG) protected appConfig: AppConfig, ) { this.metadataConfig = this.appConfig.layout.advancedAttachmentRendering.metadata; @@ -131,6 +141,11 @@ export class BitstreamAttachmentComponent implements OnInit { ).subscribe((thumbnail: RemoteData) => { this.thumbnail$.next(thumbnail); }); + this.visibleMetadataConfig$ = this.authorizationService.isAuthorized(FeatureID.AdministratorOf).pipe( + map((isAdmin: boolean) => this.metadataConfig.filter( + (config: AttachmentMetadataConfig) => config.visibility !== AdvancedAttachmentVisibility.Admin || isAdmin, + )), + ); this.allAttachmentProviders = this.attachment?.allMetadataValues('bitstream.viewer.provider'); this.bitstreamFormat$ = this.getFormat(this.attachment); this.bitstreamSize = this.getSize(this.attachment); diff --git a/src/config/advanced-attachment-rendering.config.ts b/src/config/advanced-attachment-rendering.config.ts index cbbf4a6f9d0..53de2ad0337 100644 --- a/src/config/advanced-attachment-rendering.config.ts +++ b/src/config/advanced-attachment-rendering.config.ts @@ -12,6 +12,12 @@ export interface AttachmentMetadataConfig { name: string; type: AdvancedAttachmentElementType; truncatable?: boolean; + /** + * Controls who can see this attachment element. + * When omitted, the element is treated as {@link AdvancedAttachmentVisibility.Public}. + * To hide an element from everyone, remove it from the metadata list entirely. + */ + visibility?: AdvancedAttachmentVisibility; } /** @@ -22,3 +28,17 @@ export enum AdvancedAttachmentElementType { Attribute = 'attribute' } +/** + * Controls the audience that can see an advanced attachment element. + */ +export enum AdvancedAttachmentVisibility { + /** + * Visible to everyone, including anonymous users (default when no visibility is configured). + */ + Public = 'public', + /** + * Visible only to site administrators. + */ + Admin = 'admin' +} + diff --git a/src/config/default-app-config.ts b/src/config/default-app-config.ts index fd703d4d024..c3648dfcd8b 100644 --- a/src/config/default-app-config.ts +++ b/src/config/default-app-config.ts @@ -5,7 +5,10 @@ import { AccessibilitySettingsConfig } from './accessibility-settings.config'; import { ActuatorsConfig } from './actuators.config'; import { AddToAnyPluginConfig } from './add-to-any-plugin-config'; import { AdminNotifyMetricsRow } from './admin-notify-metrics.config'; -import { AdvancedAttachmentElementType } from './advanced-attachment-rendering.config'; +import { + AdvancedAttachmentElementType, + AdvancedAttachmentVisibility, +} from './advanced-attachment-rendering.config'; import { AppConfig } from './app-config.interface'; import { AuthConfig } from './auth-config.interfaces'; import { BrowseByConfig } from './browse-by-config.interface'; @@ -812,6 +815,7 @@ export class DefaultAppConfig implements AppConfig { { name: 'checksum', type: AdvancedAttachmentElementType.Attribute, + visibility: AdvancedAttachmentVisibility.Admin, }, ], }, diff --git a/src/environments/environment.test.ts b/src/environments/environment.test.ts index 0ae82e957a1..90ceee67d0c 100644 --- a/src/environments/environment.test.ts +++ b/src/environments/environment.test.ts @@ -1,5 +1,8 @@ // This configuration is only used for unit tests, end-to-end tests use environment.production.ts -import { AdvancedAttachmentElementType } from '@dspace/config/advanced-attachment-rendering.config'; +import { + AdvancedAttachmentElementType, + AdvancedAttachmentVisibility, +} from '@dspace/config/advanced-attachment-rendering.config'; import { NotificationAnimationsType } from '@dspace/config/notifications-config.interfaces'; import { RestRequestMethod } from '@dspace/config/rest-request-method'; import { BuildConfig } from 'src/config/build-config.interface'; @@ -600,6 +603,7 @@ export const environment: BuildConfig = { { name: 'checksum', type: AdvancedAttachmentElementType.Attribute, + visibility: AdvancedAttachmentVisibility.Admin, }, ], },