Skip to content

Commit c3e0ae6

Browse files
committed
Merge branch 'dspace-cris-2024_02_x' into main-cris
2 parents 7930809 + ebe8d78 commit c3e0ae6

15 files changed

Lines changed: 237 additions & 59 deletions

File tree

config/config.example.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,8 @@ auth:
135135
# If the rest token expires in less than this amount of time, it will be refreshed automatically.
136136
# This is independent from the idle warning.
137137
timeLeftBeforeTokenRefresh: 120000 # 2 minutes
138+
# Standard login enabled
139+
disableStandardLogin: false
138140

139141
# Form settings
140142
form:

src/app/app-routes.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
RouterConfigOptions,
55
} from '@angular/router';
66

7+
import { environment } from '../environments/environment';
78
import { NOTIFICATIONS_MODULE_PATH } from './admin/admin-routing-paths';
89
import {
910
ACCESS_CONTROL_MODULE_PATH,
@@ -171,10 +172,21 @@ export const APP_ROUTES: Route[] = [
171172
providers: [provideSuggestionNotificationsState()],
172173
canActivate: [authenticatedGuard, endUserAgreementCurrentUserGuard],
173174
},
175+
{
176+
path: 'standard-login',
177+
loadChildren: () => import('./login-page/login-page-routes').then((m) => m.ROUTES),
178+
data: {
179+
isBackDoor: true,
180+
},
181+
canMatch: [() => environment.auth.disableStandardLogin],
182+
},
174183
{
175184
path: 'login',
176-
loadChildren: () => import('./login-page/login-page-routes')
177-
.then((m) => m.ROUTES),
185+
loadChildren: () => import('./login-page/login-page-routes').then((m) => m.ROUTES),
186+
data: {
187+
isBackDoor: false,
188+
},
189+
canMatch: [() => !environment.auth.disableStandardLogin],
178190
},
179191
{
180192
path: 'logout',

src/app/core/shared/image.utils.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import {
2+
Observable,
3+
of,
4+
} from 'rxjs';
5+
import { map } from 'rxjs/operators';
6+
7+
export const getDefaultImageUrlByEntityType = (entityType: string): Observable<string> => {
8+
const fallbackImage = 'assets/images/file-placeholder.svg';
9+
10+
if (!entityType) {
11+
return of(fallbackImage);
12+
}
13+
14+
const defaultImage = `assets/images/${entityType.toLowerCase()}-placeholder.svg`;
15+
return checkImageExists(defaultImage).pipe(map((exists) => exists ? defaultImage : fallbackImage));
16+
};
17+
18+
const checkImageExists = (url: string): Observable<boolean> => {
19+
return new Observable<boolean>((observer) => {
20+
const img = new Image();
21+
22+
img.onload = () => {
23+
observer.next(true);
24+
observer.complete();
25+
};
26+
27+
img.onerror = () => {
28+
observer.next(false);
29+
observer.complete();
30+
};
31+
32+
img.src = url;
33+
});
34+
};
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
<ds-thumbnail *ngIf="(initialized | async)" data-test="thumbnail"
22
[thumbnail]="thumbnail$ | async"
3-
[defaultImage]="default">
3+
[defaultImage]="default$ | async">
44
</ds-thumbnail>

src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/metadata/rendering-types/thumbnail/thumbnail.component.spec.ts

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -169,10 +169,12 @@ describe('', () => {
169169
expect(thumbnail).toBeTruthy();
170170
}));
171171

172-
it('should show default thumbnail', () => {
173-
expect(component.default).toBe('assets/images/file-placeholder.svg');
172+
it('should show default thumbnail', (done) => {
173+
component.default$.subscribe(image => {
174+
expect(image).toBe('assets/images/file-placeholder.svg');
175+
done();
176+
});
174177
});
175-
176178
});
177179

178180
describe('When bitstreams are only original', () => {
@@ -189,10 +191,12 @@ describe('', () => {
189191
expect(thumbnail).toBeTruthy();
190192
}));
191193

192-
it('should show default thumbnail', () => {
193-
expect(component.default).toBe('assets/images/file-placeholder.svg');
194+
it('should show default thumbnail', (done) => {
195+
component.default$.subscribe(image => {
196+
expect(image).toBe('assets/images/file-placeholder.svg');
197+
done();
198+
});
194199
});
195-
196200
});
197201

198202
describe('When bitstreams are only thumbnail', () => {
@@ -263,10 +267,12 @@ describe('', () => {
263267
expect(thumbnail).toBeTruthy();
264268
});
265269

266-
it('should show default thumbnail', () => {
267-
expect(component.default).toBe('assets/images/file-placeholder.svg');
270+
it('should show default thumbnail', (done) => {
271+
component.default$.subscribe(image => {
272+
expect(image).toBe('assets/images/file-placeholder.svg');
273+
done();
274+
});
268275
});
269-
270276
});
271277

272278
describe('When bitstreams are only original without the right metadata information', () => {
@@ -278,8 +284,11 @@ describe('', () => {
278284
fixture.detectChanges();
279285
});
280286

281-
it('should not show bitstream content image src but the default image', () => {
282-
expect(component.default).toBe('assets/images/file-placeholder.svg');
287+
it('should not show bitstream content image src but the default image', (done) => {
288+
component.default$.subscribe(image => {
289+
expect(image).toBe('assets/images/file-placeholder.svg');
290+
done();
291+
});
283292
});
284293

285294
});
@@ -293,10 +302,12 @@ describe('', () => {
293302
fixture.detectChanges();
294303
});
295304

296-
it('should not show thumbnail content image src but the default image', () => {
297-
expect(component.default).toBe('assets/images/file-placeholder.svg');
305+
it('should not show thumbnail content image src but the default image', (done) => {
306+
component.default$.subscribe(image => {
307+
expect(image).toBe('assets/images/file-placeholder.svg');
308+
done();
309+
});
298310
});
299-
300311
});
301312

302313
describe('When bitstreams are only original with the right metadata information', () => {
@@ -308,8 +319,11 @@ describe('', () => {
308319
fixture.detectChanges();
309320
});
310321

311-
it('should not show thumbnail content image src but the default image', () => {
312-
expect(component.default).toBe('assets/images/file-placeholder.svg');
322+
it('should not show thumbnail content image src but the default image', (done) => {
323+
component.default$.subscribe(image => {
324+
expect(image).toBe('assets/images/file-placeholder.svg');
325+
done();
326+
});
313327
});
314328

315329
});

src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/metadata/rendering-types/thumbnail/thumbnail.component.ts

Lines changed: 12 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import {
1010
import { TranslateService } from '@ngx-translate/core';
1111
import {
1212
BehaviorSubject,
13+
combineLatest,
14+
Observable,
1315
of as observableOf,
1416
} from 'rxjs';
1517
import {
@@ -21,10 +23,10 @@ import { BitstreamDataService } from '../../../../../../../core/data/bitstream-d
2123
import { PaginatedList } from '../../../../../../../core/data/paginated-list.model';
2224
import { LayoutField } from '../../../../../../../core/layout/models/box.model';
2325
import { Bitstream } from '../../../../../../../core/shared/bitstream.model';
26+
import { getDefaultImageUrlByEntityType } from '../../../../../../../core/shared/image.utils';
2427
import { Item } from '../../../../../../../core/shared/item.model';
2528
import { getFirstCompletedRemoteData } from '../../../../../../../core/shared/operators';
2629
import {
27-
hasValue,
2830
isEmpty,
2931
isNotEmpty,
3032
} from '../../../../../../../shared/empty.util';
@@ -56,7 +58,7 @@ export class ThumbnailRenderingComponent extends BitstreamRenderingModelComponen
5658
/**
5759
* Default image to be shown in the thumbnail
5860
*/
59-
default: string;
61+
default$: Observable<string>;
6062

6163
/**
6264
* Item rendering initialization state
@@ -83,9 +85,14 @@ export class ThumbnailRenderingComponent extends BitstreamRenderingModelComponen
8385
* Get the thumbnail information from api for this item
8486
*/
8587
ngOnInit(): void {
86-
this.setDefaultImage();
87-
this.getBitstreamsByItem().pipe(
88-
map((bitstreamList: PaginatedList<Bitstream>) => bitstreamList.page),
88+
const eType = this.item.firstMetadataValue('dspace.entity.type');
89+
this.default$ = getDefaultImageUrlByEntityType(eType);
90+
91+
combineLatest([
92+
this.default$,
93+
this.getBitstreamsByItem(),
94+
]).pipe(
95+
map(([_, bitstreamList]: [string, PaginatedList<Bitstream>]) => bitstreamList.page),
8996
switchMap((filteredBitstreams: Bitstream[]) => {
9097
if (filteredBitstreams.length > 0) {
9198
if (isEmpty(filteredBitstreams[0].thumbnail)) {
@@ -113,19 +120,4 @@ export class ThumbnailRenderingComponent extends BitstreamRenderingModelComponen
113120
this.initialized.next(true);
114121
});
115122
}
116-
117-
/**
118-
* Set the default image src depending on item entity type
119-
*/
120-
setDefaultImage(): void {
121-
const eType = this.item.firstMetadataValue('dspace.entity.type');
122-
this.default = 'assets/images/file-placeholder.svg';
123-
if (hasValue(eType) && eType.toUpperCase() === 'PROJECT') {
124-
this.default = 'assets/images/project-placeholder.svg';
125-
} else if (hasValue(eType) && eType.toUpperCase() === 'ORGUNIT') {
126-
this.default = 'assets/images/orgunit-placeholder.svg';
127-
} else if (hasValue(eType) && eType.toUpperCase() === 'PERSON') {
128-
this.default = 'assets/images/person-placeholder.svg';
129-
}
130-
}
131123
}

src/app/shared/log-in/log-in.component.spec.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { StoreModule } from '@ngrx/store';
1919
import { provideMockStore } from '@ngrx/store/testing';
2020
import { TranslateModule } from '@ngx-translate/core';
2121
import { of } from 'rxjs';
22+
import { AuthMethodType } from 'src/app/core/auth/models/auth.method-type';
2223

2324
import { authReducer } from '../../core/auth/auth.reducer';
2425
import { AuthService } from '../../core/auth/auth.service';
@@ -140,6 +141,59 @@ describe('LogInComponent', () => {
140141
expect(loginContainers.length).toBe(2);
141142

142143
});
144+
145+
it('returns only password method when backdoor is enabled', () => {
146+
const authMethods = [
147+
{ authMethodType: AuthMethodType.Password, position: 1 },
148+
{ authMethodType: AuthMethodType.Ip, position: 2 },
149+
{ authMethodType: AuthMethodType.Shibboleth, position: 3 },
150+
];
151+
const isBackdoor = true;
152+
component.excludedAuthMethod = undefined;
153+
const result = component.filterAndSortAuthMethods(authMethods, isBackdoor);
154+
expect(result).toEqual([{ authMethodType: AuthMethodType.Password, position: 1 }]);
155+
});
156+
157+
it('excludes password method when standard login is disabled', () => {
158+
const authMethods = [
159+
{ authMethodType: AuthMethodType.Password, position: 1 },
160+
{ authMethodType: AuthMethodType.Shibboleth, position: 2 },
161+
];
162+
component.excludedAuthMethod = undefined;
163+
const result = component.filterAndSortAuthMethods(authMethods, false, true);
164+
expect(result).toEqual([
165+
{ authMethodType: AuthMethodType.Shibboleth, position: 2 },
166+
]);
167+
});
168+
169+
it('excludes methods based on excludedAuthMethod input', () => {
170+
const authMethods = [
171+
{ authMethodType: AuthMethodType.Password, position: 1 },
172+
{ authMethodType: AuthMethodType.Ip, position: 2 },
173+
{ authMethodType: AuthMethodType.Shibboleth, position: 3 },
174+
];
175+
const isBackdoor = false;
176+
component.excludedAuthMethod = AuthMethodType.Ip;
177+
const result = component.filterAndSortAuthMethods(authMethods, isBackdoor);
178+
expect(result).toEqual([
179+
{ authMethodType: AuthMethodType.Password, position: 1 },
180+
{ authMethodType: AuthMethodType.Shibboleth, position: 3 },
181+
]);
182+
});
183+
184+
it('sorts methods by position', () => {
185+
const authMethods = [
186+
{ authMethodType: AuthMethodType.Password, position: 2 },
187+
{ authMethodType: AuthMethodType.Shibboleth, position: 1 },
188+
];
189+
const isBackdoor = false;
190+
component.excludedAuthMethod = undefined;
191+
const result = component.filterAndSortAuthMethods(authMethods, isBackdoor);
192+
expect(result).toEqual([
193+
{ authMethodType: AuthMethodType.Shibboleth, position: 1 },
194+
{ authMethodType: AuthMethodType.Password, position: 2 },
195+
]);
196+
});
143197
});
144198

145199
});

0 commit comments

Comments
 (0)