Skip to content

Commit

Permalink
add unit tests and final tweaks
Browse files Browse the repository at this point in the history
  • Loading branch information
auumgn committed Mar 29, 2024
1 parent 61bdf5e commit ca66907
Show file tree
Hide file tree
Showing 8 changed files with 244 additions and 108 deletions.
4 changes: 2 additions & 2 deletions ui/src/app/landing-page/landing-page.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ <h2>
>
</h2>
<p class="mt-4">
<img class="id-icon" src="/content/images/orcid-icon.svg" width="16" height="16" />
<img class="id-icon" src="../../content/images/orcid-icon.svg" alt="orcid-logo" width="16" height="16" />
<a target="_orcidRecord" href="{{ issuer }}/my-orcid">{{ issuer }}/{{ orcidRecord.orcid }}</a>
</p>
<p>
Expand Down Expand Up @@ -57,7 +57,7 @@ <h2>
{{ successfullyGrantedMessage }}
</p>
<p>
<img class="id-icon" src="/content/images/orcid-icon.svg" width="16" height="16" />
<img class="id-icon" src="../../content/images/orcid-icon.svg" alt="orcid-logo" width="16" height="16" />
<a target="_orcidRecord" href="{{ issuer }}/my-orcid">{{ issuer }}/{{ signedInIdToken.sub }}</a>
</p>
<p>
Expand Down
153 changes: 139 additions & 14 deletions ui/src/app/landing-page/landing-page.component.spec.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,146 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { LandingPageComponent } from './landing-page.component';
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { HttpClientModule } from '@angular/common/http'
import { LandingPageComponent } from './landing-page.component'
import { LandingPageService } from './landing-page.service'
import { OrcidRecord } from '../shared/model/orcid-record.model'
import { of } from 'rxjs'
import { Member } from '../member/model/member.model'
import { WindowLocationService } from '../shared/service/window-location.service'
import * as KEYUTIL from 'jsrsasign'

describe('LandingPageComponent', () => {
let component: LandingPageComponent;
let fixture: ComponentFixture<LandingPageComponent>;
let component: LandingPageComponent
let fixture: ComponentFixture<LandingPageComponent>
let landingPageService: jasmine.SpyObj<LandingPageService>
let windowLocationService: jasmine.SpyObj<WindowLocationService>

beforeEach(() => {
TestBed.configureTestingModule({
declarations: [LandingPageComponent]
});
fixture = TestBed.createComponent(LandingPageComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
declarations: [LandingPageComponent],
imports: [HttpClientModule],
providers: [
{
provide: LandingPageService,
useValue: jasmine.createSpyObj('LandingPageService', [
'getOrcidConnectionRecord',
'getMemberInfo',
'getPublicKey',
'submitUserResponse',
'getUserInfo',
'submitUserResponse',
]),
},
{
provide: WindowLocationService,
useValue: jasmine.createSpyObj('WindowLocationService', ['updateWindowLocation', 'getWindowLocationHash']),
},
],
})
fixture = TestBed.createComponent(LandingPageComponent)
component = fixture.componentInstance
landingPageService = TestBed.inject(LandingPageService) as jasmine.SpyObj<LandingPageService>
windowLocationService = TestBed.inject(WindowLocationService) as jasmine.SpyObj<WindowLocationService>
})

it('should create', () => {
expect(component).toBeTruthy();
});
});
expect(component).toBeTruthy()
})

it('New record connection should redirect to the registry', () => {
windowLocationService.updateWindowLocation.and.returnValue()
landingPageService.getOrcidConnectionRecord.and.returnValue(of(new OrcidRecord('email', 'orcid')))
landingPageService.getMemberInfo.and.returnValue(
of(new Member('id', 'name', 'email', 'orcid', 'salesforceId', 'clientId'))
)
component.processRequest('someState', '', '')
expect(landingPageService.getOrcidConnectionRecord).toHaveBeenCalled()
expect(component.oauthUrl).toBe(
'localhost.orcid.org/oauth/authorize?response_type=token&redirect_uri=/landing-page&client_id=name&scope=/read-limited /activities/update /person/update openid&prompt=login&state=someState'
)
expect(landingPageService.getPublicKey).toHaveBeenCalledTimes(0)
expect(windowLocationService.updateWindowLocation).toHaveBeenCalled()
})

