diff --git a/src/app/core/auth/models/auth.method-type.ts b/src/app/core/auth/models/auth.method-type.ts
index 9d999c4c3f..594d6d8b39 100644
--- a/src/app/core/auth/models/auth.method-type.ts
+++ b/src/app/core/auth/models/auth.method-type.ts
@@ -4,5 +4,6 @@ export enum AuthMethodType {
Ldap = 'ldap',
Ip = 'ip',
X509 = 'x509',
- Oidc = 'oidc'
+ Oidc = 'oidc',
+ Orcid = 'orcid'
}
diff --git a/src/app/core/auth/models/auth.method.ts b/src/app/core/auth/models/auth.method.ts
index 5a362e8606..0579ae0cd1 100644
--- a/src/app/core/auth/models/auth.method.ts
+++ b/src/app/core/auth/models/auth.method.ts
@@ -34,6 +34,11 @@ export class AuthMethod {
this.location = location;
break;
}
+ case 'orcid': {
+ this.authMethodType = AuthMethodType.Orcid;
+ this.location = location;
+ break;
+ }
default: {
break;
diff --git a/src/app/shared/log-in/methods/orcid/log-in-orcid.component.html b/src/app/shared/log-in/methods/orcid/log-in-orcid.component.html
new file mode 100644
index 0000000000..6f5453fd60
--- /dev/null
+++ b/src/app/shared/log-in/methods/orcid/log-in-orcid.component.html
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/src/app/shared/log-in/methods/orcid/log-in-orcid.component.spec.ts b/src/app/shared/log-in/methods/orcid/log-in-orcid.component.spec.ts
new file mode 100644
index 0000000000..001f0a4959
--- /dev/null
+++ b/src/app/shared/log-in/methods/orcid/log-in-orcid.component.spec.ts
@@ -0,0 +1,155 @@
+import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
+import { ActivatedRoute, Router } from '@angular/router';
+import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
+
+import { provideMockStore } from '@ngrx/store/testing';
+import { Store, StoreModule } from '@ngrx/store';
+import { TranslateModule } from '@ngx-translate/core';
+
+import { EPerson } from '../../../../core/eperson/models/eperson.model';
+import { EPersonMock } from '../../../testing/eperson.mock';
+import { authReducer } from '../../../../core/auth/auth.reducer';
+import { AuthService } from '../../../../core/auth/auth.service';
+import { AuthServiceStub } from '../../../testing/auth-service.stub';
+import { storeModuleConfig } from '../../../../app.reducer';
+import { AuthMethod } from '../../../../core/auth/models/auth.method';
+import { AuthMethodType } from '../../../../core/auth/models/auth.method-type';
+import { LogInOrcidComponent } from './log-in-orcid.component';
+import { NativeWindowService } from '../../../../core/services/window.service';
+import { RouterStub } from '../../../testing/router.stub';
+import { ActivatedRouteStub } from '../../../testing/active-router.stub';
+import { NativeWindowMockFactory } from '../../../mocks/mock-native-window-ref';
+import { HardRedirectService } from '../../../../core/services/hard-redirect.service';
+
+
+describe('LogInOrcidComponent', () => {
+
+ let component: LogInOrcidComponent;
+ let fixture: ComponentFixture;
+ let page: Page;
+ let user: EPerson;
+ let componentAsAny: any;
+ let setHrefSpy;
+ let orcidBaseUrl;
+ let location;
+ let initialState: any;
+ let hardRedirectService: HardRedirectService;
+
+ beforeEach(() => {
+ user = EPersonMock;
+ orcidBaseUrl = 'dspace-rest.test/orcid?redirectUrl=';
+ location = orcidBaseUrl + 'http://dspace-angular.test/home';
+
+ hardRedirectService = jasmine.createSpyObj('hardRedirectService', {
+ getCurrentRoute: {},
+ redirect: {}
+ });
+
+ initialState = {
+ core: {
+ auth: {
+ authenticated: false,
+ loaded: false,
+ blocking: false,
+ loading: false,
+ authMethods: []
+ }
+ }
+ };
+ });
+
+ beforeEach(waitForAsync(() => {
+ // refine the test module by declaring the test component
+ TestBed.configureTestingModule({
+ imports: [
+ StoreModule.forRoot({ auth: authReducer }, storeModuleConfig),
+ TranslateModule.forRoot()
+ ],
+ declarations: [
+ LogInOrcidComponent
+ ],
+ providers: [
+ { provide: AuthService, useClass: AuthServiceStub },
+ { provide: 'authMethodProvider', useValue: new AuthMethod(AuthMethodType.Orcid, location) },
+ { provide: 'isStandalonePage', useValue: true },
+ { provide: NativeWindowService, useFactory: NativeWindowMockFactory },
+ { provide: Router, useValue: new RouterStub() },
+ { provide: ActivatedRoute, useValue: new ActivatedRouteStub() },
+ { provide: HardRedirectService, useValue: hardRedirectService },
+ provideMockStore({ initialState }),
+ ],
+ schemas: [
+ CUSTOM_ELEMENTS_SCHEMA
+ ]
+ })
+ .compileComponents();
+
+ }));
+
+ beforeEach(() => {
+ // create component and test fixture
+ fixture = TestBed.createComponent(LogInOrcidComponent);
+
+ // get test component from the fixture
+ component = fixture.componentInstance;
+ componentAsAny = component;
+
+ // create page
+ page = new Page(component, fixture);
+ setHrefSpy = spyOnProperty(componentAsAny._window.nativeWindow.location, 'href', 'set').and.callThrough();
+
+ });
+
+ it('should set the properly a new redirectUrl', () => {
+ const currentUrl = 'http://dspace-angular.test/collections/12345';
+ componentAsAny._window.nativeWindow.location.href = currentUrl;
+
+ fixture.detectChanges();
+
+ expect(componentAsAny.injectedAuthMethodModel.location).toBe(location);
+ expect(componentAsAny._window.nativeWindow.location.href).toBe(currentUrl);
+
+ component.redirectToOrcid();
+
+ expect(setHrefSpy).toHaveBeenCalledWith(currentUrl);
+
+ });
+
+ it('should not set a new redirectUrl', () => {
+ const currentUrl = 'http://dspace-angular.test/home';
+ componentAsAny._window.nativeWindow.location.href = currentUrl;
+
+ fixture.detectChanges();
+
+ expect(componentAsAny.injectedAuthMethodModel.location).toBe(location);
+ expect(componentAsAny._window.nativeWindow.location.href).toBe(currentUrl);
+
+ component.redirectToOrcid();
+
+ expect(setHrefSpy).toHaveBeenCalledWith(currentUrl);
+
+ });
+
+});
+
+/**
+ * I represent the DOM elements and attach spies.
+ *
+ * @class Page
+ */
+class Page {
+
+ public emailInput: HTMLInputElement;
+ public navigateSpy: jasmine.Spy;
+ public passwordInput: HTMLInputElement;
+
+ constructor(private component: LogInOrcidComponent, private fixture: ComponentFixture) {
+ // use injector to get services
+ const injector = fixture.debugElement.injector;
+ const store = injector.get(Store);
+
+ // add spies
+ this.navigateSpy = spyOn(store, 'dispatch');
+ }
+
+}
diff --git a/src/app/shared/log-in/methods/orcid/log-in-orcid.component.ts b/src/app/shared/log-in/methods/orcid/log-in-orcid.component.ts
new file mode 100644
index 0000000000..df234bcbb4
--- /dev/null
+++ b/src/app/shared/log-in/methods/orcid/log-in-orcid.component.ts
@@ -0,0 +1,110 @@
+import { Component, Inject, OnInit, } from '@angular/core';
+
+import { Observable } from 'rxjs';
+import { select, Store } from '@ngrx/store';
+
+import { renderAuthMethodFor } from '../log-in.methods-decorator';
+import { AuthMethodType } from '../../../../core/auth/models/auth.method-type';
+import { AuthMethod } from '../../../../core/auth/models/auth.method';
+
+import { CoreState } from '../../../../core/core.reducers';
+import { isAuthenticated, isAuthenticationLoading } from '../../../../core/auth/selectors';
+import { NativeWindowRef, NativeWindowService } from '../../../../core/services/window.service';
+import { isNotNull, isEmpty } from '../../../empty.util';
+import { AuthService } from '../../../../core/auth/auth.service';
+import { HardRedirectService } from '../../../../core/services/hard-redirect.service';
+import { take } from 'rxjs/operators';
+import { URLCombiner } from '../../../../core/url-combiner/url-combiner';
+
+@Component({
+ selector: 'ds-log-in-orcid',
+ templateUrl: './log-in-orcid.component.html',
+})
+@renderAuthMethodFor(AuthMethodType.Orcid)
+export class LogInOrcidComponent implements OnInit {
+
+ /**
+ * The authentication method data.
+ * @type {AuthMethod}
+ */
+ public authMethod: AuthMethod;
+
+ /**
+ * True if the authentication is loading.
+ * @type {boolean}
+ */
+ public loading: Observable;
+
+ /**
+ * The orcid authentication location url.
+ * @type {string}
+ */
+ public location: string;
+
+ /**
+ * Whether user is authenticated.
+ * @type {Observable}
+ */
+ public isAuthenticated: Observable;
+
+ /**
+ * @constructor
+ * @param {AuthMethod} injectedAuthMethodModel
+ * @param {boolean} isStandalonePage
+ * @param {NativeWindowRef} _window
+ * @param {AuthService} authService
+ * @param {HardRedirectService} hardRedirectService
+ * @param {Store} store
+ */
+ constructor(
+ @Inject('authMethodProvider') public injectedAuthMethodModel: AuthMethod,
+ @Inject('isStandalonePage') public isStandalonePage: boolean,
+ @Inject(NativeWindowService) protected _window: NativeWindowRef,
+ private authService: AuthService,
+ private hardRedirectService: HardRedirectService,
+ private store: Store
+ ) {
+ this.authMethod = injectedAuthMethodModel;
+ }
+
+ ngOnInit(): void {
+ // set isAuthenticated
+ this.isAuthenticated = this.store.pipe(select(isAuthenticated));
+
+ // set loading
+ this.loading = this.store.pipe(select(isAuthenticationLoading));
+
+ // set location
+ this.location = decodeURIComponent(this.injectedAuthMethodModel.location);
+
+ }
+
+ redirectToOrcid() {
+
+ this.authService.getRedirectUrl().pipe(take(1)).subscribe((redirectRoute) => {
+ if (!this.isStandalonePage) {
+ redirectRoute = this.hardRedirectService.getCurrentRoute();
+ } else if (isEmpty(redirectRoute)) {
+ redirectRoute = '/';
+ }
+ const correctRedirectUrl = new URLCombiner(this._window.nativeWindow.origin, redirectRoute).toString();
+
+ let orcidServerUrl = this.location;
+ const myRegexp = /\?redirectUrl=(.*)/g;
+ const match = myRegexp.exec(this.location);
+ const redirectUrlFromServer = (match && match[1]) ? match[1] : null;
+
+ // Check whether the current page is different from the redirect url received from rest
+ if (isNotNull(redirectUrlFromServer) && redirectUrlFromServer !== correctRedirectUrl) {
+ // change the redirect url with the current page url
+ const newRedirectUrl = `?redirectUrl=${correctRedirectUrl}`;
+ orcidServerUrl = this.location.replace(/\?redirectUrl=(.*)/g, newRedirectUrl);
+ }
+
+ // redirect to orcid authentication url
+ this.hardRedirectService.redirect(orcidServerUrl);
+ });
+
+ }
+
+}
diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts
index 7b799bfaea..574d890ede 100644
--- a/src/app/shared/shared.module.ts
+++ b/src/app/shared/shared.module.ts
@@ -173,6 +173,7 @@ import { BitstreamRequestACopyPageComponent } from './bitstream-request-a-copy-p
import { DsSelectComponent } from './ds-select/ds-select.component';
import { LogInOidcComponent } from './log-in/methods/oidc/log-in-oidc.component';
import { ThemedItemListPreviewComponent } from './object-list/my-dspace-result-list-element/item-list-preview/themed-item-list-preview.component';
+import { LogInOrcidComponent } from './log-in/methods/orcid/log-in-orcid.component';
const MODULES = [
// Do NOT include UniversalModule, HttpModule, or JsonpModule here
@@ -306,6 +307,7 @@ const COMPONENTS = [
LogInShibbolethComponent,
LogInOidcComponent,
+ LogInOrcidComponent,
LogInPasswordComponent,
LogInContainerComponent,
ItemVersionsComponent,
@@ -378,6 +380,7 @@ const ENTRY_COMPONENTS = [
LogInPasswordComponent,
LogInShibbolethComponent,
LogInOidcComponent,
+ LogInOrcidComponent,
BundleListElementComponent,
ClaimedTaskActionsApproveComponent,
ClaimedTaskActionsRejectComponent,
diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5
index c3c68a6882..fe034ac34a 100644
--- a/src/assets/i18n/en.json5
+++ b/src/assets/i18n/en.json5
@@ -2389,6 +2389,8 @@
"login.form.oidc": "Log in with OIDC",
+ "login.form.orcid": "Log in with ORCID",
+
"login.form.password": "Password",
"login.form.shibboleth": "Log in with Shibboleth",