From f1c5545a0ed88b98ea038fe1e0033867c044a1d9 Mon Sep 17 00:00:00 2001 From: milanmajchrak <90026355+milanmajchrak@users.noreply.github.com> Date: Wed, 3 Jan 2024 09:47:21 +0100 Subject: [PATCH] customer/uk-it-7 (#436) * ufal/fe-email-restricted-download (#430) * ClarinAuthorization passed, but vanilla not because of vanilla check - I added dtoken into vanilla authorization url and because of that the vanilla authorization will be passed. * Added message for the expiration token message - it was hardcoded. * ufal/fe-not-show-shib-welcome-page * Loaded property from the cfg and check if the page with idp attributes could be showed up. If not redirect the user to the home page. (#431) * ufal/fe-s3-customization (#428) * The admin could see in the bitstream admin UI if the bitstream is stored in both storages (S3, local). * Fixed failing unit test - updated columnSizes object * ufal/curate-translation-missing Added curate collection edit translation and curation task name. (#434) * ufal/upload-on-first-attempt-fix (#435) * Moved file size limit into FileUploader options and handled Upload cancelation. * Check that uploading is successful * ufal/fe-show-checksum-result (#432) * BitstreamChecksum values are fetched and parsed from the BE * Checksum info is showed up. * Added messages and translations. * Added docs and refactored code * Fixed failing tests * Fixed wrong czech translations. * ufal/shibboleth-redirect-from-login (#433) * Send redirectUrl param to the IdP in the target url * The code is updated to be more readable. --------- Co-authored-by: Jozef Misutka <332350+vidiecan@users.noreply.github.com> --- cypress/integration/submission.spec.ts | 2 + src/aai/aai.js | 6 +- ...larin-bitstream-download-page.component.ts | 4 +- ...rin-bitstream-token-expired.component.html | 2 +- .../core/bitstream-checksum-data.service.ts | 36 ++++++++++ src/app/core/core.module.ts | 6 +- .../core/shared/bitstream-checksum.model.ts | 63 +++++++++++++++++ .../shared/bitstream-checksum.resource.ts | 9 +++ src/app/core/shared/bitstream.model.ts | 18 +++++ .../item-bitstreams.component.html | 1 + .../item-bitstreams.component.ts | 6 +- ...-drag-and-drop-bitstream-list.component.ts | 3 +- .../item-edit-bitstream.component.html | 60 +++++++++++++++++ .../item-edit-bitstream.component.spec.ts | 27 ++++++-- .../item-edit-bitstream.component.ts | 56 +++++++++++++++- .../autoregistration.component.html | 2 +- .../autoregistration.component.ts | 26 ++++++- .../upload/uploader/uploader.component.ts | 67 ++++++++++++------- src/app/thumbnail/thumbnail.component.spec.ts | 3 +- src/assets/i18n/cs.json5 | 30 ++++++++- src/assets/i18n/en.json5 | 17 ++++- 21 files changed, 398 insertions(+), 46 deletions(-) create mode 100644 src/app/core/bitstream-checksum-data.service.ts create mode 100644 src/app/core/shared/bitstream-checksum.model.ts create mode 100644 src/app/core/shared/bitstream-checksum.resource.ts diff --git a/cypress/integration/submission.spec.ts b/cypress/integration/submission.spec.ts index b7eaf33b4a8..433e1b7e217 100644 --- a/cypress/integration/submission.spec.ts +++ b/cypress/integration/submission.spec.ts @@ -144,6 +144,8 @@ describe('New Submission page', () => { // Wait for upload to complete before proceeding cy.wait('@upload'); + // Check the upload success notice + cy.get('ds-notification').contains('Upload successful'); // Close the upload success notice cy.get('[data-dismiss="alert"]').click({multiple: true}); diff --git a/src/aai/aai.js b/src/aai/aai.js index 23b60afd68e..455e7d1ad98 100644 --- a/src/aai/aai.js +++ b/src/aai/aai.js @@ -18,9 +18,11 @@ textHelpMore: "First check you are searching under the right country.\nIf your provider is not listed, please read these instructions to obtain an account." }; this.setup = function(options) { + var targetUrl = ''; var opts = jQuery.extend({}, this.defaults, options), defaultCallback = function(e) { - window.location = opts.host + '/Shibboleth.sso/Login?SAMLDS=1&target=' + opts.target + '&entityID=' + window.encodeURIComponent(e.entityID); + targetUrl = opts.target + '?redirectUrl=' + window.location.href; + window.location = opts.host + '/Shibboleth.sso/Login?SAMLDS=1&target=' + targetUrl + '&entityID=' + window.encodeURIComponent(e.entityID); }; //console.log(opts); if(!opts.target){ @@ -33,7 +35,7 @@ opts.ourEntityID, opts.responseUrl, [ ], - opts.host + '/Shibboleth.sso/Login?SAMLDS=1&target='+opts.target+'&entityID='); + opts.host + '/Shibboleth.sso/Login?SAMLDS=1&target=' + targetUrl + '&entityID='); djc.discoPath = window.location.origin + (namespace === '' ? namespace : '/' + namespace) + "/assets/"; djc.metadata = [opts.metadataFeed]; djc.subtitle = "Login via Your home institution (e.g. university)"; diff --git a/src/app/bitstream-page/clarin-bitstream-download-page/clarin-bitstream-download-page.component.ts b/src/app/bitstream-page/clarin-bitstream-download-page/clarin-bitstream-download-page.component.ts index b9e5c356b35..04d2fe87bb9 100644 --- a/src/app/bitstream-page/clarin-bitstream-download-page/clarin-bitstream-download-page.component.ts +++ b/src/app/bitstream-page/clarin-bitstream-download-page/clarin-bitstream-download-page.component.ts @@ -85,7 +85,9 @@ export class ClarinBitstreamDownloadPageComponent implements OnInit { this.requestService.send(headRequest); const clarinIsAuthorized$ = this.rdbService.buildFromRequestUUID(requestId); - const isAuthorized$ = this.authorizationService.isAuthorized(FeatureID.CanDownload, isNotEmpty(bitstream) ? bitstream.self : undefined); + // Clarin authorization will check dtoken parameter from the request + const dtoken = isNotEmpty(this.dtoken) ? '?dtoken=' + this.dtoken : ''; + const isAuthorized$ = this.authorizationService.isAuthorized(FeatureID.CanDownload, isNotEmpty(bitstream) ? bitstream.self + dtoken : undefined); const isLoggedIn$ = this.auth.isAuthenticated(); return observableCombineLatest([clarinIsAuthorized$, isAuthorized$, isLoggedIn$, observableOf(bitstream)]); }), diff --git a/src/app/bitstream-page/clarin-bitstream-token-expired/clarin-bitstream-token-expired.component.html b/src/app/bitstream-page/clarin-bitstream-token-expired/clarin-bitstream-token-expired.component.html index 835832bb45a..ec0a094f6f2 100644 --- a/src/app/bitstream-page/clarin-bitstream-token-expired/clarin-bitstream-token-expired.component.html +++ b/src/app/bitstream-page/clarin-bitstream-token-expired/clarin-bitstream-token-expired.component.html @@ -1,5 +1,5 @@
-

The download token is expired, you will be redirected to the download page.

+

{{'clarin.bitstream.expired.dtoken.message' | translate}}

diff --git a/src/app/core/bitstream-checksum-data.service.ts b/src/app/core/bitstream-checksum-data.service.ts new file mode 100644 index 00000000000..94dfef62de0 --- /dev/null +++ b/src/app/core/bitstream-checksum-data.service.ts @@ -0,0 +1,36 @@ +import { Injectable } from '@angular/core'; +import { dataService } from './data/base/data-service.decorator'; +import { BaseDataService } from './data/base/base-data.service'; +import { RequestService } from './data/request.service'; +import { RemoteDataBuildService } from './cache/builders/remote-data-build.service'; +import { Store } from '@ngrx/store'; +import { CoreState } from './core-state.model'; +import { HALEndpointService } from './shared/hal-endpoint.service'; +import { ObjectCacheService } from './cache/object-cache.service'; +import { DefaultChangeAnalyzer } from './data/default-change-analyzer.service'; +import { HttpClient } from '@angular/common/http'; +import { NotificationsService } from '../shared/notifications/notifications.service'; +import { linkName } from './data/clarin/clrua-data.service'; +import { BitstreamChecksum } from './shared/bitstream-checksum.model'; + +/** + * A service responsible for fetching BitstreamChecksum objects from the REST API + */ +@Injectable() +@dataService(BitstreamChecksum.type) +export class BitstreamChecksumDataService extends BaseDataService { + protected linkPath = 'checksum'; + + constructor( + protected requestService: RequestService, + protected rdbService: RemoteDataBuildService, + protected store: Store, + protected halService: HALEndpointService, + protected objectCache: ObjectCacheService, + protected comparator: DefaultChangeAnalyzer, + protected http: HttpClient, + protected notificationsService: NotificationsService, + ) { + super(linkName, requestService, rdbService, objectCache, halService, undefined); + } +} diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index 306b950f8f4..20a32beef40 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -189,6 +189,8 @@ import { ClarinUserMetadataDataService } from './data/clarin/clarin-user-metadat import { ClarinLicenseResourceMappingService } from './data/clarin/clarin-license-resource-mapping-data.service'; import { ClarinVerificationTokenDataService } from './data/clarin/clarin-verification-token-data.service'; import { ClruaDataService } from './data/clarin/clrua-data.service'; +import { BitstreamChecksum } from './shared/bitstream-checksum.model'; +import { BitstreamChecksumDataService } from './bitstream-checksum-data.service'; /** * When not in production, endpoint responses can be mocked for testing purposes @@ -322,7 +324,8 @@ const PROVIDERS = [ OrcidQueueDataService, OrcidHistoryDataService, SupervisionOrderDataService, - HandleDataService + HandleDataService, + BitstreamChecksumDataService ]; /** @@ -335,6 +338,7 @@ export const models = Bundle, Bitstream, BitstreamFormat, + BitstreamChecksum, Item, Site, Collection, diff --git a/src/app/core/shared/bitstream-checksum.model.ts b/src/app/core/shared/bitstream-checksum.model.ts new file mode 100644 index 00000000000..c6c282d0428 --- /dev/null +++ b/src/app/core/shared/bitstream-checksum.model.ts @@ -0,0 +1,63 @@ +import { BITSTREAM_CHECKSUM } from './bitstream-checksum.resource'; +import { excludeFromEquals } from '../utilities/equals.decorators'; +import { autoserialize, deserialize } from 'cerialize'; +import { ResourceType } from './resource-type'; +import { HALLink } from './hal-link.model'; +import { typedObject } from '../cache/builders/build-decorators'; +import { TypedObject } from '../cache/typed-object.model'; + + +/** + * Model class containing the checksums of a bitstream (local, S3, DB) + */ +@typedObject +export class BitstreamChecksum extends TypedObject { + /** + * The `bitstreamchecksum` object type. + */ + static type = BITSTREAM_CHECKSUM; + + /** + * The object type + */ + @excludeFromEquals + @autoserialize + type: ResourceType; + + /** + * The identifier of this BitstreamChecksum object + */ + @autoserialize + id: string; + + /** + * The checksum of the active store (local/S3) + */ + @autoserialize + activeStore: CheckSum; + + /** + * The checksum from the database + */ + @autoserialize + databaseChecksum: CheckSum; + + /** + * The checksum of the synchronized store (S3, local) + */ + @autoserialize + synchronizedStore: CheckSum; + + @deserialize + _links: { + self: HALLink + }; +} + +/** + * Model class containing a checksum value and algorithm + */ +export interface CheckSum { + checkSumAlgorithm: string; + value: string; +} diff --git a/src/app/core/shared/bitstream-checksum.resource.ts b/src/app/core/shared/bitstream-checksum.resource.ts new file mode 100644 index 00000000000..8404c0e7ca6 --- /dev/null +++ b/src/app/core/shared/bitstream-checksum.resource.ts @@ -0,0 +1,9 @@ +import { ResourceType } from './resource-type'; + +/** + * The resource type for BitstreamChecksum + * + * Needs to be in a separate file to prevent circular + * dependencies in webpack. + */ +export const BITSTREAM_CHECKSUM = new ResourceType('bitstreamchecksum'); diff --git a/src/app/core/shared/bitstream.model.ts b/src/app/core/shared/bitstream.model.ts index c855325d2d6..fe5aed6dab0 100644 --- a/src/app/core/shared/bitstream.model.ts +++ b/src/app/core/shared/bitstream.model.ts @@ -10,6 +10,11 @@ import { HALLink } from './hal-link.model'; import {BUNDLE} from './bundle.resource-type'; import {Bundle} from './bundle.model'; import { ChildHALResource } from './child-hal-resource.model'; +import { BITSTREAM_CHECKSUM } from './bitstream-checksum.resource'; +import { BitstreamChecksum } from './bitstream-checksum.model'; + +// Store number if the bitstream is stored in the both stores (S3 and local) +export const SYNCHRONIZED_STORES_NUMBER = 77; @typedObject @inheritSerialization(DSpaceObject) @@ -34,6 +39,12 @@ export class Bitstream extends DSpaceObject implements ChildHALResource { @autoserialize bundleName: string; + /** + * The number of the store where the bitstream is store, it could be S3, local or both. + */ + @autoserialize + storeNumber: number; + /** * The {@link HALLink}s for this Bitstream */ @@ -44,6 +55,7 @@ export class Bitstream extends DSpaceObject implements ChildHALResource { format: HALLink; content: HALLink; thumbnail: HALLink; + checksum: HALLink; }; /** @@ -67,6 +79,12 @@ export class Bitstream extends DSpaceObject implements ChildHALResource { @link(BUNDLE) bundle?: Observable>; + /** + * The checksum values fetched from the DB, local and S3 store. + */ + @link(BITSTREAM_CHECKSUM) + checksum?: Observable>; + getParentLinkKey(): keyof this['_links'] { return 'format'; } diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.html b/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.html index 4cb9577fcb5..b5c84ab8760 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.html +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.html @@ -32,6 +32,7 @@
{{'item.edit.bitstreams.headers.description' | translate}}
{{'item.edit.bitstreams.headers.format' | translate}}
{{'item.edit.bitstreams.headers.actions' | translate}}
+
{{'item.edit.bitstreams.headers.synchronized' | translate}}
this.bundleService.getBitstreams( this.bundle.id, paginatedOptions, - followLink('format') + followLink('format'), + followLink('checksum') )) ); }) diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream/item-edit-bitstream.component.html b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream/item-edit-bitstream.component.html index a3e29ac10c2..dfd1c49fadf 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream/item-edit-bitstream.component.html +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream/item-edit-bitstream.component.html @@ -48,4 +48,64 @@ +
+
+ + + + | +
+
+
+ + + +
+ + +
+
+ + +
+
+
+ {{'item.edit.bitstreams.checksum.database' | translate}} +
+
+ {{'item.edit.bitstreams.checksum.algorithm' | translate}} {{bitstreamChecksum.databaseChecksum.checkSumAlgorithm}} +
+
+ {{'item.edit.bitstreams.checksum.value' | translate}} {{ bitstreamChecksum.databaseChecksum.value }} +
+
+
+
+ {{'item.edit.bitstreams.checksum.active-store' | translate}} +
+
+ {{'item.edit.bitstreams.checksum.algorithm' | translate}} {{bitstreamChecksum.activeStore.checkSumAlgorithm}} +
+
+ {{'item.edit.bitstreams.checksum.value' | translate}} {{ bitstreamChecksum.activeStore.value }} +
+
+
+
+ {{'item.edit.bitstreams.checksum.sync-store' | translate}} +
+
+ {{'item.edit.bitstreams.checksum.algorithm' | translate}} {{bitstreamChecksum.synchronizedStore.checkSumAlgorithm}} +
+
+ {{'item.edit.bitstreams.checksum.value' | translate}} {{ bitstreamChecksum.synchronizedStore.value }} +
+
+
diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream/item-edit-bitstream.component.spec.ts b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream/item-edit-bitstream.component.spec.ts index 306c0d41e39..fec8a62448a 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream/item-edit-bitstream.component.spec.ts +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream/item-edit-bitstream.component.spec.ts @@ -14,6 +14,7 @@ import { getBitstreamDownloadRoute } from '../../../../app-routing-paths'; import { By } from '@angular/platform-browser'; import { BrowserOnlyMockPipe } from '../../../../shared/testing/browser-only-mock.pipe'; import { RouterLinkDirectiveStub } from '../../../../shared/testing/router-link-directive.stub'; +import { BitstreamChecksum } from '../../../../core/shared/bitstream-checksum.model'; let comp: ItemEditBitstreamComponent; let fixture: ComponentFixture; @@ -21,13 +22,30 @@ let fixture: ComponentFixture; const columnSizes = new ResponsiveTableSizes([ new ResponsiveColumnSizes(2, 2, 3, 4, 4), new ResponsiveColumnSizes(2, 3, 3, 3, 3), - new ResponsiveColumnSizes(2, 2, 2, 2, 2), - new ResponsiveColumnSizes(6, 5, 4, 3, 3) + new ResponsiveColumnSizes(1, 1, 1, 1, 1), + new ResponsiveColumnSizes(5, 4, 3, 2, 2), + new ResponsiveColumnSizes(2, 2, 2, 2, 2) ]); const format = Object.assign(new BitstreamFormat(), { shortDescription: 'PDF' }); + +const checksum = Object.assign(new BitstreamChecksum(), { + activeStore: { + checkSumAlgorithm: 'MD5', + value: '123' + }, + synchronizedStore: { + checkSumAlgorithm: 'MD5', + value: '456' + }, + databaseChecksum: { + checkSumAlgorithm: 'MD5', + value: '789' + } +}); + const bitstream = Object.assign(new Bitstream(), { uuid: 'bitstreamUUID', name: 'Fake Bitstream', @@ -37,7 +55,8 @@ const bitstream = Object.assign(new Bitstream(), { content: { href: 'content-link' } }, - format: createSuccessfulRemoteDataObject$(format) + format: createSuccessfulRemoteDataObject$(format), + checksum: createSuccessfulRemoteDataObject$(checksum) }); const fieldUpdate = { field: bitstream, @@ -81,7 +100,7 @@ describe('ItemEditBitstreamComponent', () => { RouterLinkDirectiveStub ], providers: [ - { provide: ObjectUpdatesService, useValue: objectUpdatesService } + { provide: ObjectUpdatesService, useValue: objectUpdatesService }, ], schemas: [ NO_ERRORS_SCHEMA ] diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream/item-edit-bitstream.component.ts b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream/item-edit-bitstream.component.ts index fcb5c706ac7..a4c81901304 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream/item-edit-bitstream.component.ts +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream/item-edit-bitstream.component.ts @@ -1,15 +1,20 @@ import { Component, Input, OnChanges, OnInit, SimpleChanges, ViewChild, ViewContainerRef } from '@angular/core'; -import { Bitstream } from '../../../../core/shared/bitstream.model'; +import { Bitstream, SYNCHRONIZED_STORES_NUMBER } from '../../../../core/shared/bitstream.model'; import cloneDeep from 'lodash/cloneDeep'; import { ObjectUpdatesService } from '../../../../core/data/object-updates/object-updates.service'; import { Observable } from 'rxjs'; import { BitstreamFormat } from '../../../../core/shared/bitstream-format.model'; -import { getRemoteDataPayload, getFirstSucceededRemoteData } from '../../../../core/shared/operators'; +import { + getRemoteDataPayload, + getFirstSucceededRemoteData, + getFirstCompletedRemoteData +} from '../../../../core/shared/operators'; import { ResponsiveTableSizes } from '../../../../shared/responsive-table-sizes/responsive-table-sizes'; import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service'; import { FieldUpdate } from '../../../../core/data/object-updates/field-update.model'; import { FieldChangeType } from '../../../../core/data/object-updates/field-change-type.model'; import { getBitstreamDownloadRoute } from '../../../../app-routing-paths'; +import { BitstreamChecksum, CheckSum } from '../../../../core/shared/bitstream-checksum.model'; @Component({ selector: 'ds-item-edit-bitstream', @@ -63,6 +68,16 @@ export class ItemEditBitstreamComponent implements OnChanges, OnInit { */ format$: Observable; + /** + * True on mouseover, false otherwise + */ + showChecksumValues = false; + + /** + * Object containing all checksums + */ + checkSum$: Observable; + constructor(private objectUpdatesService: ObjectUpdatesService, private dsoNameService: DSONameService, private viewContainerRef: ViewContainerRef) { @@ -84,6 +99,10 @@ export class ItemEditBitstreamComponent implements OnChanges, OnInit { getFirstSucceededRemoteData(), getRemoteDataPayload() ); + this.checkSum$ = this.bitstream.checksum.pipe( + getFirstCompletedRemoteData(), + getRemoteDataPayload() + ); } /** @@ -114,4 +133,37 @@ export class ItemEditBitstreamComponent implements OnChanges, OnInit { return this.fieldUpdate.changeType >= 0; } + /** + * Compare if two checksums are equal + * + * @param checksum1 e.g. DB checksum + * @param checksum2 e.g. Active store checksum (local or S3) + */ + compareChecksums(checksum1: CheckSum, checksum2: CheckSum): boolean { + return checksum1.value === checksum2.value && checksum1.checkSumAlgorithm === checksum2.checkSumAlgorithm; + } + + /** + * Compare if all checksums are equal (DB, Active store, Synchronized store) + * + * @param bitstreamChecksum which contains all checksums + */ + checksumsAreEqual(bitstreamChecksum: BitstreamChecksum): boolean { + if (this.isBitstreamSynchronized()) { + // Compare DB and Active store checksums + // Compare DB and Synchronized and Active store checksums + return this.compareChecksums(bitstreamChecksum.databaseChecksum, bitstreamChecksum.activeStore) && + this.compareChecksums(bitstreamChecksum.synchronizedStore, bitstreamChecksum.activeStore); + } + // Compare DB and Active store checksums + return this.compareChecksums(bitstreamChecksum.databaseChecksum, bitstreamChecksum.activeStore); + } + + /** + * Check if the bitstream is stored in both stores (S3 and local) + */ + isBitstreamSynchronized() { + return this.bitstream?.storeNumber === SYNCHRONIZED_STORES_NUMBER; + } + } diff --git a/src/app/login-page/autoregistration/autoregistration.component.html b/src/app/login-page/autoregistration/autoregistration.component.html index 8b9ac388f4e..8c2343ab387 100644 --- a/src/app/login-page/autoregistration/autoregistration.component.html +++ b/src/app/login-page/autoregistration/autoregistration.component.html @@ -1,4 +1,4 @@ -
+
{{'clarin.autoregistration.welcome.message' | translate}} {{dspaceName$ | async}}