it('New record connection should fail (user denied permission)', () => {
windowLocationService.updateWindowLocation.and.returnValue()
windowLocationService.getWindowLocationHash.and.returnValue('#error=access_denied')
landingPageService.getOrcidConnectionRecord.and.returnValue(of(new OrcidRecord('email', 'orcid')))
landingPageService.getMemberInfo.and.returnValue(
of(new Member('id', 'name', 'email', 'orcid', 'salesforceId', 'clientId'))
)
landingPageService.submitUserResponse.and.returnValue(of(''))
component.processRequest('someState', '', '')
expect(landingPageService.getOrcidConnectionRecord).toHaveBeenCalled()
expect(component.oauthUrl).toBe(
'localhost.orcid.org/oauth/authorize?response_type=token&redirect_uri=/landing-page&client_id=name&scope=/read-limited /activities/update /person/update openid&prompt=login&state=someState'
)
expect(landingPageService.getPublicKey).toHaveBeenCalledTimes(0)
expect(windowLocationService.updateWindowLocation).toHaveBeenCalledTimes(0)
expect(landingPageService.submitUserResponse).toHaveBeenCalled()
expect(component.showError).toBeFalsy()
expect(component.showDenied).toBeTruthy()
})

it('New record connection should fail (generic error)', () => {
windowLocationService.updateWindowLocation.and.returnValue()
windowLocationService.getWindowLocationHash.and.returnValue('#error=123')
landingPageService.getOrcidConnectionRecord.and.returnValue(of(new OrcidRecord('email', 'orcid')))
landingPageService.getMemberInfo.and.returnValue(
of(new Member('id', 'name', 'email', 'orcid', 'salesforceId', 'clientId'))
)
landingPageService.submitUserResponse.and.returnValue(of(''))
component.processRequest('someState', '', '')
expect(landingPageService.getOrcidConnectionRecord).toHaveBeenCalled()
expect(component.oauthUrl).toBe(
'localhost.orcid.org/oauth/authorize?response_type=token&redirect_uri=/landing-page&client_id=name&scope=/read-limited /activities/update /person/update openid&prompt=login&state=someState'
)
expect(landingPageService.getPublicKey).toHaveBeenCalledTimes(0)
expect(windowLocationService.updateWindowLocation).toHaveBeenCalledTimes(0)
expect(landingPageService.submitUserResponse).toHaveBeenCalledTimes(0)
expect(component.showError).toBeTruthy()
expect(component.showDenied).toBeFalsy()
})

it('Existing record connection should be identified', () => {
windowLocationService.updateWindowLocation.and.returnValue()
landingPageService.getOrcidConnectionRecord.and.returnValue(of(new OrcidRecord('email', 'orcid')))
landingPageService.getMemberInfo.and.returnValue(
of(new Member('id', 'name', 'email', 'orcid', 'salesforceId', 'clientId'))
)
landingPageService.getPublicKey.and.returnValue(of(['publicKey']))
landingPageService.submitUserResponse.and.returnValue(of(''))
landingPageService.getUserInfo.and.returnValue(of({ givenName: 'givenName', familyName: 'familyName' }))
spyOn(KEYUTIL.KEYUTIL, 'getKey').and.returnValue(new KEYUTIL.RSAKey())
spyOn(KEYUTIL.KJUR.jws.JWS, 'verifyJWT').and.returnValue(true)

component.processRequest('someState', 'it_token', '')

expect(landingPageService.getOrcidConnectionRecord).toHaveBeenCalled()
expect(landingPageService.getPublicKey).toHaveBeenCalled()
expect(windowLocationService.updateWindowLocation).toHaveBeenCalledTimes(0)
})

it('Check for wrong user', () => {
landingPageService.submitUserResponse.and.returnValue(of({ isDifferentUser: true }))
landingPageService.getPublicKey.and.returnValue(of(['publicKey']))
landingPageService.getUserInfo.and.returnValue(of({ givenName: 'givenName', familyName: 'familyName' }))
spyOn(KEYUTIL.KEYUTIL, 'getKey').and.returnValue(new KEYUTIL.RSAKey())
spyOn(KEYUTIL.KJUR.jws.JWS, 'verifyJWT').and.returnValue(true)
component.checkSubmitToken('token', 'state', 'access_token')
expect(landingPageService.submitUserResponse).toHaveBeenCalled()
expect(component.showConnectionExists).toBeFalsy()
expect(component.showConnectionExistsDifferentUser).toBeTruthy()
})

