From 8768645e4aa730860239b670b38d201efca331af Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Wed, 19 Aug 2020 17:47:33 +0200 Subject: [PATCH 01/12] 72541: InfoModule, EndUserAgreementComponent, UserAgreementGuard --- .../bitstream-page-routing.module.ts | 3 +- .../collection-page-routing.module.ts | 11 +++--- .../community-page-routing.module.ts | 7 ++-- .../+item-page/item-page-routing.module.ts | 5 ++- .../submit-page-routing.module.ts | 3 +- .../workflowitems-edit-page-routing.module.ts | 7 ++-- ...workspaceitems-edit-page-routing.module.ts | 3 +- src/app/app-routing.module.ts | 16 ++++++-- src/app/core/core.module.ts | 2 + src/app/core/shared/operators.ts | 12 ++++++ .../user-agreement/user-agreement.guard.ts | 37 +++++++++++++++++++ .../end-user-agreement-content.component.html | 37 +++++++++++++++++++ .../end-user-agreement-content.component.scss | 0 ...d-user-agreement-content.component.spec.ts | 24 ++++++++++++ .../end-user-agreement-content.component.ts | 9 +++++ .../end-user-agreement.component.html | 3 ++ .../end-user-agreement.component.scss | 0 .../end-user-agreement.component.spec.ts | 24 ++++++++++++ .../end-user-agreement.component.ts | 9 +++++ src/app/info/info-routing.module.ts | 33 +++++++++++++++++ src/app/info/info.module.ts | 20 ++++++++++ .../process-page-routing.module.ts | 3 +- .../register-page-routing.module.ts | 4 +- src/assets/i18n/en.json5 | 8 ++++ 24 files changed, 258 insertions(+), 22 deletions(-) create mode 100644 src/app/core/user-agreement/user-agreement.guard.ts create mode 100644 src/app/info/end-user-agreement/end-user-agreement-content/end-user-agreement-content.component.html create mode 100644 src/app/info/end-user-agreement/end-user-agreement-content/end-user-agreement-content.component.scss create mode 100644 src/app/info/end-user-agreement/end-user-agreement-content/end-user-agreement-content.component.spec.ts create mode 100644 src/app/info/end-user-agreement/end-user-agreement-content/end-user-agreement-content.component.ts create mode 100644 src/app/info/end-user-agreement/end-user-agreement.component.html create mode 100644 src/app/info/end-user-agreement/end-user-agreement.component.scss create mode 100644 src/app/info/end-user-agreement/end-user-agreement.component.spec.ts create mode 100644 src/app/info/end-user-agreement/end-user-agreement.component.ts create mode 100644 src/app/info/info-routing.module.ts create mode 100644 src/app/info/info.module.ts diff --git a/src/app/+bitstream-page/bitstream-page-routing.module.ts b/src/app/+bitstream-page/bitstream-page-routing.module.ts index 14d688064c..11b5349f90 100644 --- a/src/app/+bitstream-page/bitstream-page-routing.module.ts +++ b/src/app/+bitstream-page/bitstream-page-routing.module.ts @@ -3,6 +3,7 @@ import { RouterModule } from '@angular/router'; import { EditBitstreamPageComponent } from './edit-bitstream-page/edit-bitstream-page.component'; import { AuthenticatedGuard } from '../core/auth/authenticated.guard'; import { BitstreamPageResolver } from './bitstream-page.resolver'; +import { UserAgreementGuard } from '../core/user-agreement/user-agreement.guard'; const EDIT_BITSTREAM_PATH = ':id/edit'; @@ -18,7 +19,7 @@ const EDIT_BITSTREAM_PATH = ':id/edit'; resolve: { bitstream: BitstreamPageResolver }, - canActivate: [AuthenticatedGuard] + canActivate: [AuthenticatedGuard, UserAgreementGuard] } ]) ], diff --git a/src/app/+collection-page/collection-page-routing.module.ts b/src/app/+collection-page/collection-page-routing.module.ts index 1ebd9b3630..3500cf9bc8 100644 --- a/src/app/+collection-page/collection-page-routing.module.ts +++ b/src/app/+collection-page/collection-page-routing.module.ts @@ -16,6 +16,7 @@ import { CollectionBreadcrumbResolver } from '../core/breadcrumbs/collection-bre import { DSOBreadcrumbsService } from '../core/breadcrumbs/dso-breadcrumbs.service'; import { LinkService } from '../core/cache/builders/link.service'; import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver'; +import { UserAgreementGuard } from '../core/user-agreement/user-agreement.guard'; export const COLLECTION_PARENT_PARAMETER = 'parent'; @@ -41,7 +42,7 @@ const ITEMTEMPLATE_PATH = 'itemtemplate'; { path: COLLECTION_CREATE_PATH, component: CreateCollectionPageComponent, - canActivate: [AuthenticatedGuard, CreateCollectionPageGuard] + canActivate: [AuthenticatedGuard, CreateCollectionPageGuard, UserAgreementGuard] }, { path: ':id', @@ -54,18 +55,18 @@ const ITEMTEMPLATE_PATH = 'itemtemplate'; { path: COLLECTION_EDIT_PATH, loadChildren: './edit-collection-page/edit-collection-page.module#EditCollectionPageModule', - canActivate: [AuthenticatedGuard] + canActivate: [AuthenticatedGuard, UserAgreementGuard] }, { path: 'delete', pathMatch: 'full', component: DeleteCollectionPageComponent, - canActivate: [AuthenticatedGuard], + canActivate: [AuthenticatedGuard, UserAgreementGuard], }, { path: ITEMTEMPLATE_PATH, component: EditItemTemplatePageComponent, - canActivate: [AuthenticatedGuard], + canActivate: [AuthenticatedGuard, UserAgreementGuard], resolve: { item: ItemTemplatePageResolver, breadcrumb: I18nBreadcrumbResolver @@ -81,7 +82,7 @@ const ITEMTEMPLATE_PATH = 'itemtemplate'; path: '/edit/mapper', component: CollectionItemMapperComponent, pathMatch: 'full', - canActivate: [AuthenticatedGuard] + canActivate: [AuthenticatedGuard, UserAgreementGuard] } ] }, diff --git a/src/app/+community-page/community-page-routing.module.ts b/src/app/+community-page/community-page-routing.module.ts index 9922bc2c01..b5c3d9d2d6 100644 --- a/src/app/+community-page/community-page-routing.module.ts +++ b/src/app/+community-page/community-page-routing.module.ts @@ -12,6 +12,7 @@ import { getCommunityModulePath } from '../app-routing.module'; import { CommunityBreadcrumbResolver } from '../core/breadcrumbs/community-breadcrumb.resolver'; import { DSOBreadcrumbsService } from '../core/breadcrumbs/dso-breadcrumbs.service'; import { LinkService } from '../core/cache/builders/link.service'; +import { UserAgreementGuard } from '../core/user-agreement/user-agreement.guard'; export const COMMUNITY_PARENT_PARAMETER = 'parent'; @@ -36,7 +37,7 @@ const COMMUNITY_EDIT_PATH = 'edit'; { path: COMMUNITY_CREATE_PATH, component: CreateCommunityPageComponent, - canActivate: [AuthenticatedGuard, CreateCommunityPageGuard] + canActivate: [AuthenticatedGuard, CreateCommunityPageGuard, UserAgreementGuard] }, { path: ':id', @@ -49,13 +50,13 @@ const COMMUNITY_EDIT_PATH = 'edit'; { path: COMMUNITY_EDIT_PATH, loadChildren: './edit-community-page/edit-community-page.module#EditCommunityPageModule', - canActivate: [AuthenticatedGuard] + canActivate: [AuthenticatedGuard, UserAgreementGuard] }, { path: 'delete', pathMatch: 'full', component: DeleteCommunityPageComponent, - canActivate: [AuthenticatedGuard], + canActivate: [AuthenticatedGuard, UserAgreementGuard], }, { path: '', diff --git a/src/app/+item-page/item-page-routing.module.ts b/src/app/+item-page/item-page-routing.module.ts index 52faf96236..cc75a7ebc4 100644 --- a/src/app/+item-page/item-page-routing.module.ts +++ b/src/app/+item-page/item-page-routing.module.ts @@ -11,6 +11,7 @@ import { ItemBreadcrumbResolver } from '../core/breadcrumbs/item-breadcrumb.reso import { DSOBreadcrumbsService } from '../core/breadcrumbs/dso-breadcrumbs.service'; import { LinkService } from '../core/cache/builders/link.service'; import { UploadBitstreamComponent } from './bitstreams/upload/upload-bitstream.component'; +import { UserAgreementGuard } from '../core/user-agreement/user-agreement.guard'; export function getItemPageRoute(itemId: string) { return new URLCombiner(getItemModulePath(), itemId).toString(); @@ -46,12 +47,12 @@ const UPLOAD_BITSTREAM_PATH = 'bitstreams/new'; { path: ITEM_EDIT_PATH, loadChildren: './edit-item-page/edit-item-page.module#EditItemPageModule', - canActivate: [AuthenticatedGuard] + canActivate: [AuthenticatedGuard, UserAgreementGuard] }, { path: UPLOAD_BITSTREAM_PATH, component: UploadBitstreamComponent, - canActivate: [AuthenticatedGuard] + canActivate: [AuthenticatedGuard, UserAgreementGuard] } ], } diff --git a/src/app/+submit-page/submit-page-routing.module.ts b/src/app/+submit-page/submit-page-routing.module.ts index 7a123bfc31..f15d0a488c 100644 --- a/src/app/+submit-page/submit-page-routing.module.ts +++ b/src/app/+submit-page/submit-page-routing.module.ts @@ -3,12 +3,13 @@ import { RouterModule } from '@angular/router'; import { AuthenticatedGuard } from '../core/auth/authenticated.guard'; import { SubmissionSubmitComponent } from '../submission/submit/submission-submit.component'; +import { UserAgreementGuard } from '../core/user-agreement/user-agreement.guard'; @NgModule({ imports: [ RouterModule.forChild([ { - canActivate: [AuthenticatedGuard], + canActivate: [AuthenticatedGuard, UserAgreementGuard], path: '', pathMatch: 'full', component: SubmissionSubmitComponent, diff --git a/src/app/+workflowitems-edit-page/workflowitems-edit-page-routing.module.ts b/src/app/+workflowitems-edit-page/workflowitems-edit-page-routing.module.ts index e9989bf947..a8aa4f93a0 100644 --- a/src/app/+workflowitems-edit-page/workflowitems-edit-page-routing.module.ts +++ b/src/app/+workflowitems-edit-page/workflowitems-edit-page-routing.module.ts @@ -8,6 +8,7 @@ import { getWorkflowItemModulePath } from '../app-routing.module'; import { WorkflowItemDeleteComponent } from './workflow-item-delete/workflow-item-delete.component'; import { WorkflowItemPageResolver } from './workflow-item-page.resolver'; import { WorkflowItemSendBackComponent } from './workflow-item-send-back/workflow-item-send-back.component'; +import { UserAgreementGuard } from '../core/user-agreement/user-agreement.guard'; export function getWorkflowItemPageRoute(wfiId: string) { return new URLCombiner(getWorkflowItemModulePath(), wfiId).toString(); @@ -37,19 +38,19 @@ const WORKFLOW_ITEM_SEND_BACK_PATH = 'sendback'; resolve: { wfi: WorkflowItemPageResolver }, children: [ { - canActivate: [AuthenticatedGuard], + canActivate: [AuthenticatedGuard, UserAgreementGuard], path: WORKFLOW_ITEM_EDIT_PATH, component: SubmissionEditComponent, data: { title: 'submission.edit.title' } }, { - canActivate: [AuthenticatedGuard], + canActivate: [AuthenticatedGuard, UserAgreementGuard], path: WORKFLOW_ITEM_DELETE_PATH, component: WorkflowItemDeleteComponent, data: { title: 'workflow-item.delete.title' } }, { - canActivate: [AuthenticatedGuard], + canActivate: [AuthenticatedGuard, UserAgreementGuard], path: WORKFLOW_ITEM_SEND_BACK_PATH, component: WorkflowItemSendBackComponent, data: { title: 'workflow-item.send-back.title' } diff --git a/src/app/+workspaceitems-edit-page/workspaceitems-edit-page-routing.module.ts b/src/app/+workspaceitems-edit-page/workspaceitems-edit-page-routing.module.ts index d10c53e138..9dce207baf 100644 --- a/src/app/+workspaceitems-edit-page/workspaceitems-edit-page-routing.module.ts +++ b/src/app/+workspaceitems-edit-page/workspaceitems-edit-page-routing.module.ts @@ -3,13 +3,14 @@ import { RouterModule } from '@angular/router'; import { AuthenticatedGuard } from '../core/auth/authenticated.guard'; import { SubmissionEditComponent } from '../submission/edit/submission-edit.component'; +import { UserAgreementGuard } from '../core/user-agreement/user-agreement.guard'; @NgModule({ imports: [ RouterModule.forChild([ { path: '', redirectTo: '/home', pathMatch: 'full' }, { - canActivate: [AuthenticatedGuard], + canActivate: [AuthenticatedGuard, UserAgreementGuard], path: ':id/edit', component: SubmissionEditComponent, data: { title: 'submission.edit.title' } diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index c8ee6ecd8b..de077a5b3a 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -12,6 +12,7 @@ import { getItemPageRoute } from './+item-page/item-page-routing.module'; import { getCollectionPageRoute } from './+collection-page/collection-page-routing.module'; import { SiteAdministratorGuard } from './core/data/feature-authorization/feature-authorization-guard/site-administrator.guard'; import { UnauthorizedComponent } from './unauthorized/unauthorized.component'; +import { UserAgreementGuard } from './core/user-agreement/user-agreement.guard'; const ITEM_MODULE_PATH = 'items'; @@ -84,6 +85,12 @@ export function getUnauthorizedPath() { return `/${UNAUTHORIZED_PATH}`; } +const INFO_MODULE_PATH = 'info'; + +export function getInfoModulePath() { + return `/${INFO_MODULE_PATH}`; +} + @NgModule({ imports: [ RouterModule.forRoot([ @@ -102,11 +109,11 @@ export function getUnauthorizedPath() { { path: 'mydspace', loadChildren: './+my-dspace-page/my-dspace-page.module#MyDSpacePageModule', - canActivate: [AuthenticatedGuard] + canActivate: [AuthenticatedGuard, UserAgreementGuard] }, { path: 'search', loadChildren: './+search-page/search-page-routing.module#SearchPageRoutingModule' }, { path: 'browse', loadChildren: './+browse-by/browse-by.module#BrowseByModule'}, - { path: ADMIN_MODULE_PATH, loadChildren: './+admin/admin.module#AdminModule', canActivate: [SiteAdministratorGuard] }, + { path: ADMIN_MODULE_PATH, loadChildren: './+admin/admin.module#AdminModule', canActivate: [SiteAdministratorGuard, UserAgreementGuard] }, { path: 'login', loadChildren: './+login-page/login-page.module#LoginPageModule' }, { path: 'logout', loadChildren: './+logout-page/logout-page.module#LogoutPageModule' }, { path: 'submit', loadChildren: './+submit-page/submit-page.module#SubmitPageModule' }, @@ -120,9 +127,10 @@ export function getUnauthorizedPath() { }, { path: PROFILE_MODULE_PATH, - loadChildren: './profile-page/profile-page.module#ProfilePageModule', canActivate: [AuthenticatedGuard] + loadChildren: './profile-page/profile-page.module#ProfilePageModule', canActivate: [AuthenticatedGuard, UserAgreementGuard] }, - { path: 'processes', loadChildren: './process-page/process-page.module#ProcessPageModule', canActivate: [AuthenticatedGuard] }, + { path: 'processes', loadChildren: './process-page/process-page.module#ProcessPageModule', canActivate: [AuthenticatedGuard, UserAgreementGuard] }, + { path: INFO_MODULE_PATH, loadChildren: './info/info.module#InfoModule' }, { path: UNAUTHORIZED_PATH, component: UnauthorizedComponent }, { path: '**', pathMatch: 'full', component: PageNotFoundComponent }, ], diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index 5aa462d5e0..08101260dc 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -162,6 +162,7 @@ import { SubmissionCcLicenceUrl } from './submission/models/submission-cc-licens import { SubmissionCcLicenseUrlDataService } from './submission/submission-cc-license-url-data.service'; import { ConfigurationDataService } from './data/configuration-data.service'; import { ConfigurationProperty } from './shared/configuration-property.model'; +import { UserAgreementGuard } from './user-agreement/user-agreement.guard'; /** * When not in production, endpoint responses can be mocked for testing purposes @@ -289,6 +290,7 @@ const PROVIDERS = [ MetadataSchemaDataService, MetadataFieldDataService, TokenResponseParsingService, + UserAgreementGuard, // register AuthInterceptor as HttpInterceptor { provide: HTTP_INTERCEPTORS, diff --git a/src/app/core/shared/operators.ts b/src/app/core/shared/operators.ts index 7516cc6532..95eb822017 100644 --- a/src/app/core/shared/operators.ts +++ b/src/app/core/shared/operators.ts @@ -12,6 +12,7 @@ import { RequestService } from '../data/request.service'; import { BrowseDefinition } from './browse-definition.model'; import { DSpaceObject } from './dspace-object.model'; import { getUnauthorizedPath } from '../../app-routing.module'; +import { getEndUserAgreementPath } from '../../info/info-routing.module'; /** * This file contains custom RxJS operators that can be used in multiple places @@ -192,6 +193,17 @@ export const returnUnauthorizedUrlTreeOnFalse = (router: Router) => return authorized ? authorized : router.parseUrl(getUnauthorizedPath()) })); +/** + * Operator that returns a UrlTree to the unauthorized page when the boolean received is false + * @param router + */ +export const returnEndUserAgreementUrlTreeOnFalse = (router: Router) => + (source: Observable): Observable => + source.pipe( + map((hasAgreed: boolean) => { + return hasAgreed ? hasAgreed : router.parseUrl(getEndUserAgreementPath()) + })); + export const getFinishedRemoteData = () => (source: Observable>): Observable> => source.pipe(find((rd: RemoteData) => !rd.isLoading)); diff --git a/src/app/core/user-agreement/user-agreement.guard.ts b/src/app/core/user-agreement/user-agreement.guard.ts new file mode 100644 index 0000000000..d955c300a1 --- /dev/null +++ b/src/app/core/user-agreement/user-agreement.guard.ts @@ -0,0 +1,37 @@ +import { Injectable } from '@angular/core'; +import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot, UrlTree } from '@angular/router'; +import { Observable } from 'rxjs/internal/Observable'; +import { CookieService } from '../services/cookie.service'; +import { AuthService } from '../auth/auth.service'; +import { map } from 'rxjs/operators'; +import { hasValue } from '../../shared/empty.util'; +import { returnEndUserAgreementUrlTreeOnFalse } from '../shared/operators'; + +export const USER_AGREEMENT_COOKIE = 'hasAgreedEndUser'; +export const USER_AGREEMENT_METADATA_FIELD = 'dspace.agreements.end-user'; + +/** + * A guard redirecting users to the end agreement page when they haven't accepted the latest user agreement + */ +@Injectable() +export class UserAgreementGuard implements CanActivate { + + constructor(protected cookie: CookieService, + protected authService: AuthService, + protected router: Router) { + } + + /** + * True when the user has accepted the agreements + */ + canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable | boolean { + if (this.cookie.get(USER_AGREEMENT_COOKIE) === true) { + return true; + } else { + return this.authService.getAuthenticatedUserFromStore().pipe( + map((user) => hasValue(user) && user.hasMetadata(USER_AGREEMENT_METADATA_FIELD) && user.firstMetadata(USER_AGREEMENT_METADATA_FIELD).value === 'true'), + returnEndUserAgreementUrlTreeOnFalse(this.router) + ); + } + } +} diff --git a/src/app/info/end-user-agreement/end-user-agreement-content/end-user-agreement-content.component.html b/src/app/info/end-user-agreement/end-user-agreement-content/end-user-agreement-content.component.html new file mode 100644 index 0000000000..1ee8712444 --- /dev/null +++ b/src/app/info/end-user-agreement/end-user-agreement-content/end-user-agreement-content.component.html @@ -0,0 +1,37 @@ +

{{ 'info.end-user-agreement.head' | translate }}

+

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Nunc sed velit dignissim sodales ut eu. In ante metus dictum at tempor. Diam phasellus vestibulum lorem sed risus. Sed cras ornare arcu dui vivamus. Sit amet consectetur adipiscing elit pellentesque. Id velit ut tortor pretium viverra suspendisse potenti. Sed euismod nisi porta lorem mollis aliquam ut. Justo laoreet sit amet cursus sit amet dictum sit. Ullamcorper morbi tincidunt ornare massa eget egestas. +

+

+ In iaculis nunc sed augue lacus. Curabitur vitae nunc sed velit dignissim sodales ut eu sem. Tellus id interdum velit laoreet id donec ultrices tincidunt arcu. Quis vel eros donec ac odio tempor. Viverra accumsan in nisl nisi scelerisque eu ultrices vitae. Varius quam quisque id diam vel quam. Nisl tincidunt eget nullam non nisi est sit. Nunc aliquet bibendum enim facilisis. Aenean sed adipiscing diam donec adipiscing. Convallis tellus id interdum velit laoreet. Massa placerat duis ultricies lacus sed turpis tincidunt. Sed cras ornare arcu dui vivamus arcu. Egestas integer eget aliquet nibh praesent tristique. Sit amet purus gravida quis blandit turpis cursus in hac. Porta non pulvinar neque laoreet suspendisse. Quis risus sed vulputate odio ut. Dignissim enim sit amet venenatis urna cursus. +

+

+ Interdum velit laoreet id donec ultrices tincidunt arcu non sodales. Massa sapien faucibus et molestie. Dictumst vestibulum rhoncus est pellentesque elit ullamcorper. Metus dictum at tempor commodo ullamcorper. Tincidunt lobortis feugiat vivamus at augue eget. Non diam phasellus vestibulum lorem sed risus ultricies. Neque aliquam vestibulum morbi blandit cursus risus at ultrices mi. Euismod lacinia at quis risus sed. Lorem mollis aliquam ut porttitor leo a diam. Ipsum dolor sit amet consectetur. Ante in nibh mauris cursus mattis molestie a iaculis at. Commodo ullamcorper a lacus vestibulum. Pellentesque elit eget gravida cum sociis. Sit amet commodo nulla facilisi nullam vehicula. Vehicula ipsum a arcu cursus vitae congue mauris rhoncus aenean. +

+

+ Ac turpis egestas maecenas pharetra convallis. Lacus sed viverra tellus in. Nullam eget felis eget nunc lobortis mattis aliquam faucibus purus. Id aliquet risus feugiat in ante metus dictum at. Quis enim lobortis scelerisque fermentum dui faucibus. Eu volutpat odio facilisis mauris sit amet massa vitae tortor. Tellus elementum sagittis vitae et leo. Cras sed felis eget velit aliquet sagittis. Proin fermentum leo vel orci porta non pulvinar neque laoreet. Dui sapien eget mi proin sed libero enim. Ultrices mi tempus imperdiet nulla malesuada. Mattis molestie a iaculis at. Turpis massa sed elementum tempus egestas. +

+

+ Dui faucibus in ornare quam viverra orci sagittis eu volutpat. Cras adipiscing enim eu turpis. Ac felis donec et odio pellentesque. Iaculis nunc sed augue lacus viverra vitae congue eu consequat. Posuere lorem ipsum dolor sit amet consectetur adipiscing elit duis. Elit eget gravida cum sociis natoque penatibus. Id faucibus nisl tincidunt eget nullam non. Sagittis aliquam malesuada bibendum arcu vitae. Fermentum leo vel orci porta. Aliquam ultrices sagittis orci a scelerisque purus semper. Diam maecenas sed enim ut sem viverra aliquet eget sit. Et ultrices neque ornare aenean euismod. Eu mi bibendum neque egestas congue quisque egestas diam. Eget lorem dolor sed viverra. Ut lectus arcu bibendum at. Rutrum tellus pellentesque eu tincidunt tortor. Vitae congue eu consequat ac. Elit ullamcorper dignissim cras tincidunt. Sit amet volutpat consequat mauris nunc congue nisi. +

+

+ Cursus in hac habitasse platea dictumst quisque sagittis purus. Placerat duis ultricies lacus sed turpis tincidunt. In egestas erat imperdiet sed euismod nisi porta lorem mollis. Non nisi est sit amet facilisis magna. In massa tempor nec feugiat nisl pretium fusce. Pulvinar neque laoreet suspendisse interdum consectetur. Ullamcorper morbi tincidunt ornare massa eget egestas purus viverra accumsan. Fringilla urna porttitor rhoncus dolor purus non enim. Mauris nunc congue nisi vitae suscipit. Commodo elit at imperdiet dui accumsan sit amet nulla. Tempor id eu nisl nunc mi ipsum faucibus. Porta non pulvinar neque laoreet suspendisse. Nec nam aliquam sem et tortor consequat. +

+

+ Eget nunc lobortis mattis aliquam faucibus purus. Odio tempor orci dapibus ultrices. Sed nisi lacus sed viverra tellus. Elit ullamcorper dignissim cras tincidunt. Porttitor rhoncus dolor purus non enim praesent elementum facilisis. Viverra orci sagittis eu volutpat odio. Pharetra massa massa ultricies mi quis. Lectus vestibulum mattis ullamcorper velit sed ullamcorper. Pulvinar neque laoreet suspendisse interdum consectetur. Vitae auctor eu augue ut. Arcu dictum varius duis at consectetur lorem donec. Massa sed elementum tempus egestas sed sed. Risus viverra adipiscing at in tellus integer. Vulputate enim nulla aliquet porttitor lacus luctus accumsan. Pharetra massa massa ultricies mi. Elementum eu facilisis sed odio morbi quis commodo odio. Tincidunt lobortis feugiat vivamus at. Felis donec et odio pellentesque diam volutpat commodo sed. Risus feugiat in ante metus dictum at tempor commodo ullamcorper. Fringilla phasellus faucibus scelerisque eleifend donec pretium vulputate. +

+

+ Lectus proin nibh nisl condimentum id venenatis a condimentum. Id consectetur purus ut faucibus pulvinar elementum integer enim. Non pulvinar neque laoreet suspendisse interdum consectetur. Est pellentesque elit ullamcorper dignissim cras tincidunt lobortis feugiat vivamus. Suscipit tellus mauris a diam maecenas sed enim ut sem. Dolor purus non enim praesent elementum facilisis. Non enim praesent elementum facilisis leo vel. Ultricies leo integer malesuada nunc vel risus commodo viverra maecenas. Nulla porttitor massa id neque aliquam vestibulum. Erat velit scelerisque in dictum non consectetur. Amet cursus sit amet dictum. Nec tincidunt praesent semper feugiat nibh. Rutrum quisque non tellus orci ac auctor. Sagittis aliquam malesuada bibendum arcu vitae elementum. Massa tincidunt dui ut ornare lectus sit amet est. Aliquet porttitor lacus luctus accumsan tortor posuere ac. Quis hendrerit dolor magna eget est lorem ipsum dolor sit. Lectus mauris ultrices eros in. +

+

+ Massa massa ultricies mi quis hendrerit dolor magna. Est ullamcorper eget nulla facilisi etiam dignissim diam. Vulputate sapien nec sagittis aliquam malesuada. Nisi porta lorem mollis aliquam ut porttitor leo a diam. Tempus quam pellentesque nec nam. Faucibus vitae aliquet nec ullamcorper sit amet risus nullam eget. Gravida arcu ac tortor dignissim convallis aenean et tortor. A scelerisque purus semper eget duis at tellus at. Viverra ipsum nunc aliquet bibendum enim. Semper feugiat nibh sed pulvinar proin gravida hendrerit. Et ultrices neque ornare aenean euismod. Consequat semper viverra nam libero justo laoreet. Nunc mattis enim ut tellus elementum sagittis. Consectetur lorem donec massa sapien faucibus et. Vel risus commodo viverra maecenas accumsan lacus vel facilisis. Diam sollicitudin tempor id eu nisl nunc. Dolor magna eget est lorem ipsum dolor. Adipiscing elit pellentesque habitant morbi tristique. +

+

+ Nec sagittis aliquam malesuada bibendum arcu vitae elementum curabitur. Egestas fringilla phasellus faucibus scelerisque eleifend donec pretium vulputate sapien. Porttitor leo a diam sollicitudin tempor. Pellentesque dignissim enim sit amet venenatis urna cursus eget nunc. Posuere sollicitudin aliquam ultrices sagittis orci a scelerisque. Vehicula ipsum a arcu cursus vitae congue mauris rhoncus. Leo urna molestie at elementum. Duis tristique sollicitudin nibh sit amet commodo nulla facilisi. Libero id faucibus nisl tincidunt eget nullam. Tellus elementum sagittis vitae et leo duis ut diam. Sodales ut etiam sit amet nisl purus in mollis. Ipsum faucibus vitae aliquet nec ullamcorper sit amet risus. Lacus laoreet non curabitur gravida arcu ac tortor dignissim convallis. Aliquam malesuada bibendum arcu vitae elementum. Leo vel orci porta non pulvinar neque laoreet. Ipsum suspendisse ultrices gravida dictum fusce. +

+

+ Egestas erat imperdiet sed euismod nisi porta lorem. Venenatis a condimentum vitae sapien pellentesque habitant. Sit amet luctus venenatis lectus magna fringilla urna porttitor. Orci sagittis eu volutpat odio facilisis mauris sit amet massa. Ut enim blandit volutpat maecenas volutpat blandit aliquam. Libero volutpat sed cras ornare. Molestie ac feugiat sed lectus vestibulum mattis ullamcorper velit sed. Diam quis enim lobortis scelerisque fermentum dui. Pellentesque habitant morbi tristique senectus et netus. Auctor urna nunc id cursus metus aliquam eleifend. Elit scelerisque mauris pellentesque pulvinar pellentesque habitant morbi tristique. Sed risus ultricies tristique nulla aliquet enim tortor. Tincidunt arcu non sodales neque sodales ut. Sed lectus vestibulum mattis ullamcorper velit sed ullamcorper morbi tincidunt. +

+

+ Pulvinar etiam non quam lacus suspendisse faucibus. Eu mi bibendum neque egestas congue. Egestas purus viverra accumsan in nisl nisi scelerisque eu. Vulputate enim nulla aliquet porttitor lacus luctus accumsan. Eu non diam phasellus vestibulum. Semper feugiat nibh sed pulvinar. Ante in nibh mauris cursus mattis molestie a. Maecenas accumsan lacus vel facilisis volutpat. Non quam lacus suspendisse faucibus. Quis commodo odio aenean sed adipiscing. Vel elit scelerisque mauris pellentesque pulvinar pellentesque habitant morbi. Sed cras ornare arcu dui vivamus arcu felis. Tortor vitae purus faucibus ornare suspendisse sed. Morbi tincidunt ornare massa eget egestas purus viverra. Nibh cras pulvinar mattis nunc. Luctus venenatis lectus magna fringilla urna porttitor. Enim blandit volutpat maecenas volutpat blandit aliquam etiam erat. Malesuada pellentesque elit eget gravida cum sociis natoque penatibus et. Felis eget nunc lobortis mattis aliquam faucibus purus in. Vivamus arcu felis bibendum ut. +

diff --git a/src/app/info/end-user-agreement/end-user-agreement-content/end-user-agreement-content.component.scss b/src/app/info/end-user-agreement/end-user-agreement-content/end-user-agreement-content.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/info/end-user-agreement/end-user-agreement-content/end-user-agreement-content.component.spec.ts b/src/app/info/end-user-agreement/end-user-agreement-content/end-user-agreement-content.component.spec.ts new file mode 100644 index 0000000000..c95e60846e --- /dev/null +++ b/src/app/info/end-user-agreement/end-user-agreement-content/end-user-agreement-content.component.spec.ts @@ -0,0 +1,24 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { EndUserAgreementContentComponent } from './end-user-agreement-content.component'; + +describe('EndUserAgreementContentComponent', () => { + let component: EndUserAgreementContentComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ EndUserAgreementContentComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(EndUserAgreementContentComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/info/end-user-agreement/end-user-agreement-content/end-user-agreement-content.component.ts b/src/app/info/end-user-agreement/end-user-agreement-content/end-user-agreement-content.component.ts new file mode 100644 index 0000000000..cbfc706229 --- /dev/null +++ b/src/app/info/end-user-agreement/end-user-agreement-content/end-user-agreement-content.component.ts @@ -0,0 +1,9 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'ds-end-user-agreement-content', + templateUrl: './end-user-agreement-content.component.html', + styleUrls: ['./end-user-agreement-content.component.scss'] +}) +export class EndUserAgreementContentComponent { +} diff --git a/src/app/info/end-user-agreement/end-user-agreement.component.html b/src/app/info/end-user-agreement/end-user-agreement.component.html new file mode 100644 index 0000000000..cc155ee9c3 --- /dev/null +++ b/src/app/info/end-user-agreement/end-user-agreement.component.html @@ -0,0 +1,3 @@ +
+ +
diff --git a/src/app/info/end-user-agreement/end-user-agreement.component.scss b/src/app/info/end-user-agreement/end-user-agreement.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/info/end-user-agreement/end-user-agreement.component.spec.ts b/src/app/info/end-user-agreement/end-user-agreement.component.spec.ts new file mode 100644 index 0000000000..6b87a0fccb --- /dev/null +++ b/src/app/info/end-user-agreement/end-user-agreement.component.spec.ts @@ -0,0 +1,24 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { EndUserAgreementComponent } from './end-user-agreement.component'; + +describe('EndUserAgreementComponent', () => { + let component: EndUserAgreementComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ EndUserAgreementComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(EndUserAgreementComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/info/end-user-agreement/end-user-agreement.component.ts b/src/app/info/end-user-agreement/end-user-agreement.component.ts new file mode 100644 index 0000000000..3e43d68784 --- /dev/null +++ b/src/app/info/end-user-agreement/end-user-agreement.component.ts @@ -0,0 +1,9 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'ds-end-user-agreement', + templateUrl: './end-user-agreement.component.html', + styleUrls: ['./end-user-agreement.component.scss'] +}) +export class EndUserAgreementComponent { +} diff --git a/src/app/info/info-routing.module.ts b/src/app/info/info-routing.module.ts new file mode 100644 index 0000000000..bfb8b77d96 --- /dev/null +++ b/src/app/info/info-routing.module.ts @@ -0,0 +1,33 @@ +import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; +import { EndUserAgreementComponent } from './end-user-agreement/end-user-agreement.component'; +import { getInfoModulePath } from '../app-routing.module'; +import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver'; + +const END_USER_AGREEMENT_PATH = 'end-user-agreement'; + +export function getEndUserAgreementPath() { + return getSubPath(END_USER_AGREEMENT_PATH); +} + +function getSubPath(path: string) { + return `${getInfoModulePath()}/${path}`; +} + +@NgModule({ + imports: [ + RouterModule.forChild([ + { + path: END_USER_AGREEMENT_PATH, + component: EndUserAgreementComponent, + resolve: { breadcrumb: I18nBreadcrumbResolver }, + data: { title: 'info.end-user-agreement.title', breadcrumbKey: 'info.end-user-agreement' } + } + ]) + ] +}) +/** + * Module for navigating to components within the info module + */ +export class InfoRoutingModule { +} diff --git a/src/app/info/info.module.ts b/src/app/info/info.module.ts new file mode 100644 index 0000000000..93923cffe3 --- /dev/null +++ b/src/app/info/info.module.ts @@ -0,0 +1,20 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { SharedModule } from '../shared/shared.module'; +import { EndUserAgreementComponent } from './end-user-agreement/end-user-agreement.component'; +import { InfoRoutingModule } from './info-routing.module'; +import { EndUserAgreementContentComponent } from './end-user-agreement/end-user-agreement-content/end-user-agreement-content.component'; + +@NgModule({ + imports: [ + CommonModule, + SharedModule, + InfoRoutingModule + ], + declarations: [ + EndUserAgreementComponent, + EndUserAgreementContentComponent + ] +}) +export class InfoModule { +} diff --git a/src/app/process-page/process-page-routing.module.ts b/src/app/process-page/process-page-routing.module.ts index 881ca0e853..92d55467bc 100644 --- a/src/app/process-page/process-page-routing.module.ts +++ b/src/app/process-page/process-page-routing.module.ts @@ -7,6 +7,7 @@ import { ProcessDetailComponent } from './detail/process-detail.component'; import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver'; import { ProcessBreadcrumbResolver } from './process-breadcrumb.resolver'; import { AuthenticatedGuard } from '../core/auth/authenticated.guard'; +import { UserAgreementGuard } from '../core/user-agreement/user-agreement.guard'; @NgModule({ imports: [ @@ -15,7 +16,7 @@ import { AuthenticatedGuard } from '../core/auth/authenticated.guard'; path: '', resolve: { breadcrumb: I18nBreadcrumbResolver }, data: { breadcrumbKey: 'process.overview' }, - canActivate: [AuthenticatedGuard], + canActivate: [AuthenticatedGuard, UserAgreementGuard], children: [ { path: '', diff --git a/src/app/register-page/register-page-routing.module.ts b/src/app/register-page/register-page-routing.module.ts index c7cceeaaf4..c199b51c6c 100644 --- a/src/app/register-page/register-page-routing.module.ts +++ b/src/app/register-page/register-page-routing.module.ts @@ -4,6 +4,7 @@ import { RegisterEmailComponent } from './register-email/register-email.componen import { CreateProfileComponent } from './create-profile/create-profile.component'; import { ItemPageResolver } from '../+item-page/item-page.resolver'; import { RegistrationResolver } from '../register-email-form/registration.resolver'; +import { UserAgreementGuard } from '../core/user-agreement/user-agreement.guard'; @NgModule({ imports: [ @@ -16,7 +17,8 @@ import { RegistrationResolver } from '../register-email-form/registration.resolv { path: ':token', component: CreateProfileComponent, - resolve: {registration: RegistrationResolver} + resolve: {registration: RegistrationResolver}, + canActivate: [UserAgreementGuard] } ]) ], diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index b9c03eafc8..510163c6c3 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -1138,6 +1138,14 @@ + "info.end-user-agreement.breadcrumbs": "End User Agreement", + + "info.end-user-agreement.head": "End User Agreement", + + "info.end-user-agreement.title": "End User Agreement", + + + "item.edit.authorizations.heading": "With this editor you can view and alter the policies of an item, plus alter policies of individual item components: bundles and bitstreams. Briefly, an item is a container of bundles, and bundles are containers of bitstreams. Containers usually have ADD/REMOVE/READ/WRITE policies, while bitstreams only have READ/WRITE policies.", "item.edit.authorizations.title": "Edit item's Policies", From d46355e2742641e168706214259c8a0e69c9f8a0 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Thu, 20 Aug 2020 17:28:25 +0200 Subject: [PATCH 02/12] 72541: Accepting the End User Agreement + UserAgreementService --- src/app/core/core.module.ts | 2 + .../user-agreement/user-agreement.guard.ts | 33 ++++---- .../user-agreement/user-agreement.service.ts | 76 ++++++++++++++++++ .../end-user-agreement-content.component.ts | 3 + .../end-user-agreement.component.html | 10 +++ .../end-user-agreement.component.scss | 8 ++ .../end-user-agreement.component.ts | 80 ++++++++++++++++++- src/assets/i18n/en.json5 | 10 +++ 8 files changed, 202 insertions(+), 20 deletions(-) create mode 100644 src/app/core/user-agreement/user-agreement.service.ts diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index 08101260dc..f1c4ebd121 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -163,6 +163,7 @@ import { SubmissionCcLicenseUrlDataService } from './submission/submission-cc-li import { ConfigurationDataService } from './data/configuration-data.service'; import { ConfigurationProperty } from './shared/configuration-property.model'; import { UserAgreementGuard } from './user-agreement/user-agreement.guard'; +import { UserAgreementService } from './user-agreement/user-agreement.service'; /** * When not in production, endpoint responses can be mocked for testing purposes @@ -291,6 +292,7 @@ const PROVIDERS = [ MetadataFieldDataService, TokenResponseParsingService, UserAgreementGuard, + UserAgreementService, // register AuthInterceptor as HttpInterceptor { provide: HTTP_INTERCEPTORS, diff --git a/src/app/core/user-agreement/user-agreement.guard.ts b/src/app/core/user-agreement/user-agreement.guard.ts index d955c300a1..7464b87e00 100644 --- a/src/app/core/user-agreement/user-agreement.guard.ts +++ b/src/app/core/user-agreement/user-agreement.guard.ts @@ -1,14 +1,9 @@ import { Injectable } from '@angular/core'; import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot, UrlTree } from '@angular/router'; import { Observable } from 'rxjs/internal/Observable'; -import { CookieService } from '../services/cookie.service'; -import { AuthService } from '../auth/auth.service'; -import { map } from 'rxjs/operators'; -import { hasValue } from '../../shared/empty.util'; import { returnEndUserAgreementUrlTreeOnFalse } from '../shared/operators'; - -export const USER_AGREEMENT_COOKIE = 'hasAgreedEndUser'; -export const USER_AGREEMENT_METADATA_FIELD = 'dspace.agreements.end-user'; +import { UserAgreementService } from './user-agreement.service'; +import { tap } from 'rxjs/operators'; /** * A guard redirecting users to the end agreement page when they haven't accepted the latest user agreement @@ -16,22 +11,24 @@ export const USER_AGREEMENT_METADATA_FIELD = 'dspace.agreements.end-user'; @Injectable() export class UserAgreementGuard implements CanActivate { - constructor(protected cookie: CookieService, - protected authService: AuthService, + constructor(protected userAgreementService: UserAgreementService, protected router: Router) { } /** * True when the user has accepted the agreements + * The user will be redirected to the End User Agreement page if they haven't accepted it before + * A redirect URL will be provided with the navigation so the component can redirect the user back to the blocked route + * when they're finished accepting the agreement */ - canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable | boolean { - if (this.cookie.get(USER_AGREEMENT_COOKIE) === true) { - return true; - } else { - return this.authService.getAuthenticatedUserFromStore().pipe( - map((user) => hasValue(user) && user.hasMetadata(USER_AGREEMENT_METADATA_FIELD) && user.firstMetadata(USER_AGREEMENT_METADATA_FIELD).value === 'true'), - returnEndUserAgreementUrlTreeOnFalse(this.router) - ); - } + canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { + return this.userAgreementService.hasCurrentUserAcceptedAgreement().pipe( + returnEndUserAgreementUrlTreeOnFalse(this.router), + tap((result) => { + if (result instanceof UrlTree) { + this.router.navigateByUrl(result, { state: { redirect: state.url } }) + } + }) + ); } } diff --git a/src/app/core/user-agreement/user-agreement.service.ts b/src/app/core/user-agreement/user-agreement.service.ts new file mode 100644 index 0000000000..8e5694d81c --- /dev/null +++ b/src/app/core/user-agreement/user-agreement.service.ts @@ -0,0 +1,76 @@ +import { Injectable } from '@angular/core'; +import { AuthService } from '../auth/auth.service'; +import { CookieService } from '../services/cookie.service'; +import { Observable } from 'rxjs/internal/Observable'; +import { of as observableOf } from 'rxjs'; +import { map, switchMap, take } from 'rxjs/operators'; +import { hasValue } from '../../shared/empty.util'; +import { cloneDeep } from 'lodash'; +import { Metadata } from '../shared/metadata.utils'; +import { EPersonDataService } from '../eperson/eperson-data.service'; +import { getSucceededRemoteData } from '../shared/operators'; + +export const USER_AGREEMENT_COOKIE = 'hasAgreedEndUser'; +export const USER_AGREEMENT_METADATA_FIELD = 'dspace.agreements.end-user'; + +/** + * Service for checking and managing the status of the current end user agreement + */ +@Injectable() +export class UserAgreementService { + + constructor(protected cookie: CookieService, + protected authService: AuthService, + protected ePersonService: EPersonDataService) { + } + + /** + * Whether or not the current user has accepted the End User Agreement + */ + hasCurrentUserAcceptedAgreement(): Observable { + if (this.cookie.get(USER_AGREEMENT_COOKIE) === true) { + return observableOf(true); + } else { + return this.authService.isAuthenticated().pipe( + switchMap((authenticated) => { + if (authenticated) { + return this.authService.getAuthenticatedUserFromStore().pipe( + map((user) => hasValue(user) && user.hasMetadata(USER_AGREEMENT_METADATA_FIELD) && user.firstMetadata(USER_AGREEMENT_METADATA_FIELD).value === 'true') + ); + } else { + return observableOf(false); + } + }) + ); + } + } + + /** + * Set the current user's accepted agreement status + * When a user is authenticated, set his/her metadata to the provided value + * When no user is authenticated, set the cookie to the provided value + * @param accepted + */ + setUserAcceptedAgreement(accepted: boolean): Observable { + return this.authService.isAuthenticated().pipe( + switchMap((authenticated) => { + if (authenticated) { + return this.authService.getAuthenticatedUserFromStore().pipe( + switchMap((user) => { + const updatedUser = cloneDeep(user); + Metadata.setFirstValue(updatedUser.metadata, USER_AGREEMENT_METADATA_FIELD, String(accepted)); + return this.ePersonService.update(updatedUser); + }), + getSucceededRemoteData(), + map((rd) => hasValue(rd.payload)) + ); + } else { + this.cookie.set(USER_AGREEMENT_COOKIE, accepted); + return observableOf(true); + } + }), + take(1) + ); + } + +} diff --git a/src/app/info/end-user-agreement/end-user-agreement-content/end-user-agreement-content.component.ts b/src/app/info/end-user-agreement/end-user-agreement-content/end-user-agreement-content.component.ts index cbfc706229..faa7d5a78f 100644 --- a/src/app/info/end-user-agreement/end-user-agreement-content/end-user-agreement-content.component.ts +++ b/src/app/info/end-user-agreement/end-user-agreement-content/end-user-agreement-content.component.ts @@ -5,5 +5,8 @@ import { Component } from '@angular/core'; templateUrl: './end-user-agreement-content.component.html', styleUrls: ['./end-user-agreement-content.component.scss'] }) +/** + * Component displaying the contents of the End User Agreement + */ export class EndUserAgreementContentComponent { } diff --git a/src/app/info/end-user-agreement/end-user-agreement.component.html b/src/app/info/end-user-agreement/end-user-agreement.component.html index cc155ee9c3..624264e2cc 100644 --- a/src/app/info/end-user-agreement/end-user-agreement.component.html +++ b/src/app/info/end-user-agreement/end-user-agreement.component.html @@ -1,3 +1,13 @@
+ +
+ + + +
+ + +
+
diff --git a/src/app/info/end-user-agreement/end-user-agreement.component.scss b/src/app/info/end-user-agreement/end-user-agreement.component.scss index e69de29bb2..2960a0fac1 100644 --- a/src/app/info/end-user-agreement/end-user-agreement.component.scss +++ b/src/app/info/end-user-agreement/end-user-agreement.component.scss @@ -0,0 +1,8 @@ +input#user-agreement-accept { + /* Large-sized Checkboxes */ + -ms-transform: scale(1.6); /* IE */ + -moz-transform: scale(1.6); /* FF */ + -webkit-transform: scale(1.6); /* Safari and Chrome */ + -o-transform: scale(1.6); /* Opera */ + padding: 10px; +} diff --git a/src/app/info/end-user-agreement/end-user-agreement.component.ts b/src/app/info/end-user-agreement/end-user-agreement.component.ts index 3e43d68784..5da59bf5c4 100644 --- a/src/app/info/end-user-agreement/end-user-agreement.component.ts +++ b/src/app/info/end-user-agreement/end-user-agreement.component.ts @@ -1,9 +1,85 @@ -import { Component } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; +import { AuthService } from '../../core/auth/auth.service'; +import { take } from 'rxjs/operators'; +import { Router } from '@angular/router'; +import { Store } from '@ngrx/store'; +import { AppState } from '../../app.reducer'; +import { LogOutAction } from '../../core/auth/auth.actions'; +import { UserAgreementService } from '../../core/user-agreement/user-agreement.service'; +import { NotificationsService } from '../../shared/notifications/notifications.service'; +import { TranslateService } from '@ngx-translate/core'; +import { hasValue } from '../../shared/empty.util'; @Component({ selector: 'ds-end-user-agreement', templateUrl: './end-user-agreement.component.html', styleUrls: ['./end-user-agreement.component.scss'] }) -export class EndUserAgreementComponent { +/** + * Component displaying the End User Agreement and an option to accept it + */ +export class EndUserAgreementComponent implements OnInit { + + /** + * Whether or not the user agreement has been accepted + */ + accepted = false; + + constructor(protected userAgreementService: UserAgreementService, + protected notificationsService: NotificationsService, + protected translate: TranslateService, + protected authService: AuthService, + protected store: Store, + protected router: Router) { + } + + /** + * Initialize the component + */ + ngOnInit(): void { + this.initAccepted(); + } + + /** + * Initialize the "accepted" property of this component by checking if the current user has accepted it before + */ + initAccepted() { + this.userAgreementService.hasCurrentUserAcceptedAgreement().subscribe((accepted) => { + this.accepted = accepted; + }); + } + + /** + * Submit the form + * Set the End User Agreement, display a notification and (optionally) redirect the user back to their original destination + */ + submit() { + this.userAgreementService.setUserAcceptedAgreement(this.accepted).subscribe((success) => { + if (success) { + this.notificationsService.success(this.translate.instant('info.end-user-agreement.accept.success')); + const redirect = window.history.state.redirect; + if (hasValue(redirect)) { + this.router.navigateByUrl(redirect); + } + } else { + this.notificationsService.error(this.translate.instant('info.end-user-agreement.accept.error')); + } + }); + } + + /** + * Cancel the agreement + * If the user is logged in, this will log him/her out + * If the user is not logged in, they will be redirected to the homepage + */ + cancel() { + this.authService.isAuthenticated().pipe(take(1)).subscribe((authenticated) => { + if (authenticated) { + this.store.dispatch(new LogOutAction()); + } else { + this.router.navigate(['home']); + } + }); + } + } diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 510163c6c3..ca82aca80b 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -1138,8 +1138,18 @@ + "info.end-user-agreement.accept": "I have read and I agree to the End User Agreement", + + "info.end-user-agreement.accept.error": "An error occurred accepting the End User Agreement", + + "info.end-user-agreement.accept.success": "Successfully updated the End User Agreement", + "info.end-user-agreement.breadcrumbs": "End User Agreement", + "info.end-user-agreement.buttons.cancel": "Cancel", + + "info.end-user-agreement.buttons.save": "Save", + "info.end-user-agreement.head": "End User Agreement", "info.end-user-agreement.title": "End User Agreement", From ecf75efe99d8a9b2d16e8c90bfb5bb7f4f7752d4 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Fri, 21 Aug 2020 11:22:02 +0200 Subject: [PATCH 03/12] 72541: Renamed end-user-agreement-guard and -service; Add metadata on registry; privacy statement component --- .../bitstream-page-routing.module.ts | 4 +- .../collection-page-routing.module.ts | 12 +++--- .../community-page-routing.module.ts | 8 ++-- .../+item-page/item-page-routing.module.ts | 6 +-- .../submit-page-routing.module.ts | 4 +- .../workflowitems-edit-page-routing.module.ts | 8 ++-- ...workspaceitems-edit-page-routing.module.ts | 4 +- src/app/app-routing.module.ts | 10 ++--- src/app/core/core.module.ts | 8 ++-- .../end-user-agreement.guard.ts} | 8 ++-- .../end-user-agreement.service.ts} | 36 ++++++++++++++---- .../end-user-agreement.component.html | 2 +- .../end-user-agreement.component.ts | 8 ++-- src/app/info/info-routing.module.ts | 14 +++++++ src/app/info/info.module.ts | 6 ++- .../privacy-content.component.html | 37 +++++++++++++++++++ .../privacy-content.component.scss | 0 .../privacy-content.component.spec.ts | 24 ++++++++++++ .../privacy-content.component.ts | 12 ++++++ src/app/info/privacy/privacy.component.html | 3 ++ src/app/info/privacy/privacy.component.scss | 0 .../info/privacy/privacy.component.spec.ts | 24 ++++++++++++ src/app/info/privacy/privacy.component.ts | 12 ++++++ .../process-page-routing.module.ts | 4 +- .../create-profile.component.ts | 17 ++++++++- .../register-page-routing.module.ts | 4 +- src/assets/i18n/en.json5 | 6 +++ 27 files changed, 227 insertions(+), 54 deletions(-) rename src/app/core/{user-agreement/user-agreement.guard.ts => end-user-agreement/end-user-agreement.guard.ts} (80%) rename src/app/core/{user-agreement/user-agreement.service.ts => end-user-agreement/end-user-agreement.service.ts} (68%) create mode 100644 src/app/info/privacy/privacy-content/privacy-content.component.html create mode 100644 src/app/info/privacy/privacy-content/privacy-content.component.scss create mode 100644 src/app/info/privacy/privacy-content/privacy-content.component.spec.ts create mode 100644 src/app/info/privacy/privacy-content/privacy-content.component.ts create mode 100644 src/app/info/privacy/privacy.component.html create mode 100644 src/app/info/privacy/privacy.component.scss create mode 100644 src/app/info/privacy/privacy.component.spec.ts create mode 100644 src/app/info/privacy/privacy.component.ts diff --git a/src/app/+bitstream-page/bitstream-page-routing.module.ts b/src/app/+bitstream-page/bitstream-page-routing.module.ts index 11b5349f90..0856dd00cb 100644 --- a/src/app/+bitstream-page/bitstream-page-routing.module.ts +++ b/src/app/+bitstream-page/bitstream-page-routing.module.ts @@ -3,7 +3,7 @@ import { RouterModule } from '@angular/router'; import { EditBitstreamPageComponent } from './edit-bitstream-page/edit-bitstream-page.component'; import { AuthenticatedGuard } from '../core/auth/authenticated.guard'; import { BitstreamPageResolver } from './bitstream-page.resolver'; -import { UserAgreementGuard } from '../core/user-agreement/user-agreement.guard'; +import { EndUserAgreementGuard } from '../core/end-user-agreement/end-user-agreement.guard'; const EDIT_BITSTREAM_PATH = ':id/edit'; @@ -19,7 +19,7 @@ const EDIT_BITSTREAM_PATH = ':id/edit'; resolve: { bitstream: BitstreamPageResolver }, - canActivate: [AuthenticatedGuard, UserAgreementGuard] + canActivate: [AuthenticatedGuard, EndUserAgreementGuard] } ]) ], diff --git a/src/app/+collection-page/collection-page-routing.module.ts b/src/app/+collection-page/collection-page-routing.module.ts index 3500cf9bc8..c7ee89a9d2 100644 --- a/src/app/+collection-page/collection-page-routing.module.ts +++ b/src/app/+collection-page/collection-page-routing.module.ts @@ -16,7 +16,7 @@ import { CollectionBreadcrumbResolver } from '../core/breadcrumbs/collection-bre import { DSOBreadcrumbsService } from '../core/breadcrumbs/dso-breadcrumbs.service'; import { LinkService } from '../core/cache/builders/link.service'; import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver'; -import { UserAgreementGuard } from '../core/user-agreement/user-agreement.guard'; +import { EndUserAgreementGuard } from '../core/end-user-agreement/end-user-agreement.guard'; export const COLLECTION_PARENT_PARAMETER = 'parent'; @@ -42,7 +42,7 @@ const ITEMTEMPLATE_PATH = 'itemtemplate'; { path: COLLECTION_CREATE_PATH, component: CreateCollectionPageComponent, - canActivate: [AuthenticatedGuard, CreateCollectionPageGuard, UserAgreementGuard] + canActivate: [AuthenticatedGuard, CreateCollectionPageGuard, EndUserAgreementGuard] }, { path: ':id', @@ -55,18 +55,18 @@ const ITEMTEMPLATE_PATH = 'itemtemplate'; { path: COLLECTION_EDIT_PATH, loadChildren: './edit-collection-page/edit-collection-page.module#EditCollectionPageModule', - canActivate: [AuthenticatedGuard, UserAgreementGuard] + canActivate: [AuthenticatedGuard, EndUserAgreementGuard] }, { path: 'delete', pathMatch: 'full', component: DeleteCollectionPageComponent, - canActivate: [AuthenticatedGuard, UserAgreementGuard], + canActivate: [AuthenticatedGuard, EndUserAgreementGuard], }, { path: ITEMTEMPLATE_PATH, component: EditItemTemplatePageComponent, - canActivate: [AuthenticatedGuard, UserAgreementGuard], + canActivate: [AuthenticatedGuard, EndUserAgreementGuard], resolve: { item: ItemTemplatePageResolver, breadcrumb: I18nBreadcrumbResolver @@ -82,7 +82,7 @@ const ITEMTEMPLATE_PATH = 'itemtemplate'; path: '/edit/mapper', component: CollectionItemMapperComponent, pathMatch: 'full', - canActivate: [AuthenticatedGuard, UserAgreementGuard] + canActivate: [AuthenticatedGuard, EndUserAgreementGuard] } ] }, diff --git a/src/app/+community-page/community-page-routing.module.ts b/src/app/+community-page/community-page-routing.module.ts index b5c3d9d2d6..be09a2c48f 100644 --- a/src/app/+community-page/community-page-routing.module.ts +++ b/src/app/+community-page/community-page-routing.module.ts @@ -12,7 +12,7 @@ import { getCommunityModulePath } from '../app-routing.module'; import { CommunityBreadcrumbResolver } from '../core/breadcrumbs/community-breadcrumb.resolver'; import { DSOBreadcrumbsService } from '../core/breadcrumbs/dso-breadcrumbs.service'; import { LinkService } from '../core/cache/builders/link.service'; -import { UserAgreementGuard } from '../core/user-agreement/user-agreement.guard'; +import { EndUserAgreementGuard } from '../core/end-user-agreement/end-user-agreement.guard'; export const COMMUNITY_PARENT_PARAMETER = 'parent'; @@ -37,7 +37,7 @@ const COMMUNITY_EDIT_PATH = 'edit'; { path: COMMUNITY_CREATE_PATH, component: CreateCommunityPageComponent, - canActivate: [AuthenticatedGuard, CreateCommunityPageGuard, UserAgreementGuard] + canActivate: [AuthenticatedGuard, CreateCommunityPageGuard, EndUserAgreementGuard] }, { path: ':id', @@ -50,13 +50,13 @@ const COMMUNITY_EDIT_PATH = 'edit'; { path: COMMUNITY_EDIT_PATH, loadChildren: './edit-community-page/edit-community-page.module#EditCommunityPageModule', - canActivate: [AuthenticatedGuard, UserAgreementGuard] + canActivate: [AuthenticatedGuard, EndUserAgreementGuard] }, { path: 'delete', pathMatch: 'full', component: DeleteCommunityPageComponent, - canActivate: [AuthenticatedGuard, UserAgreementGuard], + canActivate: [AuthenticatedGuard, EndUserAgreementGuard], }, { path: '', diff --git a/src/app/+item-page/item-page-routing.module.ts b/src/app/+item-page/item-page-routing.module.ts index cc75a7ebc4..436f6990ba 100644 --- a/src/app/+item-page/item-page-routing.module.ts +++ b/src/app/+item-page/item-page-routing.module.ts @@ -11,7 +11,7 @@ import { ItemBreadcrumbResolver } from '../core/breadcrumbs/item-breadcrumb.reso import { DSOBreadcrumbsService } from '../core/breadcrumbs/dso-breadcrumbs.service'; import { LinkService } from '../core/cache/builders/link.service'; import { UploadBitstreamComponent } from './bitstreams/upload/upload-bitstream.component'; -import { UserAgreementGuard } from '../core/user-agreement/user-agreement.guard'; +import { EndUserAgreementGuard } from '../core/end-user-agreement/end-user-agreement.guard'; export function getItemPageRoute(itemId: string) { return new URLCombiner(getItemModulePath(), itemId).toString(); @@ -47,12 +47,12 @@ const UPLOAD_BITSTREAM_PATH = 'bitstreams/new'; { path: ITEM_EDIT_PATH, loadChildren: './edit-item-page/edit-item-page.module#EditItemPageModule', - canActivate: [AuthenticatedGuard, UserAgreementGuard] + canActivate: [AuthenticatedGuard, EndUserAgreementGuard] }, { path: UPLOAD_BITSTREAM_PATH, component: UploadBitstreamComponent, - canActivate: [AuthenticatedGuard, UserAgreementGuard] + canActivate: [AuthenticatedGuard, EndUserAgreementGuard] } ], } diff --git a/src/app/+submit-page/submit-page-routing.module.ts b/src/app/+submit-page/submit-page-routing.module.ts index f15d0a488c..a66e2636a1 100644 --- a/src/app/+submit-page/submit-page-routing.module.ts +++ b/src/app/+submit-page/submit-page-routing.module.ts @@ -3,13 +3,13 @@ import { RouterModule } from '@angular/router'; import { AuthenticatedGuard } from '../core/auth/authenticated.guard'; import { SubmissionSubmitComponent } from '../submission/submit/submission-submit.component'; -import { UserAgreementGuard } from '../core/user-agreement/user-agreement.guard'; +import { EndUserAgreementGuard } from '../core/end-user-agreement/end-user-agreement.guard'; @NgModule({ imports: [ RouterModule.forChild([ { - canActivate: [AuthenticatedGuard, UserAgreementGuard], + canActivate: [AuthenticatedGuard, EndUserAgreementGuard], path: '', pathMatch: 'full', component: SubmissionSubmitComponent, diff --git a/src/app/+workflowitems-edit-page/workflowitems-edit-page-routing.module.ts b/src/app/+workflowitems-edit-page/workflowitems-edit-page-routing.module.ts index a8aa4f93a0..585a6377ae 100644 --- a/src/app/+workflowitems-edit-page/workflowitems-edit-page-routing.module.ts +++ b/src/app/+workflowitems-edit-page/workflowitems-edit-page-routing.module.ts @@ -8,7 +8,7 @@ import { getWorkflowItemModulePath } from '../app-routing.module'; import { WorkflowItemDeleteComponent } from './workflow-item-delete/workflow-item-delete.component'; import { WorkflowItemPageResolver } from './workflow-item-page.resolver'; import { WorkflowItemSendBackComponent } from './workflow-item-send-back/workflow-item-send-back.component'; -import { UserAgreementGuard } from '../core/user-agreement/user-agreement.guard'; +import { EndUserAgreementGuard } from '../core/end-user-agreement/end-user-agreement.guard'; export function getWorkflowItemPageRoute(wfiId: string) { return new URLCombiner(getWorkflowItemModulePath(), wfiId).toString(); @@ -38,19 +38,19 @@ const WORKFLOW_ITEM_SEND_BACK_PATH = 'sendback'; resolve: { wfi: WorkflowItemPageResolver }, children: [ { - canActivate: [AuthenticatedGuard, UserAgreementGuard], + canActivate: [AuthenticatedGuard, EndUserAgreementGuard], path: WORKFLOW_ITEM_EDIT_PATH, component: SubmissionEditComponent, data: { title: 'submission.edit.title' } }, { - canActivate: [AuthenticatedGuard, UserAgreementGuard], + canActivate: [AuthenticatedGuard, EndUserAgreementGuard], path: WORKFLOW_ITEM_DELETE_PATH, component: WorkflowItemDeleteComponent, data: { title: 'workflow-item.delete.title' } }, { - canActivate: [AuthenticatedGuard, UserAgreementGuard], + canActivate: [AuthenticatedGuard, EndUserAgreementGuard], path: WORKFLOW_ITEM_SEND_BACK_PATH, component: WorkflowItemSendBackComponent, data: { title: 'workflow-item.send-back.title' } diff --git a/src/app/+workspaceitems-edit-page/workspaceitems-edit-page-routing.module.ts b/src/app/+workspaceitems-edit-page/workspaceitems-edit-page-routing.module.ts index 9dce207baf..0548b488c4 100644 --- a/src/app/+workspaceitems-edit-page/workspaceitems-edit-page-routing.module.ts +++ b/src/app/+workspaceitems-edit-page/workspaceitems-edit-page-routing.module.ts @@ -3,14 +3,14 @@ import { RouterModule } from '@angular/router'; import { AuthenticatedGuard } from '../core/auth/authenticated.guard'; import { SubmissionEditComponent } from '../submission/edit/submission-edit.component'; -import { UserAgreementGuard } from '../core/user-agreement/user-agreement.guard'; +import { EndUserAgreementGuard } from '../core/end-user-agreement/end-user-agreement.guard'; @NgModule({ imports: [ RouterModule.forChild([ { path: '', redirectTo: '/home', pathMatch: 'full' }, { - canActivate: [AuthenticatedGuard, UserAgreementGuard], + canActivate: [AuthenticatedGuard, EndUserAgreementGuard], path: ':id/edit', component: SubmissionEditComponent, data: { title: 'submission.edit.title' } diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index de077a5b3a..ed27cc1b93 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -12,7 +12,7 @@ import { getItemPageRoute } from './+item-page/item-page-routing.module'; import { getCollectionPageRoute } from './+collection-page/collection-page-routing.module'; import { SiteAdministratorGuard } from './core/data/feature-authorization/feature-authorization-guard/site-administrator.guard'; import { UnauthorizedComponent } from './unauthorized/unauthorized.component'; -import { UserAgreementGuard } from './core/user-agreement/user-agreement.guard'; +import { EndUserAgreementGuard } from './core/end-user-agreement/end-user-agreement.guard'; const ITEM_MODULE_PATH = 'items'; @@ -109,11 +109,11 @@ export function getInfoModulePath() { { path: 'mydspace', loadChildren: './+my-dspace-page/my-dspace-page.module#MyDSpacePageModule', - canActivate: [AuthenticatedGuard, UserAgreementGuard] + canActivate: [AuthenticatedGuard, EndUserAgreementGuard] }, { path: 'search', loadChildren: './+search-page/search-page-routing.module#SearchPageRoutingModule' }, { path: 'browse', loadChildren: './+browse-by/browse-by.module#BrowseByModule'}, - { path: ADMIN_MODULE_PATH, loadChildren: './+admin/admin.module#AdminModule', canActivate: [SiteAdministratorGuard, UserAgreementGuard] }, + { path: ADMIN_MODULE_PATH, loadChildren: './+admin/admin.module#AdminModule', canActivate: [SiteAdministratorGuard, EndUserAgreementGuard] }, { path: 'login', loadChildren: './+login-page/login-page.module#LoginPageModule' }, { path: 'logout', loadChildren: './+logout-page/logout-page.module#LogoutPageModule' }, { path: 'submit', loadChildren: './+submit-page/submit-page.module#SubmitPageModule' }, @@ -127,9 +127,9 @@ export function getInfoModulePath() { }, { path: PROFILE_MODULE_PATH, - loadChildren: './profile-page/profile-page.module#ProfilePageModule', canActivate: [AuthenticatedGuard, UserAgreementGuard] + loadChildren: './profile-page/profile-page.module#ProfilePageModule', canActivate: [AuthenticatedGuard, EndUserAgreementGuard] }, - { path: 'processes', loadChildren: './process-page/process-page.module#ProcessPageModule', canActivate: [AuthenticatedGuard, UserAgreementGuard] }, + { path: 'processes', loadChildren: './process-page/process-page.module#ProcessPageModule', canActivate: [AuthenticatedGuard, EndUserAgreementGuard] }, { path: INFO_MODULE_PATH, loadChildren: './info/info.module#InfoModule' }, { path: UNAUTHORIZED_PATH, component: UnauthorizedComponent }, { path: '**', pathMatch: 'full', component: PageNotFoundComponent }, diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index f1c4ebd121..c68d0311f2 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -162,8 +162,8 @@ import { SubmissionCcLicenceUrl } from './submission/models/submission-cc-licens import { SubmissionCcLicenseUrlDataService } from './submission/submission-cc-license-url-data.service'; import { ConfigurationDataService } from './data/configuration-data.service'; import { ConfigurationProperty } from './shared/configuration-property.model'; -import { UserAgreementGuard } from './user-agreement/user-agreement.guard'; -import { UserAgreementService } from './user-agreement/user-agreement.service'; +import { EndUserAgreementGuard } from './end-user-agreement/end-user-agreement.guard'; +import { EndUserAgreementService } from './end-user-agreement/end-user-agreement.service'; /** * When not in production, endpoint responses can be mocked for testing purposes @@ -291,8 +291,8 @@ const PROVIDERS = [ MetadataSchemaDataService, MetadataFieldDataService, TokenResponseParsingService, - UserAgreementGuard, - UserAgreementService, + EndUserAgreementGuard, + EndUserAgreementService, // register AuthInterceptor as HttpInterceptor { provide: HTTP_INTERCEPTORS, diff --git a/src/app/core/user-agreement/user-agreement.guard.ts b/src/app/core/end-user-agreement/end-user-agreement.guard.ts similarity index 80% rename from src/app/core/user-agreement/user-agreement.guard.ts rename to src/app/core/end-user-agreement/end-user-agreement.guard.ts index 7464b87e00..450385984a 100644 --- a/src/app/core/user-agreement/user-agreement.guard.ts +++ b/src/app/core/end-user-agreement/end-user-agreement.guard.ts @@ -2,16 +2,16 @@ import { Injectable } from '@angular/core'; import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot, UrlTree } from '@angular/router'; import { Observable } from 'rxjs/internal/Observable'; import { returnEndUserAgreementUrlTreeOnFalse } from '../shared/operators'; -import { UserAgreementService } from './user-agreement.service'; +import { EndUserAgreementService } from './end-user-agreement.service'; import { tap } from 'rxjs/operators'; /** * A guard redirecting users to the end agreement page when they haven't accepted the latest user agreement */ @Injectable() -export class UserAgreementGuard implements CanActivate { +export class EndUserAgreementGuard implements CanActivate { - constructor(protected userAgreementService: UserAgreementService, + constructor(protected endUserAgreementService: EndUserAgreementService, protected router: Router) { } @@ -22,7 +22,7 @@ export class UserAgreementGuard implements CanActivate { * when they're finished accepting the agreement */ canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { - return this.userAgreementService.hasCurrentUserAcceptedAgreement().pipe( + return this.endUserAgreementService.hasCurrentUserAcceptedAgreement().pipe( returnEndUserAgreementUrlTreeOnFalse(this.router), tap((result) => { if (result instanceof UrlTree) { diff --git a/src/app/core/user-agreement/user-agreement.service.ts b/src/app/core/end-user-agreement/end-user-agreement.service.ts similarity index 68% rename from src/app/core/user-agreement/user-agreement.service.ts rename to src/app/core/end-user-agreement/end-user-agreement.service.ts index 8e5694d81c..025fc6fc32 100644 --- a/src/app/core/user-agreement/user-agreement.service.ts +++ b/src/app/core/end-user-agreement/end-user-agreement.service.ts @@ -10,14 +10,14 @@ import { Metadata } from '../shared/metadata.utils'; import { EPersonDataService } from '../eperson/eperson-data.service'; import { getSucceededRemoteData } from '../shared/operators'; -export const USER_AGREEMENT_COOKIE = 'hasAgreedEndUser'; -export const USER_AGREEMENT_METADATA_FIELD = 'dspace.agreements.end-user'; +export const END_USER_AGREEMENT_COOKIE = 'hasAgreedEndUser'; +export const END_USER_AGREEMENT_METADATA_FIELD = 'dspace.agreements.end-user'; /** * Service for checking and managing the status of the current end user agreement */ @Injectable() -export class UserAgreementService { +export class EndUserAgreementService { constructor(protected cookie: CookieService, protected authService: AuthService, @@ -28,14 +28,14 @@ export class UserAgreementService { * Whether or not the current user has accepted the End User Agreement */ hasCurrentUserAcceptedAgreement(): Observable { - if (this.cookie.get(USER_AGREEMENT_COOKIE) === true) { + if (this.isCookieAccepted()) { return observableOf(true); } else { return this.authService.isAuthenticated().pipe( switchMap((authenticated) => { if (authenticated) { return this.authService.getAuthenticatedUserFromStore().pipe( - map((user) => hasValue(user) && user.hasMetadata(USER_AGREEMENT_METADATA_FIELD) && user.firstMetadata(USER_AGREEMENT_METADATA_FIELD).value === 'true') + map((user) => hasValue(user) && user.hasMetadata(END_USER_AGREEMENT_METADATA_FIELD) && user.firstMetadata(END_USER_AGREEMENT_METADATA_FIELD).value === 'true') ); } else { return observableOf(false); @@ -58,14 +58,14 @@ export class UserAgreementService { return this.authService.getAuthenticatedUserFromStore().pipe( switchMap((user) => { const updatedUser = cloneDeep(user); - Metadata.setFirstValue(updatedUser.metadata, USER_AGREEMENT_METADATA_FIELD, String(accepted)); + Metadata.setFirstValue(updatedUser.metadata, END_USER_AGREEMENT_METADATA_FIELD, String(accepted)); return this.ePersonService.update(updatedUser); }), getSucceededRemoteData(), map((rd) => hasValue(rd.payload)) ); } else { - this.cookie.set(USER_AGREEMENT_COOKIE, accepted); + this.setCookieAccepted(accepted); return observableOf(true); } }), @@ -73,4 +73,26 @@ export class UserAgreementService { ); } + /** + * Is the End User Agreement accepted in the cookie? + */ + isCookieAccepted(): boolean { + return this.cookie.get(END_USER_AGREEMENT_COOKIE) === true; + } + + /** + * Set the cookie's End User Agreement accepted state + * @param accepted + */ + setCookieAccepted(accepted: boolean) { + this.cookie.set(END_USER_AGREEMENT_COOKIE, accepted); + } + + /** + * Remove the End User Agreement cookie + */ + removeCookieAccepted() { + this.cookie.remove(END_USER_AGREEMENT_COOKIE); + } + } diff --git a/src/app/info/end-user-agreement/end-user-agreement.component.html b/src/app/info/end-user-agreement/end-user-agreement.component.html index 624264e2cc..628718cdcd 100644 --- a/src/app/info/end-user-agreement/end-user-agreement.component.html +++ b/src/app/info/end-user-agreement/end-user-agreement.component.html @@ -7,7 +7,7 @@
- +
diff --git a/src/app/info/end-user-agreement/end-user-agreement.component.ts b/src/app/info/end-user-agreement/end-user-agreement.component.ts index 5da59bf5c4..f86b0c3434 100644 --- a/src/app/info/end-user-agreement/end-user-agreement.component.ts +++ b/src/app/info/end-user-agreement/end-user-agreement.component.ts @@ -5,7 +5,7 @@ import { Router } from '@angular/router'; import { Store } from '@ngrx/store'; import { AppState } from '../../app.reducer'; import { LogOutAction } from '../../core/auth/auth.actions'; -import { UserAgreementService } from '../../core/user-agreement/user-agreement.service'; +import { EndUserAgreementService } from '../../core/end-user-agreement/end-user-agreement.service'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { TranslateService } from '@ngx-translate/core'; import { hasValue } from '../../shared/empty.util'; @@ -25,7 +25,7 @@ export class EndUserAgreementComponent implements OnInit { */ accepted = false; - constructor(protected userAgreementService: UserAgreementService, + constructor(protected endUserAgreementService: EndUserAgreementService, protected notificationsService: NotificationsService, protected translate: TranslateService, protected authService: AuthService, @@ -44,7 +44,7 @@ export class EndUserAgreementComponent implements OnInit { * Initialize the "accepted" property of this component by checking if the current user has accepted it before */ initAccepted() { - this.userAgreementService.hasCurrentUserAcceptedAgreement().subscribe((accepted) => { + this.endUserAgreementService.hasCurrentUserAcceptedAgreement().subscribe((accepted) => { this.accepted = accepted; }); } @@ -54,7 +54,7 @@ export class EndUserAgreementComponent implements OnInit { * Set the End User Agreement, display a notification and (optionally) redirect the user back to their original destination */ submit() { - this.userAgreementService.setUserAcceptedAgreement(this.accepted).subscribe((success) => { + this.endUserAgreementService.setUserAcceptedAgreement(this.accepted).subscribe((success) => { if (success) { this.notificationsService.success(this.translate.instant('info.end-user-agreement.accept.success')); const redirect = window.history.state.redirect; diff --git a/src/app/info/info-routing.module.ts b/src/app/info/info-routing.module.ts index bfb8b77d96..eab8cd32af 100644 --- a/src/app/info/info-routing.module.ts +++ b/src/app/info/info-routing.module.ts @@ -3,13 +3,19 @@ import { RouterModule } from '@angular/router'; import { EndUserAgreementComponent } from './end-user-agreement/end-user-agreement.component'; import { getInfoModulePath } from '../app-routing.module'; import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver'; +import { PrivacyComponent } from './privacy/privacy.component'; const END_USER_AGREEMENT_PATH = 'end-user-agreement'; +const PRIVACY_PATH = 'privacy'; export function getEndUserAgreementPath() { return getSubPath(END_USER_AGREEMENT_PATH); } +export function getPrivacyPath() { + return getSubPath(PRIVACY_PATH); +} + function getSubPath(path: string) { return `${getInfoModulePath()}/${path}`; } @@ -23,6 +29,14 @@ function getSubPath(path: string) { resolve: { breadcrumb: I18nBreadcrumbResolver }, data: { title: 'info.end-user-agreement.title', breadcrumbKey: 'info.end-user-agreement' } } + ]), + RouterModule.forChild([ + { + path: PRIVACY_PATH, + component: PrivacyComponent, + resolve: { breadcrumb: I18nBreadcrumbResolver }, + data: { title: 'info.privacy.title', breadcrumbKey: 'info.privacy' } + } ]) ] }) diff --git a/src/app/info/info.module.ts b/src/app/info/info.module.ts index 93923cffe3..ae8ef89b27 100644 --- a/src/app/info/info.module.ts +++ b/src/app/info/info.module.ts @@ -4,6 +4,8 @@ import { SharedModule } from '../shared/shared.module'; import { EndUserAgreementComponent } from './end-user-agreement/end-user-agreement.component'; import { InfoRoutingModule } from './info-routing.module'; import { EndUserAgreementContentComponent } from './end-user-agreement/end-user-agreement-content/end-user-agreement-content.component'; +import { PrivacyComponent } from './privacy/privacy.component'; +import { PrivacyContentComponent } from './privacy/privacy-content/privacy-content.component'; @NgModule({ imports: [ @@ -13,7 +15,9 @@ import { EndUserAgreementContentComponent } from './end-user-agreement/end-user- ], declarations: [ EndUserAgreementComponent, - EndUserAgreementContentComponent + EndUserAgreementContentComponent, + PrivacyComponent, + PrivacyContentComponent ] }) export class InfoModule { diff --git a/src/app/info/privacy/privacy-content/privacy-content.component.html b/src/app/info/privacy/privacy-content/privacy-content.component.html new file mode 100644 index 0000000000..a5bbb3fe10 --- /dev/null +++ b/src/app/info/privacy/privacy-content/privacy-content.component.html @@ -0,0 +1,37 @@ +

{{ 'info.privacy.head' | translate }}

+

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Nunc sed velit dignissim sodales ut eu. In ante metus dictum at tempor. Diam phasellus vestibulum lorem sed risus. Sed cras ornare arcu dui vivamus. Sit amet consectetur adipiscing elit pellentesque. Id velit ut tortor pretium viverra suspendisse potenti. Sed euismod nisi porta lorem mollis aliquam ut. Justo laoreet sit amet cursus sit amet dictum sit. Ullamcorper morbi tincidunt ornare massa eget egestas. +

+

+ In iaculis nunc sed augue lacus. Curabitur vitae nunc sed velit dignissim sodales ut eu sem. Tellus id interdum velit laoreet id donec ultrices tincidunt arcu. Quis vel eros donec ac odio tempor. Viverra accumsan in nisl nisi scelerisque eu ultrices vitae. Varius quam quisque id diam vel quam. Nisl tincidunt eget nullam non nisi est sit. Nunc aliquet bibendum enim facilisis. Aenean sed adipiscing diam donec adipiscing. Convallis tellus id interdum velit laoreet. Massa placerat duis ultricies lacus sed turpis tincidunt. Sed cras ornare arcu dui vivamus arcu. Egestas integer eget aliquet nibh praesent tristique. Sit amet purus gravida quis blandit turpis cursus in hac. Porta non pulvinar neque laoreet suspendisse. Quis risus sed vulputate odio ut. Dignissim enim sit amet venenatis urna cursus. +

+

+ Interdum velit laoreet id donec ultrices tincidunt arcu non sodales. Massa sapien faucibus et molestie. Dictumst vestibulum rhoncus est pellentesque elit ullamcorper. Metus dictum at tempor commodo ullamcorper. Tincidunt lobortis feugiat vivamus at augue eget. Non diam phasellus vestibulum lorem sed risus ultricies. Neque aliquam vestibulum morbi blandit cursus risus at ultrices mi. Euismod lacinia at quis risus sed. Lorem mollis aliquam ut porttitor leo a diam. Ipsum dolor sit amet consectetur. Ante in nibh mauris cursus mattis molestie a iaculis at. Commodo ullamcorper a lacus vestibulum. Pellentesque elit eget gravida cum sociis. Sit amet commodo nulla facilisi nullam vehicula. Vehicula ipsum a arcu cursus vitae congue mauris rhoncus aenean. +

+

+ Ac turpis egestas maecenas pharetra convallis. Lacus sed viverra tellus in. Nullam eget felis eget nunc lobortis mattis aliquam faucibus purus. Id aliquet risus feugiat in ante metus dictum at. Quis enim lobortis scelerisque fermentum dui faucibus. Eu volutpat odio facilisis mauris sit amet massa vitae tortor. Tellus elementum sagittis vitae et leo. Cras sed felis eget velit aliquet sagittis. Proin fermentum leo vel orci porta non pulvinar neque laoreet. Dui sapien eget mi proin sed libero enim. Ultrices mi tempus imperdiet nulla malesuada. Mattis molestie a iaculis at. Turpis massa sed elementum tempus egestas. +

+

+ Dui faucibus in ornare quam viverra orci sagittis eu volutpat. Cras adipiscing enim eu turpis. Ac felis donec et odio pellentesque. Iaculis nunc sed augue lacus viverra vitae congue eu consequat. Posuere lorem ipsum dolor sit amet consectetur adipiscing elit duis. Elit eget gravida cum sociis natoque penatibus. Id faucibus nisl tincidunt eget nullam non. Sagittis aliquam malesuada bibendum arcu vitae. Fermentum leo vel orci porta. Aliquam ultrices sagittis orci a scelerisque purus semper. Diam maecenas sed enim ut sem viverra aliquet eget sit. Et ultrices neque ornare aenean euismod. Eu mi bibendum neque egestas congue quisque egestas diam. Eget lorem dolor sed viverra. Ut lectus arcu bibendum at. Rutrum tellus pellentesque eu tincidunt tortor. Vitae congue eu consequat ac. Elit ullamcorper dignissim cras tincidunt. Sit amet volutpat consequat mauris nunc congue nisi. +

+

+ Cursus in hac habitasse platea dictumst quisque sagittis purus. Placerat duis ultricies lacus sed turpis tincidunt. In egestas erat imperdiet sed euismod nisi porta lorem mollis. Non nisi est sit amet facilisis magna. In massa tempor nec feugiat nisl pretium fusce. Pulvinar neque laoreet suspendisse interdum consectetur. Ullamcorper morbi tincidunt ornare massa eget egestas purus viverra accumsan. Fringilla urna porttitor rhoncus dolor purus non enim. Mauris nunc congue nisi vitae suscipit. Commodo elit at imperdiet dui accumsan sit amet nulla. Tempor id eu nisl nunc mi ipsum faucibus. Porta non pulvinar neque laoreet suspendisse. Nec nam aliquam sem et tortor consequat. +

+

+ Eget nunc lobortis mattis aliquam faucibus purus. Odio tempor orci dapibus ultrices. Sed nisi lacus sed viverra tellus. Elit ullamcorper dignissim cras tincidunt. Porttitor rhoncus dolor purus non enim praesent elementum facilisis. Viverra orci sagittis eu volutpat odio. Pharetra massa massa ultricies mi quis. Lectus vestibulum mattis ullamcorper velit sed ullamcorper. Pulvinar neque laoreet suspendisse interdum consectetur. Vitae auctor eu augue ut. Arcu dictum varius duis at consectetur lorem donec. Massa sed elementum tempus egestas sed sed. Risus viverra adipiscing at in tellus integer. Vulputate enim nulla aliquet porttitor lacus luctus accumsan. Pharetra massa massa ultricies mi. Elementum eu facilisis sed odio morbi quis commodo odio. Tincidunt lobortis feugiat vivamus at. Felis donec et odio pellentesque diam volutpat commodo sed. Risus feugiat in ante metus dictum at tempor commodo ullamcorper. Fringilla phasellus faucibus scelerisque eleifend donec pretium vulputate. +

+

+ Lectus proin nibh nisl condimentum id venenatis a condimentum. Id consectetur purus ut faucibus pulvinar elementum integer enim. Non pulvinar neque laoreet suspendisse interdum consectetur. Est pellentesque elit ullamcorper dignissim cras tincidunt lobortis feugiat vivamus. Suscipit tellus mauris a diam maecenas sed enim ut sem. Dolor purus non enim praesent elementum facilisis. Non enim praesent elementum facilisis leo vel. Ultricies leo integer malesuada nunc vel risus commodo viverra maecenas. Nulla porttitor massa id neque aliquam vestibulum. Erat velit scelerisque in dictum non consectetur. Amet cursus sit amet dictum. Nec tincidunt praesent semper feugiat nibh. Rutrum quisque non tellus orci ac auctor. Sagittis aliquam malesuada bibendum arcu vitae elementum. Massa tincidunt dui ut ornare lectus sit amet est. Aliquet porttitor lacus luctus accumsan tortor posuere ac. Quis hendrerit dolor magna eget est lorem ipsum dolor sit. Lectus mauris ultrices eros in. +

+

+ Massa massa ultricies mi quis hendrerit dolor magna. Est ullamcorper eget nulla facilisi etiam dignissim diam. Vulputate sapien nec sagittis aliquam malesuada. Nisi porta lorem mollis aliquam ut porttitor leo a diam. Tempus quam pellentesque nec nam. Faucibus vitae aliquet nec ullamcorper sit amet risus nullam eget. Gravida arcu ac tortor dignissim convallis aenean et tortor. A scelerisque purus semper eget duis at tellus at. Viverra ipsum nunc aliquet bibendum enim. Semper feugiat nibh sed pulvinar proin gravida hendrerit. Et ultrices neque ornare aenean euismod. Consequat semper viverra nam libero justo laoreet. Nunc mattis enim ut tellus elementum sagittis. Consectetur lorem donec massa sapien faucibus et. Vel risus commodo viverra maecenas accumsan lacus vel facilisis. Diam sollicitudin tempor id eu nisl nunc. Dolor magna eget est lorem ipsum dolor. Adipiscing elit pellentesque habitant morbi tristique. +

+

+ Nec sagittis aliquam malesuada bibendum arcu vitae elementum curabitur. Egestas fringilla phasellus faucibus scelerisque eleifend donec pretium vulputate sapien. Porttitor leo a diam sollicitudin tempor. Pellentesque dignissim enim sit amet venenatis urna cursus eget nunc. Posuere sollicitudin aliquam ultrices sagittis orci a scelerisque. Vehicula ipsum a arcu cursus vitae congue mauris rhoncus. Leo urna molestie at elementum. Duis tristique sollicitudin nibh sit amet commodo nulla facilisi. Libero id faucibus nisl tincidunt eget nullam. Tellus elementum sagittis vitae et leo duis ut diam. Sodales ut etiam sit amet nisl purus in mollis. Ipsum faucibus vitae aliquet nec ullamcorper sit amet risus. Lacus laoreet non curabitur gravida arcu ac tortor dignissim convallis. Aliquam malesuada bibendum arcu vitae elementum. Leo vel orci porta non pulvinar neque laoreet. Ipsum suspendisse ultrices gravida dictum fusce. +

+

+ Egestas erat imperdiet sed euismod nisi porta lorem. Venenatis a condimentum vitae sapien pellentesque habitant. Sit amet luctus venenatis lectus magna fringilla urna porttitor. Orci sagittis eu volutpat odio facilisis mauris sit amet massa. Ut enim blandit volutpat maecenas volutpat blandit aliquam. Libero volutpat sed cras ornare. Molestie ac feugiat sed lectus vestibulum mattis ullamcorper velit sed. Diam quis enim lobortis scelerisque fermentum dui. Pellentesque habitant morbi tristique senectus et netus. Auctor urna nunc id cursus metus aliquam eleifend. Elit scelerisque mauris pellentesque pulvinar pellentesque habitant morbi tristique. Sed risus ultricies tristique nulla aliquet enim tortor. Tincidunt arcu non sodales neque sodales ut. Sed lectus vestibulum mattis ullamcorper velit sed ullamcorper morbi tincidunt. +

+

+ Pulvinar etiam non quam lacus suspendisse faucibus. Eu mi bibendum neque egestas congue. Egestas purus viverra accumsan in nisl nisi scelerisque eu. Vulputate enim nulla aliquet porttitor lacus luctus accumsan. Eu non diam phasellus vestibulum. Semper feugiat nibh sed pulvinar. Ante in nibh mauris cursus mattis molestie a. Maecenas accumsan lacus vel facilisis volutpat. Non quam lacus suspendisse faucibus. Quis commodo odio aenean sed adipiscing. Vel elit scelerisque mauris pellentesque pulvinar pellentesque habitant morbi. Sed cras ornare arcu dui vivamus arcu felis. Tortor vitae purus faucibus ornare suspendisse sed. Morbi tincidunt ornare massa eget egestas purus viverra. Nibh cras pulvinar mattis nunc. Luctus venenatis lectus magna fringilla urna porttitor. Enim blandit volutpat maecenas volutpat blandit aliquam etiam erat. Malesuada pellentesque elit eget gravida cum sociis natoque penatibus et. Felis eget nunc lobortis mattis aliquam faucibus purus in. Vivamus arcu felis bibendum ut. +

diff --git a/src/app/info/privacy/privacy-content/privacy-content.component.scss b/src/app/info/privacy/privacy-content/privacy-content.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/info/privacy/privacy-content/privacy-content.component.spec.ts b/src/app/info/privacy/privacy-content/privacy-content.component.spec.ts new file mode 100644 index 0000000000..eca0659147 --- /dev/null +++ b/src/app/info/privacy/privacy-content/privacy-content.component.spec.ts @@ -0,0 +1,24 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { PrivacyContentComponent } from './privacy-content.component'; + +describe('PrivacyContentComponent', () => { + let component: PrivacyContentComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ PrivacyContentComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(PrivacyContentComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/info/privacy/privacy-content/privacy-content.component.ts b/src/app/info/privacy/privacy-content/privacy-content.component.ts new file mode 100644 index 0000000000..6a7b394cf4 --- /dev/null +++ b/src/app/info/privacy/privacy-content/privacy-content.component.ts @@ -0,0 +1,12 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'ds-privacy-content', + templateUrl: './privacy-content.component.html', + styleUrls: ['./privacy-content.component.scss'] +}) +/** + * Component displaying the contents of the Privacy Statement + */ +export class PrivacyContentComponent { +} diff --git a/src/app/info/privacy/privacy.component.html b/src/app/info/privacy/privacy.component.html new file mode 100644 index 0000000000..c6772e98f2 --- /dev/null +++ b/src/app/info/privacy/privacy.component.html @@ -0,0 +1,3 @@ +
+ +
diff --git a/src/app/info/privacy/privacy.component.scss b/src/app/info/privacy/privacy.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/info/privacy/privacy.component.spec.ts b/src/app/info/privacy/privacy.component.spec.ts new file mode 100644 index 0000000000..b7daa30029 --- /dev/null +++ b/src/app/info/privacy/privacy.component.spec.ts @@ -0,0 +1,24 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { PrivacyComponent } from './privacy.component'; + +describe('PrivacyComponent', () => { + let component: PrivacyComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ PrivacyComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(PrivacyComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/info/privacy/privacy.component.ts b/src/app/info/privacy/privacy.component.ts new file mode 100644 index 0000000000..dc9d3d69dc --- /dev/null +++ b/src/app/info/privacy/privacy.component.ts @@ -0,0 +1,12 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'ds-privacy', + templateUrl: './privacy.component.html', + styleUrls: ['./privacy.component.scss'] +}) +/** + * Component displaying the Privacy Statement + */ +export class PrivacyComponent { +} diff --git a/src/app/process-page/process-page-routing.module.ts b/src/app/process-page/process-page-routing.module.ts index 92d55467bc..6e9ecc4b29 100644 --- a/src/app/process-page/process-page-routing.module.ts +++ b/src/app/process-page/process-page-routing.module.ts @@ -7,7 +7,7 @@ import { ProcessDetailComponent } from './detail/process-detail.component'; import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver'; import { ProcessBreadcrumbResolver } from './process-breadcrumb.resolver'; import { AuthenticatedGuard } from '../core/auth/authenticated.guard'; -import { UserAgreementGuard } from '../core/user-agreement/user-agreement.guard'; +import { EndUserAgreementGuard } from '../core/end-user-agreement/end-user-agreement.guard'; @NgModule({ imports: [ @@ -16,7 +16,7 @@ import { UserAgreementGuard } from '../core/user-agreement/user-agreement.guard' path: '', resolve: { breadcrumb: I18nBreadcrumbResolver }, data: { breadcrumbKey: 'process.overview' }, - canActivate: [AuthenticatedGuard, UserAgreementGuard], + canActivate: [AuthenticatedGuard, EndUserAgreementGuard], children: [ { path: '', diff --git a/src/app/register-page/create-profile/create-profile.component.ts b/src/app/register-page/create-profile/create-profile.component.ts index 2755a17739..589e2d741e 100644 --- a/src/app/register-page/create-profile/create-profile.component.ts +++ b/src/app/register-page/create-profile/create-profile.component.ts @@ -14,6 +14,10 @@ import { AuthenticateAction } from '../../core/auth/auth.actions'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { environment } from '../../../environments/environment'; import { isEmpty } from '../../shared/empty.util'; +import { + END_USER_AGREEMENT_METADATA_FIELD, + EndUserAgreementService +} from '../../core/end-user-agreement/end-user-agreement.service'; /** * Component that renders the create profile page to be used by a user registering through a token @@ -41,7 +45,8 @@ export class CreateProfileComponent implements OnInit { private router: Router, private route: ActivatedRoute, private formBuilder: FormBuilder, - private notificationsService: NotificationsService + private notificationsService: NotificationsService, + private endUserAgreementService: EndUserAgreementService ) { } @@ -137,6 +142,16 @@ export class CreateProfileComponent implements OnInit { requireCertificate: false }; + // If the End User Agreement cookie is accepted, add end-user agreement metadata to the user + if (this.endUserAgreementService.isCookieAccepted()) { + values.metadata[END_USER_AGREEMENT_METADATA_FIELD] = [ + { + value: String(true) + } + ]; + this.endUserAgreementService.removeCookieAccepted(); + } + const eperson = Object.assign(new EPerson(), values); this.ePersonDataService.createEPersonForToken(eperson, this.token).subscribe((response) => { if (response.isSuccessful) { diff --git a/src/app/register-page/register-page-routing.module.ts b/src/app/register-page/register-page-routing.module.ts index c199b51c6c..a57450a329 100644 --- a/src/app/register-page/register-page-routing.module.ts +++ b/src/app/register-page/register-page-routing.module.ts @@ -4,7 +4,7 @@ import { RegisterEmailComponent } from './register-email/register-email.componen import { CreateProfileComponent } from './create-profile/create-profile.component'; import { ItemPageResolver } from '../+item-page/item-page.resolver'; import { RegistrationResolver } from '../register-email-form/registration.resolver'; -import { UserAgreementGuard } from '../core/user-agreement/user-agreement.guard'; +import { EndUserAgreementGuard } from '../core/end-user-agreement/end-user-agreement.guard'; @NgModule({ imports: [ @@ -18,7 +18,7 @@ import { UserAgreementGuard } from '../core/user-agreement/user-agreement.guard' path: ':token', component: CreateProfileComponent, resolve: {registration: RegistrationResolver}, - canActivate: [UserAgreementGuard] + canActivate: [EndUserAgreementGuard] } ]) ], diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index ca82aca80b..18c1f927f5 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -1154,6 +1154,12 @@ "info.end-user-agreement.title": "End User Agreement", + "info.privacy.breadcrumbs": "Privacy Statement", + + "info.privacy.head": "Privacy Statement", + + "info.privacy.title": "Privacy Statement", + "item.edit.authorizations.heading": "With this editor you can view and alter the policies of an item, plus alter policies of individual item components: bundles and bitstreams. Briefly, an item is a container of bundles, and bundles are containers of bitstreams. Containers usually have ADD/REMOVE/READ/WRITE policies, while bitstreams only have READ/WRITE policies.", From de1e1a70d1a7ebe6f3b2bfc4cfe7a37104391f8a Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Fri, 21 Aug 2020 16:05:19 +0200 Subject: [PATCH 04/12] 72541: End User Agreement test cases --- .../end-user-agreement.guard.spec.ts | 48 ++++++ .../end-user-agreement.service.spec.ts | 144 ++++++++++++++++++ ...d-user-agreement-content.component.spec.ts | 9 +- .../end-user-agreement.component.html | 4 +- .../end-user-agreement.component.spec.ts | 135 +++++++++++++++- .../privacy-content.component.spec.ts | 9 +- .../info/privacy/privacy.component.spec.ts | 9 +- .../create-profile.component.spec.ts | 143 +++++++++++++---- src/app/shared/mocks/cookie.service.mock.ts | 3 +- 9 files changed, 458 insertions(+), 46 deletions(-) create mode 100644 src/app/core/end-user-agreement/end-user-agreement.guard.spec.ts create mode 100644 src/app/core/end-user-agreement/end-user-agreement.service.spec.ts diff --git a/src/app/core/end-user-agreement/end-user-agreement.guard.spec.ts b/src/app/core/end-user-agreement/end-user-agreement.guard.spec.ts new file mode 100644 index 0000000000..589b227b5a --- /dev/null +++ b/src/app/core/end-user-agreement/end-user-agreement.guard.spec.ts @@ -0,0 +1,48 @@ +import { EndUserAgreementGuard } from './end-user-agreement.guard'; +import { EndUserAgreementService } from './end-user-agreement.service'; +import { Router, UrlTree } from '@angular/router'; +import { of as observableOf } from 'rxjs'; + +describe('EndUserAgreementGuard', () => { + let guard: EndUserAgreementGuard; + + let endUserAgreementService: EndUserAgreementService; + let router: Router; + + beforeEach(() => { + endUserAgreementService = jasmine.createSpyObj('endUserAgreementService', { + hasCurrentUserAcceptedAgreement: observableOf(true) + }); + router = jasmine.createSpyObj('router', { + navigateByUrl: {}, + parseUrl: new UrlTree() + }); + + guard = new EndUserAgreementGuard(endUserAgreementService, router); + }); + + describe('canActivate', () => { + describe('when the user has accepted the agreement', () => { + it('should return true', (done) => { + guard.canActivate(undefined, undefined).subscribe((result) => { + expect(result).toEqual(true); + done(); + }); + }); + }); + + describe('when the user hasn\'t accepted the agreement', () => { + beforeEach(() => { + (endUserAgreementService.hasCurrentUserAcceptedAgreement as jasmine.Spy).and.returnValue(observableOf(false)); + }); + + it('should navigate the user with a redirect url', (done) => { + const redirect = 'redirect/url'; + guard.canActivate(undefined, Object.assign({ url: redirect })).subscribe(() => { + expect(router.navigateByUrl).toHaveBeenCalledWith(jasmine.anything(), { state: { redirect } }); + done(); + }); + }); + }); + }); +}); diff --git a/src/app/core/end-user-agreement/end-user-agreement.service.spec.ts b/src/app/core/end-user-agreement/end-user-agreement.service.spec.ts new file mode 100644 index 0000000000..a292b2371c --- /dev/null +++ b/src/app/core/end-user-agreement/end-user-agreement.service.spec.ts @@ -0,0 +1,144 @@ +import { + END_USER_AGREEMENT_COOKIE, + END_USER_AGREEMENT_METADATA_FIELD, + EndUserAgreementService +} from './end-user-agreement.service'; +import { CookieServiceMock } from '../../shared/mocks/cookie.service.mock'; +import { of as observableOf } from 'rxjs'; +import { EPerson } from '../eperson/models/eperson.model'; +import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils'; + +describe('EndUserAgreementService', () => { + let service: EndUserAgreementService; + + let userWithMetadata: EPerson; + let userWithoutMetadata: EPerson; + + let cookie; + let authService; + let ePersonService; + + beforeEach(() => { + userWithMetadata = Object.assign(new EPerson(), { + metadata: { + [END_USER_AGREEMENT_METADATA_FIELD]: [ + { + value: 'true' + } + ] + } + }); + userWithoutMetadata = Object.assign(new EPerson()); + + cookie = new CookieServiceMock(); + authService = jasmine.createSpyObj('authService', { + isAuthenticated: observableOf(true), + getAuthenticatedUserFromStore: observableOf(userWithMetadata) + }); + ePersonService = jasmine.createSpyObj('ePersonService', { + update: createSuccessfulRemoteDataObject$(userWithMetadata) + }); + + service = new EndUserAgreementService(cookie, authService, ePersonService); + }); + + describe('when the cookie is set to true', () => { + beforeEach(() => { + cookie.set(END_USER_AGREEMENT_COOKIE, true); + }); + + it('hasCurrentUserAcceptedAgreement should return true', (done) => { + service.hasCurrentUserAcceptedAgreement().subscribe((result) => { + expect(result).toEqual(true); + done(); + }); + }); + + it('isCookieAccepted should return true', () => { + expect(service.isCookieAccepted()).toEqual(true); + }); + + it('removeCookieAccepted should remove the cookie', () => { + service.removeCookieAccepted(); + expect(cookie.get(END_USER_AGREEMENT_COOKIE)).toBeUndefined(); + }); + }); + + describe('when the cookie isn\'t set', () => { + describe('and the user is authenticated', () => { + beforeEach(() => { + (authService.isAuthenticated as jasmine.Spy).and.returnValue(observableOf(true)); + }); + + describe('and the user contains agreement metadata', () => { + beforeEach(() => { + (authService.getAuthenticatedUserFromStore as jasmine.Spy).and.returnValue(observableOf(userWithMetadata)); + }); + + it('hasCurrentUserAcceptedAgreement should return true', (done) => { + service.hasCurrentUserAcceptedAgreement().subscribe((result) => { + expect(result).toEqual(true); + done(); + }); + }); + }); + + describe('and the user doesn\'t contain agreement metadata', () => { + beforeEach(() => { + (authService.getAuthenticatedUserFromStore as jasmine.Spy).and.returnValue(observableOf(userWithoutMetadata)); + }); + + it('hasCurrentUserAcceptedAgreement should return false', (done) => { + service.hasCurrentUserAcceptedAgreement().subscribe((result) => { + expect(result).toEqual(false); + done(); + }); + }); + }); + + it('setUserAcceptedAgreement should update the user with new metadata', (done) => { + service.setUserAcceptedAgreement(true).subscribe(() => { + expect(ePersonService.update).toHaveBeenCalledWith(jasmine.objectContaining({ + metadata: jasmine.objectContaining({ + [END_USER_AGREEMENT_METADATA_FIELD]: [ + { + value: 'true' + } + ] + }) + })); + done(); + }); + }); + }); + + describe('and the user is not authenticated', () => { + beforeEach(() => { + (authService.isAuthenticated as jasmine.Spy).and.returnValue(observableOf(false)); + }); + + it('hasCurrentUserAcceptedAgreement should return false', (done) => { + service.hasCurrentUserAcceptedAgreement().subscribe((result) => { + expect(result).toEqual(false); + done(); + }); + }); + + it('setUserAcceptedAgreement should set the cookie to true', (done) => { + service.setUserAcceptedAgreement(true).subscribe(() => { + expect(cookie.get(END_USER_AGREEMENT_COOKIE)).toEqual(true); + done(); + }); + }); + }); + + it('isCookieAccepted should return false', () => { + expect(service.isCookieAccepted()).toEqual(false); + }); + + it('setCookieAccepted should set the cookie', () => { + service.setCookieAccepted(true); + expect(cookie.get(END_USER_AGREEMENT_COOKIE)).toEqual(true); + }); + }); +}); diff --git a/src/app/info/end-user-agreement/end-user-agreement-content/end-user-agreement-content.component.spec.ts b/src/app/info/end-user-agreement/end-user-agreement-content/end-user-agreement-content.component.spec.ts index c95e60846e..d2290cd01c 100644 --- a/src/app/info/end-user-agreement/end-user-agreement-content/end-user-agreement-content.component.spec.ts +++ b/src/app/info/end-user-agreement/end-user-agreement-content/end-user-agreement-content.component.spec.ts @@ -1,5 +1,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { EndUserAgreementContentComponent } from './end-user-agreement-content.component'; +import { TranslateModule } from '@ngx-translate/core'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; describe('EndUserAgreementContentComponent', () => { let component: EndUserAgreementContentComponent; @@ -7,9 +9,10 @@ describe('EndUserAgreementContentComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [ EndUserAgreementContentComponent ] - }) - .compileComponents(); + imports: [ TranslateModule.forRoot() ], + declarations: [ EndUserAgreementContentComponent ], + schemas: [NO_ERRORS_SCHEMA] + }).compileComponents(); })); beforeEach(() => { diff --git a/src/app/info/end-user-agreement/end-user-agreement.component.html b/src/app/info/end-user-agreement/end-user-agreement.component.html index 628718cdcd..2338bfa460 100644 --- a/src/app/info/end-user-agreement/end-user-agreement.component.html +++ b/src/app/info/end-user-agreement/end-user-agreement.component.html @@ -6,8 +6,8 @@
- - + +
diff --git a/src/app/info/end-user-agreement/end-user-agreement.component.spec.ts b/src/app/info/end-user-agreement/end-user-agreement.component.spec.ts index 6b87a0fccb..5d6b3f904c 100644 --- a/src/app/info/end-user-agreement/end-user-agreement.component.spec.ts +++ b/src/app/info/end-user-agreement/end-user-agreement.component.spec.ts @@ -1,15 +1,58 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { EndUserAgreementComponent } from './end-user-agreement.component'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { EndUserAgreementService } from '../../core/end-user-agreement/end-user-agreement.service'; +import { NotificationsService } from '../../shared/notifications/notifications.service'; +import { TranslateModule } from '@ngx-translate/core'; +import { AuthService } from '../../core/auth/auth.service'; +import { Router } from '@angular/router'; +import { of as observableOf } from 'rxjs'; +import { Store } from '@ngrx/store'; +import { By } from '@angular/platform-browser'; +import { LogOutAction } from '../../core/auth/auth.actions'; describe('EndUserAgreementComponent', () => { let component: EndUserAgreementComponent; let fixture: ComponentFixture; + let endUserAgreementService: EndUserAgreementService; + let notificationsService: NotificationsService; + let authService: AuthService; + let store; + let router: Router; + + let redirectUrl; + + function init() { + endUserAgreementService = jasmine.createSpyObj('endUserAgreementService', { + hasCurrentUserAcceptedAgreement: observableOf(false), + setUserAcceptedAgreement: observableOf(true) + }); + notificationsService = jasmine.createSpyObj('notificationsService', ['success', 'error']); + authService = jasmine.createSpyObj('authService', { + isAuthenticated: observableOf(true) + }); + store = jasmine.createSpyObj('store', ['dispatch']); + router = jasmine.createSpyObj('router', ['navigate', 'navigateByUrl']); + + redirectUrl = 'redirect/url'; + window.history.pushState({ redirect: redirectUrl }, ''); + } + beforeEach(async(() => { + init(); TestBed.configureTestingModule({ - declarations: [ EndUserAgreementComponent ] - }) - .compileComponents(); + imports: [ TranslateModule.forRoot() ], + declarations: [ EndUserAgreementComponent ], + providers: [ + { provide: EndUserAgreementService, useValue: endUserAgreementService }, + { provide: NotificationsService, useValue: notificationsService }, + { provide: AuthService, useValue: authService }, + { provide: Store, useValue: store }, + { provide: Router, useValue: router } + ], + schemas: [NO_ERRORS_SCHEMA] + }).compileComponents(); })); beforeEach(() => { @@ -18,7 +61,89 @@ describe('EndUserAgreementComponent', () => { fixture.detectChanges(); }); - it('should create', () => { - expect(component).toBeTruthy(); + describe('when the user hasn\'t accepted the agreement', () => { + beforeEach(() => { + (endUserAgreementService.hasCurrentUserAcceptedAgreement as jasmine.Spy).and.returnValue(observableOf(false)); + component.ngOnInit(); + fixture.detectChanges(); + }); + + it('should initialize the accepted property', () => { + expect(component.accepted).toEqual(false); + }); + + it('should disable the save button', () => { + const button = fixture.debugElement.query(By.css('#button-save')).nativeElement; + expect(button.disabled).toBeTruthy(); + }); + }); + + describe('when the user has accepted the agreement', () => { + beforeEach(() => { + (endUserAgreementService.hasCurrentUserAcceptedAgreement as jasmine.Spy).and.returnValue(observableOf(true)); + component.ngOnInit(); + fixture.detectChanges(); + }); + + it('should initialize the accepted property', () => { + expect(component.accepted).toEqual(true); + }); + + it('should enable the save button', () => { + const button = fixture.debugElement.query(By.css('#button-save')).nativeElement; + expect(button.disabled).toBeFalsy(); + }); + + describe('submit', () => { + describe('when accepting the agreement was successful', () => { + beforeEach(() => { + (endUserAgreementService.setUserAcceptedAgreement as jasmine.Spy).and.returnValue(observableOf(true)); + component.submit(); + }); + + it('should display a success notification', () => { + expect(notificationsService.success).toHaveBeenCalled(); + }); + + it('should navigate the user to the redirect url', () => { + expect(router.navigateByUrl).toHaveBeenCalledWith(redirectUrl); + }); + }); + + describe('when accepting the agreement was unsuccessful', () => { + beforeEach(() => { + (endUserAgreementService.setUserAcceptedAgreement as jasmine.Spy).and.returnValue(observableOf(false)); + component.submit(); + }); + + it('should display an error notification', () => { + expect(notificationsService.error).toHaveBeenCalled(); + }); + }); + }); + }); + + describe('cancel', () => { + describe('when the user is authenticated', () => { + beforeEach(() => { + (authService.isAuthenticated as jasmine.Spy).and.returnValue(observableOf(true)); + component.cancel(); + }); + + it('should logout the user', () => { + expect(store.dispatch).toHaveBeenCalledWith(new LogOutAction()); + }); + }); + + describe('when the user is not authenticated', () => { + beforeEach(() => { + (authService.isAuthenticated as jasmine.Spy).and.returnValue(observableOf(false)); + component.cancel(); + }); + + it('should navigate the user to the homepage', () => { + expect(router.navigate).toHaveBeenCalledWith(['home']); + }); + }); }); }); diff --git a/src/app/info/privacy/privacy-content/privacy-content.component.spec.ts b/src/app/info/privacy/privacy-content/privacy-content.component.spec.ts index eca0659147..a77e809dc3 100644 --- a/src/app/info/privacy/privacy-content/privacy-content.component.spec.ts +++ b/src/app/info/privacy/privacy-content/privacy-content.component.spec.ts @@ -1,5 +1,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { PrivacyContentComponent } from './privacy-content.component'; +import { TranslateModule } from '@ngx-translate/core'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; describe('PrivacyContentComponent', () => { let component: PrivacyContentComponent; @@ -7,9 +9,10 @@ describe('PrivacyContentComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [ PrivacyContentComponent ] - }) - .compileComponents(); + imports: [ TranslateModule.forRoot() ], + declarations: [ PrivacyContentComponent ], + schemas: [NO_ERRORS_SCHEMA] + }).compileComponents(); })); beforeEach(() => { diff --git a/src/app/info/privacy/privacy.component.spec.ts b/src/app/info/privacy/privacy.component.spec.ts index b7daa30029..a3d47e82f9 100644 --- a/src/app/info/privacy/privacy.component.spec.ts +++ b/src/app/info/privacy/privacy.component.spec.ts @@ -1,5 +1,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { PrivacyComponent } from './privacy.component'; +import { TranslateModule } from '@ngx-translate/core'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; describe('PrivacyComponent', () => { let component: PrivacyComponent; @@ -7,9 +9,10 @@ describe('PrivacyComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [ PrivacyComponent ] - }) - .compileComponents(); + imports: [ TranslateModule.forRoot() ], + declarations: [ PrivacyComponent ], + schemas: [NO_ERRORS_SCHEMA] + }).compileComponents(); })); beforeEach(() => { diff --git a/src/app/register-page/create-profile/create-profile.component.spec.ts b/src/app/register-page/create-profile/create-profile.component.spec.ts index a435e1143e..00c2eef99d 100644 --- a/src/app/register-page/create-profile/create-profile.component.spec.ts +++ b/src/app/register-page/create-profile/create-profile.component.spec.ts @@ -18,6 +18,10 @@ import { EPerson } from '../../core/eperson/models/eperson.model'; import { AuthenticateAction } from '../../core/auth/auth.actions'; import { RouterStub } from '../../shared/testing/router.stub'; import { NotificationsServiceStub } from '../../shared/testing/notifications-service.stub'; +import { + END_USER_AGREEMENT_METADATA_FIELD, + EndUserAgreementService +} from '../../core/end-user-agreement/end-user-agreement.service'; describe('CreateProfileComponent', () => { let comp: CreateProfileComponent; @@ -28,40 +32,80 @@ describe('CreateProfileComponent', () => { let ePersonDataService: EPersonDataService; let notificationsService; let store: Store; + let endUserAgreementService: EndUserAgreementService; const registration = Object.assign(new Registration(), {email: 'test@email.org', token: 'test-token'}); - const values = { - metadata: { - 'eperson.firstname': [ - { - value: 'First' - } - ], - 'eperson.lastname': [ - { - value: 'Last' - }, - ], - 'eperson.phone': [ - { - value: 'Phone' - } - ], - 'eperson.language': [ - { - value: 'en' - } - ] - }, - email: 'test@email.org', - password: 'password', - canLogIn: true, - requireCertificate: false - }; - const eperson = Object.assign(new EPerson(), values); + let values; + let eperson: EPerson; + let valuesWithAgreement; + let epersonWithAgreement: EPerson; beforeEach(async(() => { + values = { + metadata: { + 'eperson.firstname': [ + { + value: 'First' + } + ], + 'eperson.lastname': [ + { + value: 'Last' + }, + ], + 'eperson.phone': [ + { + value: 'Phone' + } + ], + 'eperson.language': [ + { + value: 'en' + } + ] + }, + email: 'test@email.org', + password: 'password', + canLogIn: true, + requireCertificate: false + }; + eperson = Object.assign(new EPerson(), values); + valuesWithAgreement = { + metadata: { + 'eperson.firstname': [ + { + value: 'First' + } + ], + 'eperson.lastname': [ + { + value: 'Last' + }, + ], + 'eperson.phone': [ + { + value: 'Phone' + } + ], + 'eperson.language': [ + { + value: 'en' + } + ], + [END_USER_AGREEMENT_METADATA_FIELD]: [ + { + value: 'true' + } + ] + }, + email: 'test@email.org', + password: 'password', + canLogIn: true, + requireCertificate: false + }; + epersonWithAgreement = Object.assign(new EPerson(), valuesWithAgreement); + route = {data: observableOf({registration: registration})}; router = new RouterStub(); notificationsService = new NotificationsServiceStub(); @@ -74,6 +118,11 @@ describe('CreateProfileComponent', () => { dispatch: {}, }); + endUserAgreementService = jasmine.createSpyObj('endUserAgreementService', { + isCookieAccepted: false, + removeCookieAccepted: {} + }); + TestBed.configureTestingModule({ imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), ReactiveFormsModule], declarations: [CreateProfileComponent], @@ -84,6 +133,7 @@ describe('CreateProfileComponent', () => { {provide: EPersonDataService, useValue: ePersonDataService}, {provide: FormBuilder, useValue: new FormBuilder()}, {provide: NotificationsService, useValue: notificationsService}, + {provide: EndUserAgreementService, useValue: endUserAgreementService}, ], schemas: [CUSTOM_ELEMENTS_SCHEMA] }).compileComponents(); @@ -131,6 +181,41 @@ describe('CreateProfileComponent', () => { expect(notificationsService.success).toHaveBeenCalled(); }); + describe('when the end-user-agreement cookie is accepted', () => { + beforeEach(() => { + (endUserAgreementService.isCookieAccepted as jasmine.Spy).and.returnValue(true); + }); + + it('should submit an eperson with agreement metadata for creation and log in on success', () => { + comp.firstName.patchValue('First'); + comp.lastName.patchValue('Last'); + comp.contactPhone.patchValue('Phone'); + comp.language.patchValue('en'); + comp.password = 'password'; + comp.isInValidPassword = false; + + comp.submitEperson(); + + expect(ePersonDataService.createEPersonForToken).toHaveBeenCalledWith(epersonWithAgreement, 'test-token'); + expect(store.dispatch).toHaveBeenCalledWith(new AuthenticateAction('test@email.org', 'password')); + expect(router.navigate).toHaveBeenCalledWith(['/home']); + expect(notificationsService.success).toHaveBeenCalled(); + }); + + it('should remove the cookie', () => { + comp.firstName.patchValue('First'); + comp.lastName.patchValue('Last'); + comp.contactPhone.patchValue('Phone'); + comp.language.patchValue('en'); + comp.password = 'password'; + comp.isInValidPassword = false; + + comp.submitEperson(); + + expect(endUserAgreementService.removeCookieAccepted).toHaveBeenCalled(); + }); + }); + it('should submit an eperson for creation and stay on page on error', () => { (ePersonDataService.createEPersonForToken as jasmine.Spy).and.returnValue(observableOf(new RestResponse(false, 500, 'Error'))); diff --git a/src/app/shared/mocks/cookie.service.mock.ts b/src/app/shared/mocks/cookie.service.mock.ts index f94f3d4a7d..17e1b36981 100644 --- a/src/app/shared/mocks/cookie.service.mock.ts +++ b/src/app/shared/mocks/cookie.service.mock.ts @@ -16,7 +16,8 @@ export class CookieServiceMock { return this.cookies.get(name); } - remove() { + remove(name) { + this.cookies.delete(name); return jasmine.createSpy('remove'); } From 32cf92eba94657767db7f1fb6581ff26095c6666 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Fri, 28 Aug 2020 10:37:55 +0200 Subject: [PATCH 05/12] 72541: Spacing between checkbox and buttons --- .../info/end-user-agreement/end-user-agreement.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/info/end-user-agreement/end-user-agreement.component.html b/src/app/info/end-user-agreement/end-user-agreement.component.html index 2338bfa460..2ab0005c69 100644 --- a/src/app/info/end-user-agreement/end-user-agreement.component.html +++ b/src/app/info/end-user-agreement/end-user-agreement.component.html @@ -5,7 +5,7 @@ -
+
From cb3ef1dde4ec3831ce1783f33c0d99033665f92c Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Fri, 28 Aug 2020 13:05:02 +0200 Subject: [PATCH 06/12] 72541: End-User-Agreement redirect via store --- .../end-user-agreement.guard.spec.ts | 8 ++++-- .../end-user-agreement.guard.ts | 5 +++- .../end-user-agreement.component.spec.ts | 8 +++--- .../end-user-agreement.component.ts | 26 ++++++++++++------- 4 files changed, 30 insertions(+), 17 deletions(-) diff --git a/src/app/core/end-user-agreement/end-user-agreement.guard.spec.ts b/src/app/core/end-user-agreement/end-user-agreement.guard.spec.ts index 589b227b5a..a7f3f32a6b 100644 --- a/src/app/core/end-user-agreement/end-user-agreement.guard.spec.ts +++ b/src/app/core/end-user-agreement/end-user-agreement.guard.spec.ts @@ -2,23 +2,26 @@ import { EndUserAgreementGuard } from './end-user-agreement.guard'; import { EndUserAgreementService } from './end-user-agreement.service'; import { Router, UrlTree } from '@angular/router'; import { of as observableOf } from 'rxjs'; +import { AuthService } from '../auth/auth.service'; describe('EndUserAgreementGuard', () => { let guard: EndUserAgreementGuard; let endUserAgreementService: EndUserAgreementService; + let authService: AuthService; let router: Router; beforeEach(() => { endUserAgreementService = jasmine.createSpyObj('endUserAgreementService', { hasCurrentUserAcceptedAgreement: observableOf(true) }); + authService = jasmine.createSpyObj('authService', ['setRedirectUrl']); router = jasmine.createSpyObj('router', { navigateByUrl: {}, parseUrl: new UrlTree() }); - guard = new EndUserAgreementGuard(endUserAgreementService, router); + guard = new EndUserAgreementGuard(endUserAgreementService, authService, router); }); describe('canActivate', () => { @@ -39,7 +42,8 @@ describe('EndUserAgreementGuard', () => { it('should navigate the user with a redirect url', (done) => { const redirect = 'redirect/url'; guard.canActivate(undefined, Object.assign({ url: redirect })).subscribe(() => { - expect(router.navigateByUrl).toHaveBeenCalledWith(jasmine.anything(), { state: { redirect } }); + expect(authService.setRedirectUrl).toHaveBeenCalledWith(redirect); + expect(router.navigateByUrl).toHaveBeenCalled(); done(); }); }); diff --git a/src/app/core/end-user-agreement/end-user-agreement.guard.ts b/src/app/core/end-user-agreement/end-user-agreement.guard.ts index 450385984a..e42d885133 100644 --- a/src/app/core/end-user-agreement/end-user-agreement.guard.ts +++ b/src/app/core/end-user-agreement/end-user-agreement.guard.ts @@ -4,6 +4,7 @@ import { Observable } from 'rxjs/internal/Observable'; import { returnEndUserAgreementUrlTreeOnFalse } from '../shared/operators'; import { EndUserAgreementService } from './end-user-agreement.service'; import { tap } from 'rxjs/operators'; +import { AuthService } from '../auth/auth.service'; /** * A guard redirecting users to the end agreement page when they haven't accepted the latest user agreement @@ -12,6 +13,7 @@ import { tap } from 'rxjs/operators'; export class EndUserAgreementGuard implements CanActivate { constructor(protected endUserAgreementService: EndUserAgreementService, + protected authService: AuthService, protected router: Router) { } @@ -26,7 +28,8 @@ export class EndUserAgreementGuard implements CanActivate { returnEndUserAgreementUrlTreeOnFalse(this.router), tap((result) => { if (result instanceof UrlTree) { - this.router.navigateByUrl(result, { state: { redirect: state.url } }) + this.authService.setRedirectUrl(state.url); + this.router.navigateByUrl(result); } }) ); diff --git a/src/app/info/end-user-agreement/end-user-agreement.component.spec.ts b/src/app/info/end-user-agreement/end-user-agreement.component.spec.ts index 5d6b3f904c..875d6e2dbd 100644 --- a/src/app/info/end-user-agreement/end-user-agreement.component.spec.ts +++ b/src/app/info/end-user-agreement/end-user-agreement.component.spec.ts @@ -24,19 +24,19 @@ describe('EndUserAgreementComponent', () => { let redirectUrl; function init() { + redirectUrl = 'redirect/url'; + endUserAgreementService = jasmine.createSpyObj('endUserAgreementService', { hasCurrentUserAcceptedAgreement: observableOf(false), setUserAcceptedAgreement: observableOf(true) }); notificationsService = jasmine.createSpyObj('notificationsService', ['success', 'error']); authService = jasmine.createSpyObj('authService', { - isAuthenticated: observableOf(true) + isAuthenticated: observableOf(true), + getRedirectUrl: observableOf(redirectUrl) }); store = jasmine.createSpyObj('store', ['dispatch']); router = jasmine.createSpyObj('router', ['navigate', 'navigateByUrl']); - - redirectUrl = 'redirect/url'; - window.history.pushState({ redirect: redirectUrl }, ''); } beforeEach(async(() => { diff --git a/src/app/info/end-user-agreement/end-user-agreement.component.ts b/src/app/info/end-user-agreement/end-user-agreement.component.ts index f86b0c3434..a60db6a496 100644 --- a/src/app/info/end-user-agreement/end-user-agreement.component.ts +++ b/src/app/info/end-user-agreement/end-user-agreement.component.ts @@ -1,6 +1,6 @@ import { Component, OnInit } from '@angular/core'; import { AuthService } from '../../core/auth/auth.service'; -import { take } from 'rxjs/operators'; +import { switchMap, take } from 'rxjs/operators'; import { Router } from '@angular/router'; import { Store } from '@ngrx/store'; import { AppState } from '../../app.reducer'; @@ -8,7 +8,8 @@ import { LogOutAction } from '../../core/auth/auth.actions'; import { EndUserAgreementService } from '../../core/end-user-agreement/end-user-agreement.service'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { TranslateService } from '@ngx-translate/core'; -import { hasValue } from '../../shared/empty.util'; +import { of as observableOf } from 'rxjs'; +import { isNotEmpty } from '../../shared/empty.util'; @Component({ selector: 'ds-end-user-agreement', @@ -54,15 +55,20 @@ export class EndUserAgreementComponent implements OnInit { * Set the End User Agreement, display a notification and (optionally) redirect the user back to their original destination */ submit() { - this.endUserAgreementService.setUserAcceptedAgreement(this.accepted).subscribe((success) => { - if (success) { - this.notificationsService.success(this.translate.instant('info.end-user-agreement.accept.success')); - const redirect = window.history.state.redirect; - if (hasValue(redirect)) { - this.router.navigateByUrl(redirect); + this.endUserAgreementService.setUserAcceptedAgreement(this.accepted).pipe( + switchMap((success) => { + if (success) { + this.notificationsService.success(this.translate.instant('info.end-user-agreement.accept.success')); + return this.authService.getRedirectUrl(); + } else { + this.notificationsService.error(this.translate.instant('info.end-user-agreement.accept.error')); + return observableOf(undefined); } - } else { - this.notificationsService.error(this.translate.instant('info.end-user-agreement.accept.error')); + }), + take(1) + ).subscribe((redirectUrl) => { + if (isNotEmpty(redirectUrl)) { + this.router.navigateByUrl(redirectUrl); } }); } From eda1a34da5459868405b0dc106d96cf1903884ab Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Thu, 3 Sep 2020 17:22:38 +0200 Subject: [PATCH 07/12] 72541: EndUserAgreementCurrentUserGuard and EndUserAgreementCookieGuard --- .../bitstream-page-routing.module.ts | 3 +- .../collection-page-routing.module.ts | 11 +++-- .../community-page-routing.module.ts | 7 ++-- .../+item-page/item-page-routing.module.ts | 5 +-- .../submit-page-routing.module.ts | 3 +- .../workflowitems-edit-page-routing.module.ts | 7 ++-- ...workspaceitems-edit-page-routing.module.ts | 3 +- src/app/app-routing.module.ts | 40 ++++++++++--------- src/app/core/core.module.ts | 6 ++- ...s => abstract-end-user-agreement.guard.ts} | 26 +++++++----- .../end-user-agreement-cookie.guard.ts | 28 +++++++++++++ ...user-agreement-current-user.guard.spec.ts} | 6 +-- .../end-user-agreement-current-user.guard.ts | 27 +++++++++++++ .../end-user-agreement.service.ts | 37 +++++++++++------ .../end-user-agreement.component.ts | 2 +- .../process-page-routing.module.ts | 3 +- .../register-page-routing.module.ts | 4 +- 17 files changed, 142 insertions(+), 76 deletions(-) rename src/app/core/end-user-agreement/{end-user-agreement.guard.ts => abstract-end-user-agreement.guard.ts} (64%) create mode 100644 src/app/core/end-user-agreement/end-user-agreement-cookie.guard.ts rename src/app/core/end-user-agreement/{end-user-agreement.guard.spec.ts => end-user-agreement-current-user.guard.spec.ts} (87%) create mode 100644 src/app/core/end-user-agreement/end-user-agreement-current-user.guard.ts diff --git a/src/app/+bitstream-page/bitstream-page-routing.module.ts b/src/app/+bitstream-page/bitstream-page-routing.module.ts index 0856dd00cb..14d688064c 100644 --- a/src/app/+bitstream-page/bitstream-page-routing.module.ts +++ b/src/app/+bitstream-page/bitstream-page-routing.module.ts @@ -3,7 +3,6 @@ import { RouterModule } from '@angular/router'; import { EditBitstreamPageComponent } from './edit-bitstream-page/edit-bitstream-page.component'; import { AuthenticatedGuard } from '../core/auth/authenticated.guard'; import { BitstreamPageResolver } from './bitstream-page.resolver'; -import { EndUserAgreementGuard } from '../core/end-user-agreement/end-user-agreement.guard'; const EDIT_BITSTREAM_PATH = ':id/edit'; @@ -19,7 +18,7 @@ const EDIT_BITSTREAM_PATH = ':id/edit'; resolve: { bitstream: BitstreamPageResolver }, - canActivate: [AuthenticatedGuard, EndUserAgreementGuard] + canActivate: [AuthenticatedGuard] } ]) ], diff --git a/src/app/+collection-page/collection-page-routing.module.ts b/src/app/+collection-page/collection-page-routing.module.ts index 13898055f9..479a136140 100644 --- a/src/app/+collection-page/collection-page-routing.module.ts +++ b/src/app/+collection-page/collection-page-routing.module.ts @@ -14,7 +14,6 @@ import { CollectionBreadcrumbResolver } from '../core/breadcrumbs/collection-bre import { DSOBreadcrumbsService } from '../core/breadcrumbs/dso-breadcrumbs.service'; import { LinkService } from '../core/cache/builders/link.service'; import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver'; -import { EndUserAgreementGuard } from '../core/end-user-agreement/end-user-agreement.guard'; import { ITEMTEMPLATE_PATH, COLLECTION_EDIT_PATH, @@ -27,7 +26,7 @@ import { { path: COLLECTION_CREATE_PATH, component: CreateCollectionPageComponent, - canActivate: [AuthenticatedGuard, CreateCollectionPageGuard, EndUserAgreementGuard] + canActivate: [AuthenticatedGuard, CreateCollectionPageGuard] }, { path: ':id', @@ -40,18 +39,18 @@ import { { path: COLLECTION_EDIT_PATH, loadChildren: './edit-collection-page/edit-collection-page.module#EditCollectionPageModule', - canActivate: [AuthenticatedGuard, EndUserAgreementGuard] + canActivate: [AuthenticatedGuard] }, { path: 'delete', pathMatch: 'full', component: DeleteCollectionPageComponent, - canActivate: [AuthenticatedGuard, EndUserAgreementGuard], + canActivate: [AuthenticatedGuard], }, { path: ITEMTEMPLATE_PATH, component: EditItemTemplatePageComponent, - canActivate: [AuthenticatedGuard, EndUserAgreementGuard], + canActivate: [AuthenticatedGuard], resolve: { item: ItemTemplatePageResolver, breadcrumb: I18nBreadcrumbResolver @@ -67,7 +66,7 @@ import { path: '/edit/mapper', component: CollectionItemMapperComponent, pathMatch: 'full', - canActivate: [AuthenticatedGuard, EndUserAgreementGuard] + canActivate: [AuthenticatedGuard] } ] }, diff --git a/src/app/+community-page/community-page-routing.module.ts b/src/app/+community-page/community-page-routing.module.ts index 050d24e9b4..08520ab8d4 100644 --- a/src/app/+community-page/community-page-routing.module.ts +++ b/src/app/+community-page/community-page-routing.module.ts @@ -10,7 +10,6 @@ import { DeleteCommunityPageComponent } from './delete-community-page/delete-com import { CommunityBreadcrumbResolver } from '../core/breadcrumbs/community-breadcrumb.resolver'; import { DSOBreadcrumbsService } from '../core/breadcrumbs/dso-breadcrumbs.service'; import { LinkService } from '../core/cache/builders/link.service'; -import { EndUserAgreementGuard } from '../core/end-user-agreement/end-user-agreement.guard'; import { COMMUNITY_EDIT_PATH, COMMUNITY_CREATE_PATH } from './community-page-routing-paths'; @NgModule({ @@ -19,7 +18,7 @@ import { COMMUNITY_EDIT_PATH, COMMUNITY_CREATE_PATH } from './community-page-rou { path: COMMUNITY_CREATE_PATH, component: CreateCommunityPageComponent, - canActivate: [AuthenticatedGuard, CreateCommunityPageGuard, EndUserAgreementGuard] + canActivate: [AuthenticatedGuard, CreateCommunityPageGuard] }, { path: ':id', @@ -32,13 +31,13 @@ import { COMMUNITY_EDIT_PATH, COMMUNITY_CREATE_PATH } from './community-page-rou { path: COMMUNITY_EDIT_PATH, loadChildren: './edit-community-page/edit-community-page.module#EditCommunityPageModule', - canActivate: [AuthenticatedGuard, EndUserAgreementGuard] + canActivate: [AuthenticatedGuard] }, { path: 'delete', pathMatch: 'full', component: DeleteCommunityPageComponent, - canActivate: [AuthenticatedGuard, EndUserAgreementGuard], + canActivate: [AuthenticatedGuard], }, { path: '', diff --git a/src/app/+item-page/item-page-routing.module.ts b/src/app/+item-page/item-page-routing.module.ts index 5b8a3cb293..088aab326d 100644 --- a/src/app/+item-page/item-page-routing.module.ts +++ b/src/app/+item-page/item-page-routing.module.ts @@ -9,7 +9,6 @@ import { ItemBreadcrumbResolver } from '../core/breadcrumbs/item-breadcrumb.reso import { DSOBreadcrumbsService } from '../core/breadcrumbs/dso-breadcrumbs.service'; import { LinkService } from '../core/cache/builders/link.service'; import { UploadBitstreamComponent } from './bitstreams/upload/upload-bitstream.component'; -import { EndUserAgreementGuard } from '../core/end-user-agreement/end-user-agreement.guard'; import { UPLOAD_BITSTREAM_PATH, ITEM_EDIT_PATH } from './item-page-routing-paths'; @NgModule({ @@ -35,12 +34,12 @@ import { UPLOAD_BITSTREAM_PATH, ITEM_EDIT_PATH } from './item-page-routing-paths { path: ITEM_EDIT_PATH, loadChildren: './edit-item-page/edit-item-page.module#EditItemPageModule', - canActivate: [AuthenticatedGuard, EndUserAgreementGuard] + canActivate: [AuthenticatedGuard] }, { path: UPLOAD_BITSTREAM_PATH, component: UploadBitstreamComponent, - canActivate: [AuthenticatedGuard, EndUserAgreementGuard] + canActivate: [AuthenticatedGuard] } ], } diff --git a/src/app/+submit-page/submit-page-routing.module.ts b/src/app/+submit-page/submit-page-routing.module.ts index a66e2636a1..7a123bfc31 100644 --- a/src/app/+submit-page/submit-page-routing.module.ts +++ b/src/app/+submit-page/submit-page-routing.module.ts @@ -3,13 +3,12 @@ import { RouterModule } from '@angular/router'; import { AuthenticatedGuard } from '../core/auth/authenticated.guard'; import { SubmissionSubmitComponent } from '../submission/submit/submission-submit.component'; -import { EndUserAgreementGuard } from '../core/end-user-agreement/end-user-agreement.guard'; @NgModule({ imports: [ RouterModule.forChild([ { - canActivate: [AuthenticatedGuard, EndUserAgreementGuard], + canActivate: [AuthenticatedGuard], path: '', pathMatch: 'full', component: SubmissionSubmitComponent, diff --git a/src/app/+workflowitems-edit-page/workflowitems-edit-page-routing.module.ts b/src/app/+workflowitems-edit-page/workflowitems-edit-page-routing.module.ts index 7f3b9026f2..27b7fe1199 100644 --- a/src/app/+workflowitems-edit-page/workflowitems-edit-page-routing.module.ts +++ b/src/app/+workflowitems-edit-page/workflowitems-edit-page-routing.module.ts @@ -6,7 +6,6 @@ import { SubmissionEditComponent } from '../submission/edit/submission-edit.comp import { WorkflowItemDeleteComponent } from './workflow-item-delete/workflow-item-delete.component'; import { WorkflowItemPageResolver } from './workflow-item-page.resolver'; import { WorkflowItemSendBackComponent } from './workflow-item-send-back/workflow-item-send-back.component'; -import { EndUserAgreementGuard } from '../core/end-user-agreement/end-user-agreement.guard'; import { WORKFLOW_ITEM_SEND_BACK_PATH, WORKFLOW_ITEM_DELETE_PATH, @@ -21,19 +20,19 @@ import { resolve: { wfi: WorkflowItemPageResolver }, children: [ { - canActivate: [AuthenticatedGuard, EndUserAgreementGuard], + canActivate: [AuthenticatedGuard], path: WORKFLOW_ITEM_EDIT_PATH, component: SubmissionEditComponent, data: { title: 'submission.edit.title' } }, { - canActivate: [AuthenticatedGuard, EndUserAgreementGuard], + canActivate: [AuthenticatedGuard], path: WORKFLOW_ITEM_DELETE_PATH, component: WorkflowItemDeleteComponent, data: { title: 'workflow-item.delete.title' } }, { - canActivate: [AuthenticatedGuard, EndUserAgreementGuard], + canActivate: [AuthenticatedGuard], path: WORKFLOW_ITEM_SEND_BACK_PATH, component: WorkflowItemSendBackComponent, data: { title: 'workflow-item.send-back.title' } diff --git a/src/app/+workspaceitems-edit-page/workspaceitems-edit-page-routing.module.ts b/src/app/+workspaceitems-edit-page/workspaceitems-edit-page-routing.module.ts index 0548b488c4..d10c53e138 100644 --- a/src/app/+workspaceitems-edit-page/workspaceitems-edit-page-routing.module.ts +++ b/src/app/+workspaceitems-edit-page/workspaceitems-edit-page-routing.module.ts @@ -3,14 +3,13 @@ import { RouterModule } from '@angular/router'; import { AuthenticatedGuard } from '../core/auth/authenticated.guard'; import { SubmissionEditComponent } from '../submission/edit/submission-edit.component'; -import { EndUserAgreementGuard } from '../core/end-user-agreement/end-user-agreement.guard'; @NgModule({ imports: [ RouterModule.forChild([ { path: '', redirectTo: '/home', pathMatch: 'full' }, { - canActivate: [AuthenticatedGuard, EndUserAgreementGuard], + canActivate: [AuthenticatedGuard], path: ':id/edit', component: SubmissionEditComponent, data: { title: 'submission.edit.title' } diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 13afd0423e..0851b1e1f8 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -20,7 +20,7 @@ import { COLLECTION_MODULE_PATH } from './+collection-page/collection-page-routi import { COMMUNITY_MODULE_PATH } from './+community-page/community-page-routing-paths'; import { ITEM_MODULE_PATH } from './+item-page/item-page-routing-paths'; import { ReloadGuard } from './core/reload/reload.guard'; -import { EndUserAgreementGuard } from './core/end-user-agreement/end-user-agreement.guard'; +import { EndUserAgreementCurrentUserGuard } from './core/end-user-agreement/end-user-agreement-current-user.guard'; @NgModule({ imports: [ @@ -29,40 +29,42 @@ import { EndUserAgreementGuard } from './core/end-user-agreement/end-user-agreem children: [ { path: '', redirectTo: '/home', pathMatch: 'full' }, { path: 'reload/:rnd', component: PageNotFoundComponent, pathMatch: 'full', canActivate: [ReloadGuard] }, - { path: 'home', loadChildren: './+home-page/home-page.module#HomePageModule', data: { showBreadcrumbs: false } }, - { path: 'community-list', loadChildren: './community-list-page/community-list-page.module#CommunityListPageModule' }, - { path: 'id', loadChildren: './+lookup-by-id/lookup-by-id.module#LookupIdModule' }, - { path: 'handle', loadChildren: './+lookup-by-id/lookup-by-id.module#LookupIdModule' }, + { path: 'home', loadChildren: './+home-page/home-page.module#HomePageModule', data: { showBreadcrumbs: false }, canActivate: [EndUserAgreementCurrentUserGuard] }, + { path: 'community-list', loadChildren: './community-list-page/community-list-page.module#CommunityListPageModule', canActivate: [EndUserAgreementCurrentUserGuard] }, + { path: 'id', loadChildren: './+lookup-by-id/lookup-by-id.module#LookupIdModule', canActivate: [EndUserAgreementCurrentUserGuard] }, + { path: 'handle', loadChildren: './+lookup-by-id/lookup-by-id.module#LookupIdModule', canActivate: [EndUserAgreementCurrentUserGuard] }, { path: REGISTER_PATH, loadChildren: './register-page/register-page.module#RegisterPageModule' }, - { path: FORGOT_PASSWORD_PATH, loadChildren: './forgot-password/forgot-password.module#ForgotPasswordModule' }, - { path: COMMUNITY_MODULE_PATH, loadChildren: './+community-page/community-page.module#CommunityPageModule' }, - { path: COLLECTION_MODULE_PATH, loadChildren: './+collection-page/collection-page.module#CollectionPageModule' }, - { path: ITEM_MODULE_PATH, loadChildren: './+item-page/item-page.module#ItemPageModule' }, - { path: BITSTREAM_MODULE_PATH, loadChildren: './+bitstream-page/bitstream-page.module#BitstreamPageModule' }, + { path: FORGOT_PASSWORD_PATH, loadChildren: './forgot-password/forgot-password.module#ForgotPasswordModule', canActivate: [EndUserAgreementCurrentUserGuard] }, + { path: COMMUNITY_MODULE_PATH, loadChildren: './+community-page/community-page.module#CommunityPageModule', canActivate: [EndUserAgreementCurrentUserGuard] }, + { path: COLLECTION_MODULE_PATH, loadChildren: './+collection-page/collection-page.module#CollectionPageModule', canActivate: [EndUserAgreementCurrentUserGuard] }, + { path: ITEM_MODULE_PATH, loadChildren: './+item-page/item-page.module#ItemPageModule', canActivate: [EndUserAgreementCurrentUserGuard] }, + { path: BITSTREAM_MODULE_PATH, loadChildren: './+bitstream-page/bitstream-page.module#BitstreamPageModule', canActivate: [EndUserAgreementCurrentUserGuard] }, { path: 'mydspace', loadChildren: './+my-dspace-page/my-dspace-page.module#MyDSpacePageModule', - canActivate: [AuthenticatedGuard, EndUserAgreementGuard] + canActivate: [AuthenticatedGuard, EndUserAgreementCurrentUserGuard] }, - { path: 'search', loadChildren: './+search-page/search-page-routing.module#SearchPageRoutingModule' }, - { path: 'browse', loadChildren: './+browse-by/browse-by.module#BrowseByModule'}, - { path: ADMIN_MODULE_PATH, loadChildren: './+admin/admin.module#AdminModule', canActivate: [SiteAdministratorGuard, EndUserAgreementGuard] }, + { path: 'search', loadChildren: './+search-page/search-page-routing.module#SearchPageRoutingModule', canActivate: [EndUserAgreementCurrentUserGuard] }, + { path: 'browse', loadChildren: './+browse-by/browse-by.module#BrowseByModule', canActivate: [EndUserAgreementCurrentUserGuard] }, + { path: ADMIN_MODULE_PATH, loadChildren: './+admin/admin.module#AdminModule', canActivate: [SiteAdministratorGuard, EndUserAgreementCurrentUserGuard] }, { path: 'login', loadChildren: './+login-page/login-page.module#LoginPageModule' }, { path: 'logout', loadChildren: './+logout-page/logout-page.module#LogoutPageModule' }, - { path: 'submit', loadChildren: './+submit-page/submit-page.module#SubmitPageModule' }, + { path: 'submit', loadChildren: './+submit-page/submit-page.module#SubmitPageModule', canActivate: [EndUserAgreementCurrentUserGuard] }, { path: 'workspaceitems', - loadChildren: './+workspaceitems-edit-page/workspaceitems-edit-page.module#WorkspaceitemsEditPageModule' + loadChildren: './+workspaceitems-edit-page/workspaceitems-edit-page.module#WorkspaceitemsEditPageModule', + canActivate: [EndUserAgreementCurrentUserGuard] }, { path: WORKFLOW_ITEM_MODULE_PATH, - loadChildren: './+workflowitems-edit-page/workflowitems-edit-page.module#WorkflowItemsEditPageModule' + loadChildren: './+workflowitems-edit-page/workflowitems-edit-page.module#WorkflowItemsEditPageModule', + canActivate: [EndUserAgreementCurrentUserGuard] }, { path: PROFILE_MODULE_PATH, - loadChildren: './profile-page/profile-page.module#ProfilePageModule', canActivate: [AuthenticatedGuard, EndUserAgreementGuard] + loadChildren: './profile-page/profile-page.module#ProfilePageModule', canActivate: [AuthenticatedGuard, EndUserAgreementCurrentUserGuard] }, - { path: 'processes', loadChildren: './process-page/process-page.module#ProcessPageModule', canActivate: [AuthenticatedGuard, EndUserAgreementGuard] }, + { path: 'processes', loadChildren: './process-page/process-page.module#ProcessPageModule', canActivate: [AuthenticatedGuard, EndUserAgreementCurrentUserGuard] }, { path: INFO_MODULE_PATH, loadChildren: './info/info.module#InfoModule' }, { path: UNAUTHORIZED_PATH, component: UnauthorizedComponent }, { path: '**', pathMatch: 'full', component: PageNotFoundComponent }, diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index 7eed3baf91..e35f2c9b88 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -163,7 +163,8 @@ import { SubmissionCcLicenseUrlDataService } from './submission/submission-cc-li import { ConfigurationDataService } from './data/configuration-data.service'; import { ConfigurationProperty } from './shared/configuration-property.model'; import { ReloadGuard } from './reload/reload.guard'; -import { EndUserAgreementGuard } from './end-user-agreement/end-user-agreement.guard'; +import { EndUserAgreementCurrentUserGuard } from './end-user-agreement/end-user-agreement-current-user.guard'; +import { EndUserAgreementCookieGuard } from './end-user-agreement/end-user-agreement-cookie.guard'; import { EndUserAgreementService } from './end-user-agreement/end-user-agreement.service'; /** @@ -293,7 +294,8 @@ const PROVIDERS = [ MetadataFieldDataService, TokenResponseParsingService, ReloadGuard, - EndUserAgreementGuard, + EndUserAgreementCurrentUserGuard, + EndUserAgreementCookieGuard, EndUserAgreementService, // register AuthInterceptor as HttpInterceptor { diff --git a/src/app/core/end-user-agreement/end-user-agreement.guard.ts b/src/app/core/end-user-agreement/abstract-end-user-agreement.guard.ts similarity index 64% rename from src/app/core/end-user-agreement/end-user-agreement.guard.ts rename to src/app/core/end-user-agreement/abstract-end-user-agreement.guard.ts index e42d885133..2f6bde11b9 100644 --- a/src/app/core/end-user-agreement/end-user-agreement.guard.ts +++ b/src/app/core/end-user-agreement/abstract-end-user-agreement.guard.ts @@ -1,30 +1,27 @@ -import { Injectable } from '@angular/core'; import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot, UrlTree } from '@angular/router'; import { Observable } from 'rxjs/internal/Observable'; -import { returnEndUserAgreementUrlTreeOnFalse } from '../shared/operators'; -import { EndUserAgreementService } from './end-user-agreement.service'; -import { tap } from 'rxjs/operators'; import { AuthService } from '../auth/auth.service'; +import { returnEndUserAgreementUrlTreeOnFalse } from '../shared/operators'; +import { tap } from 'rxjs/operators'; /** - * A guard redirecting users to the end agreement page when they haven't accepted the latest user agreement + * An abstract guard for redirecting users to the user agreement page if a certain condition is met + * That condition is defined by abstract method hasAccepted */ -@Injectable() -export class EndUserAgreementGuard implements CanActivate { +export abstract class AbstractEndUserAgreementGuard implements CanActivate { - constructor(protected endUserAgreementService: EndUserAgreementService, - protected authService: AuthService, + constructor(protected authService: AuthService, protected router: Router) { } /** - * True when the user has accepted the agreements + * True when the user agreement has been accepted * The user will be redirected to the End User Agreement page if they haven't accepted it before * A redirect URL will be provided with the navigation so the component can redirect the user back to the blocked route * when they're finished accepting the agreement */ canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { - return this.endUserAgreementService.hasCurrentUserAcceptedAgreement().pipe( + return this.hasAccepted().pipe( returnEndUserAgreementUrlTreeOnFalse(this.router), tap((result) => { if (result instanceof UrlTree) { @@ -34,4 +31,11 @@ export class EndUserAgreementGuard implements CanActivate { }) ); } + + /** + * This abstract method determines how the User Agreement has to be accepted before the user is allowed to visit + * the desired route + */ + abstract hasAccepted(): Observable; + } diff --git a/src/app/core/end-user-agreement/end-user-agreement-cookie.guard.ts b/src/app/core/end-user-agreement/end-user-agreement-cookie.guard.ts new file mode 100644 index 0000000000..a7bdd6b320 --- /dev/null +++ b/src/app/core/end-user-agreement/end-user-agreement-cookie.guard.ts @@ -0,0 +1,28 @@ +import { Injectable } from '@angular/core'; +import { AbstractEndUserAgreementGuard } from './abstract-end-user-agreement.guard'; +import { Observable } from 'rxjs/internal/Observable'; +import { of as observableOf } from 'rxjs'; +import { EndUserAgreementService } from './end-user-agreement.service'; +import { AuthService } from '../auth/auth.service'; +import { Router } from '@angular/router'; + +/** + * A guard redirecting users to the end agreement page when the user agreement cookie hasn't been accepted + */ +@Injectable() +export class EndUserAgreementCookieGuard extends AbstractEndUserAgreementGuard { + + constructor(protected endUserAgreementService: EndUserAgreementService, + protected authService: AuthService, + protected router: Router) { + super(authService, router); + } + + /** + * True when the user agreement cookie has been accepted + */ + hasAccepted(): Observable { + return observableOf(this.endUserAgreementService.isCookieAccepted()); + } + +} diff --git a/src/app/core/end-user-agreement/end-user-agreement.guard.spec.ts b/src/app/core/end-user-agreement/end-user-agreement-current-user.guard.spec.ts similarity index 87% rename from src/app/core/end-user-agreement/end-user-agreement.guard.spec.ts rename to src/app/core/end-user-agreement/end-user-agreement-current-user.guard.spec.ts index a7f3f32a6b..09cb236b3f 100644 --- a/src/app/core/end-user-agreement/end-user-agreement.guard.spec.ts +++ b/src/app/core/end-user-agreement/end-user-agreement-current-user.guard.spec.ts @@ -1,11 +1,11 @@ -import { EndUserAgreementGuard } from './end-user-agreement.guard'; +import { EndUserAgreementCurrentUserGuard } from './end-user-agreement-current-user.guard'; import { EndUserAgreementService } from './end-user-agreement.service'; import { Router, UrlTree } from '@angular/router'; import { of as observableOf } from 'rxjs'; import { AuthService } from '../auth/auth.service'; describe('EndUserAgreementGuard', () => { - let guard: EndUserAgreementGuard; + let guard: EndUserAgreementCurrentUserGuard; let endUserAgreementService: EndUserAgreementService; let authService: AuthService; @@ -21,7 +21,7 @@ describe('EndUserAgreementGuard', () => { parseUrl: new UrlTree() }); - guard = new EndUserAgreementGuard(endUserAgreementService, authService, router); + guard = new EndUserAgreementCurrentUserGuard(endUserAgreementService, authService, router); }); describe('canActivate', () => { diff --git a/src/app/core/end-user-agreement/end-user-agreement-current-user.guard.ts b/src/app/core/end-user-agreement/end-user-agreement-current-user.guard.ts new file mode 100644 index 0000000000..e48d4361c6 --- /dev/null +++ b/src/app/core/end-user-agreement/end-user-agreement-current-user.guard.ts @@ -0,0 +1,27 @@ +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs/internal/Observable'; +import { AbstractEndUserAgreementGuard } from './abstract-end-user-agreement.guard'; +import { EndUserAgreementService } from './end-user-agreement.service'; +import { AuthService } from '../auth/auth.service'; +import { Router } from '@angular/router'; + +/** + * A guard redirecting logged in users to the end agreement page when they haven't accepted the latest user agreement + */ +@Injectable() +export class EndUserAgreementCurrentUserGuard extends AbstractEndUserAgreementGuard { + + constructor(protected endUserAgreementService: EndUserAgreementService, + protected authService: AuthService, + protected router: Router) { + super(authService, router); + } + + /** + * True when the currently logged in user has accepted the agreements or when the user is not currently authenticated + */ + hasAccepted(): Observable { + return this.endUserAgreementService.hasCurrentUserAcceptedAgreement(true); + } + +} diff --git a/src/app/core/end-user-agreement/end-user-agreement.service.ts b/src/app/core/end-user-agreement/end-user-agreement.service.ts index 025fc6fc32..31b606cfb4 100644 --- a/src/app/core/end-user-agreement/end-user-agreement.service.ts +++ b/src/app/core/end-user-agreement/end-user-agreement.service.ts @@ -25,26 +25,37 @@ export class EndUserAgreementService { } /** - * Whether or not the current user has accepted the End User Agreement + * Whether or not either the cookie was accepted or the current user has accepted the End User Agreement + * @param acceptedWhenAnonymous Whether or not the user agreement should be considered accepted if the user is + * currently not authenticated (anonymous) */ - hasCurrentUserAcceptedAgreement(): Observable { + hasCurrentUserOrCookieAcceptedAgreement(acceptedWhenAnonymous: boolean): Observable { if (this.isCookieAccepted()) { return observableOf(true); } else { - return this.authService.isAuthenticated().pipe( - switchMap((authenticated) => { - if (authenticated) { - return this.authService.getAuthenticatedUserFromStore().pipe( - map((user) => hasValue(user) && user.hasMetadata(END_USER_AGREEMENT_METADATA_FIELD) && user.firstMetadata(END_USER_AGREEMENT_METADATA_FIELD).value === 'true') - ); - } else { - return observableOf(false); - } - }) - ); + return this.hasCurrentUserAcceptedAgreement(acceptedWhenAnonymous); } } + /** + * Whether or not the current user has accepted the End User Agreement + * @param acceptedWhenAnonymous Whether or not the user agreement should be considered accepted if the user is + * currently not authenticated (anonymous) + */ + hasCurrentUserAcceptedAgreement(acceptedWhenAnonymous: boolean): Observable { + return this.authService.isAuthenticated().pipe( + switchMap((authenticated) => { + if (authenticated) { + return this.authService.getAuthenticatedUserFromStore().pipe( + map((user) => hasValue(user) && user.hasMetadata(END_USER_AGREEMENT_METADATA_FIELD) && user.firstMetadata(END_USER_AGREEMENT_METADATA_FIELD).value === 'true') + ); + } else { + return observableOf(acceptedWhenAnonymous); + } + }) + ); + } + /** * Set the current user's accepted agreement status * When a user is authenticated, set his/her metadata to the provided value diff --git a/src/app/info/end-user-agreement/end-user-agreement.component.ts b/src/app/info/end-user-agreement/end-user-agreement.component.ts index a60db6a496..e258ba8878 100644 --- a/src/app/info/end-user-agreement/end-user-agreement.component.ts +++ b/src/app/info/end-user-agreement/end-user-agreement.component.ts @@ -45,7 +45,7 @@ export class EndUserAgreementComponent implements OnInit { * Initialize the "accepted" property of this component by checking if the current user has accepted it before */ initAccepted() { - this.endUserAgreementService.hasCurrentUserAcceptedAgreement().subscribe((accepted) => { + this.endUserAgreementService.hasCurrentUserOrCookieAcceptedAgreement(false).subscribe((accepted) => { this.accepted = accepted; }); } diff --git a/src/app/process-page/process-page-routing.module.ts b/src/app/process-page/process-page-routing.module.ts index 6e9ecc4b29..881ca0e853 100644 --- a/src/app/process-page/process-page-routing.module.ts +++ b/src/app/process-page/process-page-routing.module.ts @@ -7,7 +7,6 @@ import { ProcessDetailComponent } from './detail/process-detail.component'; import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver'; import { ProcessBreadcrumbResolver } from './process-breadcrumb.resolver'; import { AuthenticatedGuard } from '../core/auth/authenticated.guard'; -import { EndUserAgreementGuard } from '../core/end-user-agreement/end-user-agreement.guard'; @NgModule({ imports: [ @@ -16,7 +15,7 @@ import { EndUserAgreementGuard } from '../core/end-user-agreement/end-user-agree path: '', resolve: { breadcrumb: I18nBreadcrumbResolver }, data: { breadcrumbKey: 'process.overview' }, - canActivate: [AuthenticatedGuard, EndUserAgreementGuard], + canActivate: [AuthenticatedGuard], children: [ { path: '', diff --git a/src/app/register-page/register-page-routing.module.ts b/src/app/register-page/register-page-routing.module.ts index a57450a329..7954d7963a 100644 --- a/src/app/register-page/register-page-routing.module.ts +++ b/src/app/register-page/register-page-routing.module.ts @@ -4,7 +4,7 @@ import { RegisterEmailComponent } from './register-email/register-email.componen import { CreateProfileComponent } from './create-profile/create-profile.component'; import { ItemPageResolver } from '../+item-page/item-page.resolver'; import { RegistrationResolver } from '../register-email-form/registration.resolver'; -import { EndUserAgreementGuard } from '../core/end-user-agreement/end-user-agreement.guard'; +import { EndUserAgreementCookieGuard } from '../core/end-user-agreement/end-user-agreement-cookie.guard'; @NgModule({ imports: [ @@ -18,7 +18,7 @@ import { EndUserAgreementGuard } from '../core/end-user-agreement/end-user-agree path: ':token', component: CreateProfileComponent, resolve: {registration: RegistrationResolver}, - canActivate: [EndUserAgreementGuard] + canActivate: [EndUserAgreementCookieGuard] } ]) ], From bd522038a1b8a9e7d276dddbcc9c9aaabfc68580 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Thu, 3 Sep 2020 17:52:52 +0200 Subject: [PATCH 08/12] 72541: Additional tests and fixes --- .../end-user-agreement-cookie.guard.spec.ts | 51 +++++++++++++++++++ .../end-user-agreement.service.spec.ts | 16 +++--- .../end-user-agreement.component.spec.ts | 6 +-- 3 files changed, 62 insertions(+), 11 deletions(-) create mode 100644 src/app/core/end-user-agreement/end-user-agreement-cookie.guard.spec.ts diff --git a/src/app/core/end-user-agreement/end-user-agreement-cookie.guard.spec.ts b/src/app/core/end-user-agreement/end-user-agreement-cookie.guard.spec.ts new file mode 100644 index 0000000000..feeb8d7903 --- /dev/null +++ b/src/app/core/end-user-agreement/end-user-agreement-cookie.guard.spec.ts @@ -0,0 +1,51 @@ +import { EndUserAgreementService } from './end-user-agreement.service'; +import { Router, UrlTree } from '@angular/router'; +import { AuthService } from '../auth/auth.service'; +import { EndUserAgreementCookieGuard } from './end-user-agreement-cookie.guard'; + +describe('EndUserAgreementCookieGuard', () => { + let guard: EndUserAgreementCookieGuard; + + let endUserAgreementService: EndUserAgreementService; + let authService: AuthService; + let router: Router; + + beforeEach(() => { + endUserAgreementService = jasmine.createSpyObj('endUserAgreementService', { + isCookieAccepted: true + }); + authService = jasmine.createSpyObj('authService', ['setRedirectUrl']); + router = jasmine.createSpyObj('router', { + navigateByUrl: {}, + parseUrl: new UrlTree() + }); + + guard = new EndUserAgreementCookieGuard(endUserAgreementService, authService, router); + }); + + describe('canActivate', () => { + describe('when the cookie has been accepted', () => { + it('should return true', (done) => { + guard.canActivate(undefined, undefined).subscribe((result) => { + expect(result).toEqual(true); + done(); + }); + }); + }); + + describe('when the cookie hasn\'t been accepted', () => { + beforeEach(() => { + (endUserAgreementService.isCookieAccepted as jasmine.Spy).and.returnValue(false); + }); + + it('should navigate the user with a redirect url', (done) => { + const redirect = 'redirect/url'; + guard.canActivate(undefined, Object.assign({ url: redirect })).subscribe(() => { + expect(authService.setRedirectUrl).toHaveBeenCalledWith(redirect); + expect(router.navigateByUrl).toHaveBeenCalled(); + done(); + }); + }); + }); + }); +}); diff --git a/src/app/core/end-user-agreement/end-user-agreement.service.spec.ts b/src/app/core/end-user-agreement/end-user-agreement.service.spec.ts index a292b2371c..4b0a37a94f 100644 --- a/src/app/core/end-user-agreement/end-user-agreement.service.spec.ts +++ b/src/app/core/end-user-agreement/end-user-agreement.service.spec.ts @@ -47,8 +47,8 @@ describe('EndUserAgreementService', () => { cookie.set(END_USER_AGREEMENT_COOKIE, true); }); - it('hasCurrentUserAcceptedAgreement should return true', (done) => { - service.hasCurrentUserAcceptedAgreement().subscribe((result) => { + it('hasCurrentUserOrCookieAcceptedAgreement should return true', (done) => { + service.hasCurrentUserOrCookieAcceptedAgreement(false).subscribe((result) => { expect(result).toEqual(true); done(); }); @@ -75,8 +75,8 @@ describe('EndUserAgreementService', () => { (authService.getAuthenticatedUserFromStore as jasmine.Spy).and.returnValue(observableOf(userWithMetadata)); }); - it('hasCurrentUserAcceptedAgreement should return true', (done) => { - service.hasCurrentUserAcceptedAgreement().subscribe((result) => { + it('hasCurrentUserOrCookieAcceptedAgreement should return true', (done) => { + service.hasCurrentUserOrCookieAcceptedAgreement(false).subscribe((result) => { expect(result).toEqual(true); done(); }); @@ -88,8 +88,8 @@ describe('EndUserAgreementService', () => { (authService.getAuthenticatedUserFromStore as jasmine.Spy).and.returnValue(observableOf(userWithoutMetadata)); }); - it('hasCurrentUserAcceptedAgreement should return false', (done) => { - service.hasCurrentUserAcceptedAgreement().subscribe((result) => { + it('hasCurrentUserOrCookieAcceptedAgreement should return false', (done) => { + service.hasCurrentUserOrCookieAcceptedAgreement(false).subscribe((result) => { expect(result).toEqual(false); done(); }); @@ -117,8 +117,8 @@ describe('EndUserAgreementService', () => { (authService.isAuthenticated as jasmine.Spy).and.returnValue(observableOf(false)); }); - it('hasCurrentUserAcceptedAgreement should return false', (done) => { - service.hasCurrentUserAcceptedAgreement().subscribe((result) => { + it('hasCurrentUserOrCookieAcceptedAgreement should return false', (done) => { + service.hasCurrentUserOrCookieAcceptedAgreement(false).subscribe((result) => { expect(result).toEqual(false); done(); }); diff --git a/src/app/info/end-user-agreement/end-user-agreement.component.spec.ts b/src/app/info/end-user-agreement/end-user-agreement.component.spec.ts index 875d6e2dbd..33cd0bfd98 100644 --- a/src/app/info/end-user-agreement/end-user-agreement.component.spec.ts +++ b/src/app/info/end-user-agreement/end-user-agreement.component.spec.ts @@ -27,7 +27,7 @@ describe('EndUserAgreementComponent', () => { redirectUrl = 'redirect/url'; endUserAgreementService = jasmine.createSpyObj('endUserAgreementService', { - hasCurrentUserAcceptedAgreement: observableOf(false), + hasCurrentUserOrCookieAcceptedAgreement : observableOf(false), setUserAcceptedAgreement: observableOf(true) }); notificationsService = jasmine.createSpyObj('notificationsService', ['success', 'error']); @@ -63,7 +63,7 @@ describe('EndUserAgreementComponent', () => { describe('when the user hasn\'t accepted the agreement', () => { beforeEach(() => { - (endUserAgreementService.hasCurrentUserAcceptedAgreement as jasmine.Spy).and.returnValue(observableOf(false)); + (endUserAgreementService.hasCurrentUserOrCookieAcceptedAgreement as jasmine.Spy).and.returnValue(observableOf(false)); component.ngOnInit(); fixture.detectChanges(); }); @@ -80,7 +80,7 @@ describe('EndUserAgreementComponent', () => { describe('when the user has accepted the agreement', () => { beforeEach(() => { - (endUserAgreementService.hasCurrentUserAcceptedAgreement as jasmine.Spy).and.returnValue(observableOf(true)); + (endUserAgreementService.hasCurrentUserOrCookieAcceptedAgreement as jasmine.Spy).and.returnValue(observableOf(true)); component.ngOnInit(); fixture.detectChanges(); }); From aa1b568bf437bbecf264679e87ea5ac33546c7bc Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Fri, 4 Sep 2020 17:46:05 +0200 Subject: [PATCH 09/12] 72541: Redirect through query param at EndUserAgreement --- .../abstract-end-user-agreement.guard.ts | 9 +-------- src/app/core/shared/operators.ts | 5 +++-- .../end-user-agreement.component.ts | 13 +++++++------ 3 files changed, 11 insertions(+), 16 deletions(-) diff --git a/src/app/core/end-user-agreement/abstract-end-user-agreement.guard.ts b/src/app/core/end-user-agreement/abstract-end-user-agreement.guard.ts index 2f6bde11b9..7fc4cc3172 100644 --- a/src/app/core/end-user-agreement/abstract-end-user-agreement.guard.ts +++ b/src/app/core/end-user-agreement/abstract-end-user-agreement.guard.ts @@ -2,7 +2,6 @@ import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot, UrlTr import { Observable } from 'rxjs/internal/Observable'; import { AuthService } from '../auth/auth.service'; import { returnEndUserAgreementUrlTreeOnFalse } from '../shared/operators'; -import { tap } from 'rxjs/operators'; /** * An abstract guard for redirecting users to the user agreement page if a certain condition is met @@ -22,13 +21,7 @@ export abstract class AbstractEndUserAgreementGuard implements CanActivate { */ canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { return this.hasAccepted().pipe( - returnEndUserAgreementUrlTreeOnFalse(this.router), - tap((result) => { - if (result instanceof UrlTree) { - this.authService.setRedirectUrl(state.url); - this.router.navigateByUrl(result); - } - }) + returnEndUserAgreementUrlTreeOnFalse(this.router, state.url) ); } diff --git a/src/app/core/shared/operators.ts b/src/app/core/shared/operators.ts index e9fe5fea2c..1018cee4ab 100644 --- a/src/app/core/shared/operators.ts +++ b/src/app/core/shared/operators.ts @@ -197,11 +197,12 @@ export const returnUnauthorizedUrlTreeOnFalse = (router: Router) => * Operator that returns a UrlTree to the unauthorized page when the boolean received is false * @param router */ -export const returnEndUserAgreementUrlTreeOnFalse = (router: Router) => +export const returnEndUserAgreementUrlTreeOnFalse = (router: Router, redirect: string) => (source: Observable): Observable => source.pipe( map((hasAgreed: boolean) => { - return hasAgreed ? hasAgreed : router.parseUrl(getEndUserAgreementPath()) + const queryParams = { redirect: encodeURIComponent(redirect) }; + return hasAgreed ? hasAgreed : router.createUrlTree([getEndUserAgreementPath()], { queryParams }); })); export const getFinishedRemoteData = () => diff --git a/src/app/info/end-user-agreement/end-user-agreement.component.ts b/src/app/info/end-user-agreement/end-user-agreement.component.ts index e258ba8878..539ca3308c 100644 --- a/src/app/info/end-user-agreement/end-user-agreement.component.ts +++ b/src/app/info/end-user-agreement/end-user-agreement.component.ts @@ -1,7 +1,7 @@ import { Component, OnInit } from '@angular/core'; import { AuthService } from '../../core/auth/auth.service'; -import { switchMap, take } from 'rxjs/operators'; -import { Router } from '@angular/router'; +import { map, switchMap, take } from 'rxjs/operators'; +import { ActivatedRoute, Router } from '@angular/router'; import { Store } from '@ngrx/store'; import { AppState } from '../../app.reducer'; import { LogOutAction } from '../../core/auth/auth.actions'; @@ -9,7 +9,7 @@ import { EndUserAgreementService } from '../../core/end-user-agreement/end-user- import { NotificationsService } from '../../shared/notifications/notifications.service'; import { TranslateService } from '@ngx-translate/core'; import { of as observableOf } from 'rxjs'; -import { isNotEmpty } from '../../shared/empty.util'; +import { hasValue, isNotEmpty } from '../../shared/empty.util'; @Component({ selector: 'ds-end-user-agreement', @@ -31,7 +31,8 @@ export class EndUserAgreementComponent implements OnInit { protected translate: TranslateService, protected authService: AuthService, protected store: Store, - protected router: Router) { + protected router: Router, + protected route: ActivatedRoute) { } /** @@ -59,7 +60,7 @@ export class EndUserAgreementComponent implements OnInit { switchMap((success) => { if (success) { this.notificationsService.success(this.translate.instant('info.end-user-agreement.accept.success')); - return this.authService.getRedirectUrl(); + return this.route.queryParams.pipe(map((params) => params.redirect)); } else { this.notificationsService.error(this.translate.instant('info.end-user-agreement.accept.error')); return observableOf(undefined); @@ -68,7 +69,7 @@ export class EndUserAgreementComponent implements OnInit { take(1) ).subscribe((redirectUrl) => { if (isNotEmpty(redirectUrl)) { - this.router.navigateByUrl(redirectUrl); + this.router.navigateByUrl(decodeURIComponent(redirectUrl)); } }); } From de9f642bf92690a9efba0295badb553eb3338dae Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Mon, 7 Sep 2020 12:01:23 +0200 Subject: [PATCH 10/12] 72541: Fix tests, remove redundant injection and added JSDocs --- .../abstract-end-user-agreement.guard.ts | 4 +--- .../end-user-agreement-cookie.guard.spec.ts | 18 +++++++----------- .../end-user-agreement-cookie.guard.ts | 4 +--- ...d-user-agreement-current-user.guard.spec.ts | 17 +++++++---------- .../end-user-agreement-current-user.guard.ts | 4 +--- src/app/core/shared/operators.ts | 4 +++- .../end-user-agreement.component.spec.ts | 15 +++++++++++---- 7 files changed, 31 insertions(+), 35 deletions(-) diff --git a/src/app/core/end-user-agreement/abstract-end-user-agreement.guard.ts b/src/app/core/end-user-agreement/abstract-end-user-agreement.guard.ts index 7fc4cc3172..ee07da004b 100644 --- a/src/app/core/end-user-agreement/abstract-end-user-agreement.guard.ts +++ b/src/app/core/end-user-agreement/abstract-end-user-agreement.guard.ts @@ -1,6 +1,5 @@ import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot, UrlTree } from '@angular/router'; import { Observable } from 'rxjs/internal/Observable'; -import { AuthService } from '../auth/auth.service'; import { returnEndUserAgreementUrlTreeOnFalse } from '../shared/operators'; /** @@ -9,8 +8,7 @@ import { returnEndUserAgreementUrlTreeOnFalse } from '../shared/operators'; */ export abstract class AbstractEndUserAgreementGuard implements CanActivate { - constructor(protected authService: AuthService, - protected router: Router) { + constructor(protected router: Router) { } /** diff --git a/src/app/core/end-user-agreement/end-user-agreement-cookie.guard.spec.ts b/src/app/core/end-user-agreement/end-user-agreement-cookie.guard.spec.ts index feeb8d7903..805c765832 100644 --- a/src/app/core/end-user-agreement/end-user-agreement-cookie.guard.spec.ts +++ b/src/app/core/end-user-agreement/end-user-agreement-cookie.guard.spec.ts @@ -1,32 +1,30 @@ import { EndUserAgreementService } from './end-user-agreement.service'; import { Router, UrlTree } from '@angular/router'; -import { AuthService } from '../auth/auth.service'; import { EndUserAgreementCookieGuard } from './end-user-agreement-cookie.guard'; describe('EndUserAgreementCookieGuard', () => { let guard: EndUserAgreementCookieGuard; let endUserAgreementService: EndUserAgreementService; - let authService: AuthService; let router: Router; beforeEach(() => { endUserAgreementService = jasmine.createSpyObj('endUserAgreementService', { isCookieAccepted: true }); - authService = jasmine.createSpyObj('authService', ['setRedirectUrl']); router = jasmine.createSpyObj('router', { navigateByUrl: {}, - parseUrl: new UrlTree() + parseUrl: new UrlTree(), + createUrlTree: new UrlTree() }); - guard = new EndUserAgreementCookieGuard(endUserAgreementService, authService, router); + guard = new EndUserAgreementCookieGuard(endUserAgreementService, router); }); describe('canActivate', () => { describe('when the cookie has been accepted', () => { it('should return true', (done) => { - guard.canActivate(undefined, undefined).subscribe((result) => { + guard.canActivate(undefined, { url: Object.assign({ url: 'redirect' }) } as any).subscribe((result) => { expect(result).toEqual(true); done(); }); @@ -38,11 +36,9 @@ describe('EndUserAgreementCookieGuard', () => { (endUserAgreementService.isCookieAccepted as jasmine.Spy).and.returnValue(false); }); - it('should navigate the user with a redirect url', (done) => { - const redirect = 'redirect/url'; - guard.canActivate(undefined, Object.assign({ url: redirect })).subscribe(() => { - expect(authService.setRedirectUrl).toHaveBeenCalledWith(redirect); - expect(router.navigateByUrl).toHaveBeenCalled(); + it('should return a UrlTree', (done) => { + guard.canActivate(undefined, Object.assign({ url: 'redirect' })).subscribe((result) => { + expect(result).toEqual(jasmine.any(UrlTree)); done(); }); }); diff --git a/src/app/core/end-user-agreement/end-user-agreement-cookie.guard.ts b/src/app/core/end-user-agreement/end-user-agreement-cookie.guard.ts index a7bdd6b320..e6461859f3 100644 --- a/src/app/core/end-user-agreement/end-user-agreement-cookie.guard.ts +++ b/src/app/core/end-user-agreement/end-user-agreement-cookie.guard.ts @@ -3,7 +3,6 @@ import { AbstractEndUserAgreementGuard } from './abstract-end-user-agreement.gua import { Observable } from 'rxjs/internal/Observable'; import { of as observableOf } from 'rxjs'; import { EndUserAgreementService } from './end-user-agreement.service'; -import { AuthService } from '../auth/auth.service'; import { Router } from '@angular/router'; /** @@ -13,9 +12,8 @@ import { Router } from '@angular/router'; export class EndUserAgreementCookieGuard extends AbstractEndUserAgreementGuard { constructor(protected endUserAgreementService: EndUserAgreementService, - protected authService: AuthService, protected router: Router) { - super(authService, router); + super(router); } /** diff --git a/src/app/core/end-user-agreement/end-user-agreement-current-user.guard.spec.ts b/src/app/core/end-user-agreement/end-user-agreement-current-user.guard.spec.ts index 09cb236b3f..1892509aef 100644 --- a/src/app/core/end-user-agreement/end-user-agreement-current-user.guard.spec.ts +++ b/src/app/core/end-user-agreement/end-user-agreement-current-user.guard.spec.ts @@ -8,26 +8,25 @@ describe('EndUserAgreementGuard', () => { let guard: EndUserAgreementCurrentUserGuard; let endUserAgreementService: EndUserAgreementService; - let authService: AuthService; let router: Router; beforeEach(() => { endUserAgreementService = jasmine.createSpyObj('endUserAgreementService', { hasCurrentUserAcceptedAgreement: observableOf(true) }); - authService = jasmine.createSpyObj('authService', ['setRedirectUrl']); router = jasmine.createSpyObj('router', { navigateByUrl: {}, - parseUrl: new UrlTree() + parseUrl: new UrlTree(), + createUrlTree: new UrlTree() }); - guard = new EndUserAgreementCurrentUserGuard(endUserAgreementService, authService, router); + guard = new EndUserAgreementCurrentUserGuard(endUserAgreementService, router); }); describe('canActivate', () => { describe('when the user has accepted the agreement', () => { it('should return true', (done) => { - guard.canActivate(undefined, undefined).subscribe((result) => { + guard.canActivate(undefined, Object.assign({ url: 'redirect' })).subscribe((result) => { expect(result).toEqual(true); done(); }); @@ -39,11 +38,9 @@ describe('EndUserAgreementGuard', () => { (endUserAgreementService.hasCurrentUserAcceptedAgreement as jasmine.Spy).and.returnValue(observableOf(false)); }); - it('should navigate the user with a redirect url', (done) => { - const redirect = 'redirect/url'; - guard.canActivate(undefined, Object.assign({ url: redirect })).subscribe(() => { - expect(authService.setRedirectUrl).toHaveBeenCalledWith(redirect); - expect(router.navigateByUrl).toHaveBeenCalled(); + it('should return a UrlTree', (done) => { + guard.canActivate(undefined, Object.assign({ url: 'redirect' })).subscribe((result) => { + expect(result).toEqual(jasmine.any(UrlTree)); done(); }); }); diff --git a/src/app/core/end-user-agreement/end-user-agreement-current-user.guard.ts b/src/app/core/end-user-agreement/end-user-agreement-current-user.guard.ts index e48d4361c6..348a3285cc 100644 --- a/src/app/core/end-user-agreement/end-user-agreement-current-user.guard.ts +++ b/src/app/core/end-user-agreement/end-user-agreement-current-user.guard.ts @@ -2,7 +2,6 @@ import { Injectable } from '@angular/core'; import { Observable } from 'rxjs/internal/Observable'; import { AbstractEndUserAgreementGuard } from './abstract-end-user-agreement.guard'; import { EndUserAgreementService } from './end-user-agreement.service'; -import { AuthService } from '../auth/auth.service'; import { Router } from '@angular/router'; /** @@ -12,9 +11,8 @@ import { Router } from '@angular/router'; export class EndUserAgreementCurrentUserGuard extends AbstractEndUserAgreementGuard { constructor(protected endUserAgreementService: EndUserAgreementService, - protected authService: AuthService, protected router: Router) { - super(authService, router); + super(router); } /** diff --git a/src/app/core/shared/operators.ts b/src/app/core/shared/operators.ts index 1018cee4ab..dac12cda5b 100644 --- a/src/app/core/shared/operators.ts +++ b/src/app/core/shared/operators.ts @@ -195,7 +195,9 @@ export const returnUnauthorizedUrlTreeOnFalse = (router: Router) => /** * Operator that returns a UrlTree to the unauthorized page when the boolean received is false - * @param router + * @param router Router + * @param redirect Redirect URL to add to the UrlTree. This is used to redirect back to the original route after the + * user accepts the agreement. */ export const returnEndUserAgreementUrlTreeOnFalse = (router: Router, redirect: string) => (source: Observable): Observable => diff --git a/src/app/info/end-user-agreement/end-user-agreement.component.spec.ts b/src/app/info/end-user-agreement/end-user-agreement.component.spec.ts index 33cd0bfd98..c0957fa7ba 100644 --- a/src/app/info/end-user-agreement/end-user-agreement.component.spec.ts +++ b/src/app/info/end-user-agreement/end-user-agreement.component.spec.ts @@ -5,11 +5,12 @@ import { EndUserAgreementService } from '../../core/end-user-agreement/end-user- import { NotificationsService } from '../../shared/notifications/notifications.service'; import { TranslateModule } from '@ngx-translate/core'; import { AuthService } from '../../core/auth/auth.service'; -import { Router } from '@angular/router'; +import { ActivatedRoute, Router } from '@angular/router'; import { of as observableOf } from 'rxjs'; import { Store } from '@ngrx/store'; import { By } from '@angular/platform-browser'; import { LogOutAction } from '../../core/auth/auth.actions'; +import { ActivatedRouteStub } from '../../shared/testing/active-router.stub'; describe('EndUserAgreementComponent', () => { let component: EndUserAgreementComponent; @@ -20,6 +21,7 @@ describe('EndUserAgreementComponent', () => { let authService: AuthService; let store; let router: Router; + let route: ActivatedRoute; let redirectUrl; @@ -32,11 +34,15 @@ describe('EndUserAgreementComponent', () => { }); notificationsService = jasmine.createSpyObj('notificationsService', ['success', 'error']); authService = jasmine.createSpyObj('authService', { - isAuthenticated: observableOf(true), - getRedirectUrl: observableOf(redirectUrl) + isAuthenticated: observableOf(true) }); store = jasmine.createSpyObj('store', ['dispatch']); router = jasmine.createSpyObj('router', ['navigate', 'navigateByUrl']); + route = Object.assign(new ActivatedRouteStub(), { + queryParams: observableOf({ + redirect: redirectUrl + }) + }) as any; } beforeEach(async(() => { @@ -49,7 +55,8 @@ describe('EndUserAgreementComponent', () => { { provide: NotificationsService, useValue: notificationsService }, { provide: AuthService, useValue: authService }, { provide: Store, useValue: store }, - { provide: Router, useValue: router } + { provide: Router, useValue: router }, + { provide: ActivatedRoute, useValue: route } ], schemas: [NO_ERRORS_SCHEMA] }).compileComponents(); From b82e840935f7880f6cf3b94c439bad8f8eb7d9c9 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Tue, 8 Sep 2020 11:16:12 +0200 Subject: [PATCH 11/12] 72541: Removed unused import --- src/app/info/end-user-agreement/end-user-agreement.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/info/end-user-agreement/end-user-agreement.component.ts b/src/app/info/end-user-agreement/end-user-agreement.component.ts index 539ca3308c..a3350319ba 100644 --- a/src/app/info/end-user-agreement/end-user-agreement.component.ts +++ b/src/app/info/end-user-agreement/end-user-agreement.component.ts @@ -9,7 +9,7 @@ import { EndUserAgreementService } from '../../core/end-user-agreement/end-user- import { NotificationsService } from '../../shared/notifications/notifications.service'; import { TranslateService } from '@ngx-translate/core'; import { of as observableOf } from 'rxjs'; -import { hasValue, isNotEmpty } from '../../shared/empty.util'; +import { isNotEmpty } from '../../shared/empty.util'; @Component({ selector: 'ds-end-user-agreement', From 4e10db8a53c45541ec43f407ac4337818f22f783 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Thu, 17 Sep 2020 11:31:16 +0200 Subject: [PATCH 12/12] 72541: user-agreement update to patch --- src/app/core/data/data.service.ts | 3 ++- .../end-user-agreement.service.spec.ts | 14 ++++---------- .../end-user-agreement.service.ts | 18 ++++++++++-------- 3 files changed, 16 insertions(+), 19 deletions(-) diff --git a/src/app/core/data/data.service.ts b/src/app/core/data/data.service.ts index 3b4e5f7d42..528889cd83 100644 --- a/src/app/core/data/data.service.ts +++ b/src/app/core/data/data.service.ts @@ -3,7 +3,7 @@ import { Store } from '@ngrx/store'; import { Operation } from 'fast-json-patch'; import { Observable } from 'rxjs'; import { distinctUntilChanged, filter, find, first, map, mergeMap, switchMap, take } from 'rxjs/operators'; -import { hasValue, isNotEmpty, isNotEmptyOperator } from '../../shared/empty.util'; +import { hasValue, hasValueOperator, isNotEmpty, isNotEmptyOperator } from '../../shared/empty.util'; import { NotificationOptions } from '../../shared/notifications/models/notification-options.model'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model'; @@ -376,6 +376,7 @@ export abstract class DataService implements UpdateDa ).subscribe(); return this.requestService.getByUUID(requestId).pipe( + hasValueOperator(), find((request: RequestEntry) => request.completed), map((request: RequestEntry) => request.response) ); diff --git a/src/app/core/end-user-agreement/end-user-agreement.service.spec.ts b/src/app/core/end-user-agreement/end-user-agreement.service.spec.ts index 4b0a37a94f..d50c730d28 100644 --- a/src/app/core/end-user-agreement/end-user-agreement.service.spec.ts +++ b/src/app/core/end-user-agreement/end-user-agreement.service.spec.ts @@ -7,6 +7,7 @@ import { CookieServiceMock } from '../../shared/mocks/cookie.service.mock'; import { of as observableOf } from 'rxjs'; import { EPerson } from '../eperson/models/eperson.model'; import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils'; +import { RestResponse } from '../cache/response.models'; describe('EndUserAgreementService', () => { let service: EndUserAgreementService; @@ -36,7 +37,8 @@ describe('EndUserAgreementService', () => { getAuthenticatedUserFromStore: observableOf(userWithMetadata) }); ePersonService = jasmine.createSpyObj('ePersonService', { - update: createSuccessfulRemoteDataObject$(userWithMetadata) + update: createSuccessfulRemoteDataObject$(userWithMetadata), + patch: observableOf(new RestResponse(true, 200, 'OK')) }); service = new EndUserAgreementService(cookie, authService, ePersonService); @@ -98,15 +100,7 @@ describe('EndUserAgreementService', () => { it('setUserAcceptedAgreement should update the user with new metadata', (done) => { service.setUserAcceptedAgreement(true).subscribe(() => { - expect(ePersonService.update).toHaveBeenCalledWith(jasmine.objectContaining({ - metadata: jasmine.objectContaining({ - [END_USER_AGREEMENT_METADATA_FIELD]: [ - { - value: 'true' - } - ] - }) - })); + expect(ePersonService.patch).toHaveBeenCalled(); done(); }); }); diff --git a/src/app/core/end-user-agreement/end-user-agreement.service.ts b/src/app/core/end-user-agreement/end-user-agreement.service.ts index 31b606cfb4..23bda89169 100644 --- a/src/app/core/end-user-agreement/end-user-agreement.service.ts +++ b/src/app/core/end-user-agreement/end-user-agreement.service.ts @@ -5,10 +5,7 @@ import { Observable } from 'rxjs/internal/Observable'; import { of as observableOf } from 'rxjs'; import { map, switchMap, take } from 'rxjs/operators'; import { hasValue } from '../../shared/empty.util'; -import { cloneDeep } from 'lodash'; -import { Metadata } from '../shared/metadata.utils'; import { EPersonDataService } from '../eperson/eperson-data.service'; -import { getSucceededRemoteData } from '../shared/operators'; export const END_USER_AGREEMENT_COOKIE = 'hasAgreedEndUser'; export const END_USER_AGREEMENT_METADATA_FIELD = 'dspace.agreements.end-user'; @@ -67,13 +64,18 @@ export class EndUserAgreementService { switchMap((authenticated) => { if (authenticated) { return this.authService.getAuthenticatedUserFromStore().pipe( + take(1), switchMap((user) => { - const updatedUser = cloneDeep(user); - Metadata.setFirstValue(updatedUser.metadata, END_USER_AGREEMENT_METADATA_FIELD, String(accepted)); - return this.ePersonService.update(updatedUser); + const newValue = { value: String(accepted) }; + let operation; + if (user.hasMetadata(END_USER_AGREEMENT_METADATA_FIELD)) { + operation = { op: 'replace', path: `/metadata/${END_USER_AGREEMENT_METADATA_FIELD}/0`, value: newValue }; + } else { + operation = { op: 'add', path: `/metadata/${END_USER_AGREEMENT_METADATA_FIELD}`, value: [ newValue ] }; + } + return this.ePersonService.patch(user, [operation]); }), - getSucceededRemoteData(), - map((rd) => hasValue(rd.payload)) + map((response) => response.isSuccessful) ); } else { this.setCookieAccepted(accepted);