it('Check for existing connection', () => {
landingPageService.submitUserResponse.and.returnValue(of({ isSameUserThatAlreadyGranted: true }))
landingPageService.getPublicKey.and.returnValue(of(['publicKey']))
landingPageService.getUserInfo.and.returnValue(of({ givenName: 'givenName', familyName: 'familyName' }))
spyOn(KEYUTIL.KEYUTIL, 'getKey').and.returnValue(new KEYUTIL.RSAKey())
spyOn(KEYUTIL.KJUR.jws.JWS, 'verifyJWT').and.returnValue(true)
component.checkSubmitToken('token', 'state', 'access_token')
expect(landingPageService.submitUserResponse).toHaveBeenCalled()
expect(component.showConnectionExists).toBeTruthy()
expect(component.showConnectionExistsDifferentUser).toBeFalsy()
})
})
151 changes: 70 additions & 81 deletions ui/src/app/landing-page/landing-page.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,23 @@ import { LandingPageService } from './landing-page.service'
import { MemberService } from '../member/service/member.service'
import { IMember } from '../member/model/member.model'
import { ORCID_BASE_URL } from '../app.constants'
import { WindowLocationService } from '../shared/service/window-location.service'

@Component({
selector: 'app-landing-page',
templateUrl: './landing-page.component.html',
})
export class LandingPageComponent implements OnInit {
issuer: string = ORCID_BASE_URL
oauthBaseUrl: string = ORCID_BASE_URL + '/oauth/authorize'
redirectUri: string = '/landing-page'
issuer = ORCID_BASE_URL
oauthBaseUrl = ORCID_BASE_URL + '/oauth/authorize'
redirectUri = '/landing-page'

loading: Boolean = true
showConnectionExists: Boolean = false
showConnectionExistsDifferentUser: Boolean = false
showDenied: Boolean = false
showError: Boolean = false
showSuccess: Boolean = false
loading = true
showConnectionExists = false
showConnectionExistsDifferentUser = false
showDenied = false
showError = false
showSuccess = false
key: any
clientName: string | undefined
salesforceId: string | undefined
Expand All @@ -42,6 +43,7 @@ export class LandingPageComponent implements OnInit {

constructor(
private landingPageService: LandingPageService,
private windowLocationService: WindowLocationService,
protected memberService: MemberService
) {}

Expand All @@ -51,67 +53,73 @@ export class LandingPageComponent implements OnInit {
const state_param = this.getQueryParameterByName('state')

if (state_param) {
this.landingPageService.getOrcidConnectionRecord(state_param).subscribe({
next: (result: HttpResponse<any>) => {
this.orcidRecord = result.body
this.landingPageService.getMemberInfo(state_param).subscribe({
next: (res: IMember) => {
this.clientName = res.clientName
this.clientId = res.clientId
this.salesforceId = res.salesforceId
this.oauthUrl =
this.oauthBaseUrl +
'?response_type=token&redirect_uri=' +
this.redirectUri +
'&client_id=' +
this.clientId +
'&scope=/read-limited /activities/update /person/update openid&prompt=login&state=' +
state_param
this.processRequest(state_param, id_token_fragment, access_token_fragment)
}
}

this.incorrectDataMessage = $localize`:@@landingPage.success.ifYouFind:If you find that data added to your ORCID record is incorrect, please contact ${this.clientName}`
this.linkAlreadyUsedMessage = $localize`:@@landingPage.connectionExists.differentUser.string:This authorization link has already been used. Please contact ${this.clientName} for a new authorization link.`
this.allowToUpdateRecordMessage = $localize`:@@landingPage.denied.grantAccess.string:Allow ${this.clientName} to update my ORCID record.`
this.successfullyGrantedMessage = $localize`:@@landingPage.success.youHaveSuccessfully.string:You have successfully granted ${this.clientName} permission to update your ORCID record, and your record has been updated with affiliation information.`
processRequest(state_param: string, id_token_fragment: string, access_token_fragment: string) {
this.landingPageService.getOrcidConnectionRecord(state_param).subscribe({
next: (result) => {
this.orcidRecord = result
this.landingPageService.getMemberInfo(state_param).subscribe({
next: (res: IMember) => {
this.clientName = res.clientName
this.clientId = res.clientId
this.salesforceId = res.salesforceId
this.oauthUrl =
this.oauthBaseUrl +
'?response_type=token&redirect_uri=' +
this.redirectUri +
'&client_id=' +
this.clientId +
'&scope=/read-limited /activities/update /person/update openid&prompt=login&state=' +
state_param

// Check if id token exists in URL (user just granted permission)
if (id_token_fragment != null && id_token_fragment !== '') {
this.checkSubmitToken(id_token_fragment, state_param, access_token_fragment)
} else {
const error = this.getFragmentParameterByName('error')
// Check if user denied permission
if (error != null && error !== '') {
if (error === 'access_denied') {
this.submitUserDenied(state_param)
} else {
this.showErrorElement()
}
this.incorrectDataMessage = $localize`:@@landingPage.success.ifYouFind:If you find that data added to your ORCID record is incorrect, please contact ${this.clientName}`
this.linkAlreadyUsedMessage = $localize`:@@landingPage.connectionExists.differentUser.string:This authorization link has already been used. Please contact ${this.clientName} for a new authorization link.`
this.allowToUpdateRecordMessage = $localize`:@@landingPage.denied.grantAccess.string:Allow ${this.clientName} to update my ORCID record.`
this.successfullyGrantedMessage = $localize`:@@landingPage.success.youHaveSuccessfully.string:You have successfully granted ${this.clientName} permission to update your ORCID record, and your record has been updated with affiliation information.`

// Check if id token exists in URL (user just granted permission)
if (id_token_fragment != null && id_token_fragment !== '') {
this.checkSubmitToken(id_token_fragment, state_param, access_token_fragment)
} else {
const error = this.getFragmentParameterByName('error')
// Check if user denied permission
if (error != null && error !== '') {
if (error === 'access_denied') {
this.submitUserDenied(state_param)
} else {
window.location.replace(this.oauthUrl)
this.showErrorElement()
}
} else {
this.windowLocationService.updateWindowLocation(this.oauthUrl)
}
}

this.startTimer(600)
},
error: (res: HttpErrorResponse) => {
console.log('error')
},
})
},
error: (res: HttpErrorResponse) => {
console.log('error')
},
})
}
this.startTimer(600)
},
error: (res: HttpErrorResponse) => {
console.log('error')
},
})
},
error: (res: HttpErrorResponse) => {
console.log('error')
},
})
}

getFragmentParameterByName(name: string): string {
// eslint-disable-next-line
name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]')
const regex = new RegExp('[\\#&]' + name + '=([^&#]*)'),
results = regex.exec(window.location.hash)
results = regex.exec(this.windowLocationService.getWindowLocationHash())
return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' '))
}

getQueryParameterByName(name: string): string | null {
// eslint-disable-next-line
name = name.replace(/[\[\]]/g, '\\$&')
const regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)'),
results = regex.exec(window.location.href)
Expand All @@ -125,8 +133,8 @@ export class LandingPageComponent implements OnInit {
}

checkSubmitToken(id_token: string, state: string, access_token: string) {
this.landingPageService.getPublicKey().subscribe(
(res) => {
this.landingPageService.getPublicKey().subscribe({
next: (res) => {
const pubKey = KEYUTIL.getKey(res.keys[0]) as RSAKey
const response = KJUR.jws.JWS.verifyJWT(id_token, pubKey, {
alg: ['RS256'],
Expand Down Expand Up @@ -178,40 +186,21 @@ export class LandingPageComponent implements OnInit {
this.showErrorElement()
}
},
() => {
this.showErrorElement()
}
)
}

submitIdTokenData(id_token: string, state: string, access_token: string) {
this.landingPageService.submitUserResponse({ id_token, state }).subscribe({
next: () => {
this.landingPageService.getUserInfo(access_token).subscribe({
next: (res: HttpResponse<any>) => {
this.signedInIdToken = res
this.showSuccessElement()
},
error: () => {
this.showErrorElement()
},
})
},
error: () => {
this.showErrorElement()
},
})
}

submitUserDenied(state: string) {
this.landingPageService.submitUserResponse({ denied: true, state }).subscribe(
() => {
this.landingPageService.submitUserResponse({ denied: true, state }).subscribe({
next: () => {
this.showDeniedElement()
},
() => {
error: () => {
this.showErrorElement()
}
)
},
})
}

startTimer(seconds: number) {
Expand Down
Loading

0 comments on commit ca66907

Please sign in to comment.