Commit c878b141 authored by Eduardo Sanz García's avatar Eduardo Sanz García
Browse files

test (all): simplification

docs (README): addition of white and black lists
parent 82b25b74
...@@ -194,6 +194,8 @@ export function removeToken(): void { ...@@ -194,6 +194,8 @@ export function removeToken(): void {
JwtModule.forRoot({ JwtModule.forRoot({
config: { config: {
tokenGetter: getToken, tokenGetter: getToken,
whitelistedDomains: ['api.aai.ebi.ac.uk'], // Necessary for creating domains
blacklistedRoutes: ['https://api.aai.ebi.ac.uk/auth'] // Necessary for login via AAP local account (instead of ELIXIR)
} }
}) })
], ],
......
...@@ -21,11 +21,11 @@ ...@@ -21,11 +21,11 @@
<input formControlName="name" /> <input formControlName="name" />
</label> </label>
<label> <label>
Username: Username*:
<input formControlName="username" /> <input formControlName="username" />
</label> </label>
<label> <label>
Password: Password*:
<input formControlName="password" /> <input formControlName="password" />
</label> </label>
<label> <label>
...@@ -43,11 +43,11 @@ ...@@ -43,11 +43,11 @@
<h3 class="separator">Login AAP account:</h3> <h3 class="separator">Login AAP account:</h3>
<form [formGroup]="loginAAP"> <form [formGroup]="loginAAP">
<label> <label>
Username: Username*:
<input formControlName="username" /> <input formControlName="username" />
</label> </label>
<label> <label>
Password: Password*:
<input formControlName="password" /> <input formControlName="password" />
</label> </label>
</form> </form>
...@@ -57,15 +57,15 @@ ...@@ -57,15 +57,15 @@
<h3 class="separator">Change AAP account password:</h3> <h3 class="separator">Change AAP account password:</h3>
<form [formGroup]="changePasswordAAP"> <form [formGroup]="changePasswordAAP">
<label> <label>
Username: Username*:
<input formControlName="username" /> <input formControlName="username" />
</label> </label>
<label> <label>
Old password: Old password*:
<input formControlName="oldPassword" /> <input formControlName="oldPassword" />
</label> </label>
<label> <label>
New password: New password*:
<input formControlName="newPassword" /> <input formControlName="newPassword" />
</label> </label>
</form> </form>
......
...@@ -64,7 +64,7 @@ export class AppComponent implements OnInit { ...@@ -64,7 +64,7 @@ export class AppComponent implements OnInit {
// * add custom sync validator for username // * add custom sync validator for username
createAAP = this._fb.group({ createAAP = this._fb.group({
name: ['', { name: ['', {
validators: [Validators.required, Validators.minLength(5), Validators.maxLength(255)] validators: [Validators.minLength(5), Validators.maxLength(255)]
}], }],
username: ['', { username: ['', {
validators: [Validators.required, Validators.minLength(5), Validators.maxLength(255)] validators: [Validators.required, Validators.minLength(5), Validators.maxLength(255)]
...@@ -73,7 +73,7 @@ export class AppComponent implements OnInit { ...@@ -73,7 +73,7 @@ export class AppComponent implements OnInit {
validators: [Validators.required, Validators.minLength(5), Validators.maxLength(255)] validators: [Validators.required, Validators.minLength(5), Validators.maxLength(255)]
}], }],
email: ['', { email: ['', {
validators: [Validators.required, Validators.email, Validators.minLength(5), Validators.maxLength(255)] validators: [Validators.email, Validators.minLength(5), Validators.maxLength(255)]
}], }],
organization: ['', { organization: ['', {
validators: [Validators.maxLength(255)] validators: [Validators.maxLength(255)]
......
...@@ -25,9 +25,14 @@ import { ...@@ -25,9 +25,14 @@ import {
} from '@auth0/angular-jwt'; } from '@auth0/angular-jwt';
import { import {
CommonTestingModule, CommonTestingModule,
getToken,
updateToken, updateToken,
removeToken, removeToken,
tokenName
} from 'testing/common'; } from 'testing/common';
import {
AuthModule
} from './auth.module';
import { import {
VALID_TOKEN_1, VALID_TOKEN_1,
VALID_TOKEN_2, VALID_TOKEN_2,
...@@ -99,12 +104,10 @@ describe('AuthService', () => { ...@@ -99,12 +104,10 @@ describe('AuthService', () => {
AuthService, AuthService,
] ]
}); });
});
beforeEach(inject([AuthService], (serv: AuthService) => { service = TestBed.get(AuthService);
service = serv;
service.user().subscribe(state => user = state); service.user().subscribe(state => user = state);
})); });
it('must be created', () => { it('must be created', () => {
expect(service).toBeTruthy(); expect(service).toBeTruthy();
...@@ -122,7 +125,8 @@ describe('AuthService', () => { ...@@ -122,7 +125,8 @@ describe('AuthService', () => {
}); });
it('must execute openLoginWindow', () => { it('must execute openLoginWindow', () => {
const open = spyOn(window, 'open'); const open = spyOn(window, 'open').and.returnValue(window);
const focus = spyOn(window, 'focus');
let left, top, width, height = 0; let left, top, width, height = 0;
...@@ -140,6 +144,7 @@ describe('AuthService', () => { ...@@ -140,6 +144,7 @@ describe('AuthService', () => {
// tslint:disable-next-line:max-line-length // tslint:disable-next-line:max-line-length
`width=${width},height=${height},left=${left},top=${top},personalbar=no,toolbar=no,scrollbars=yes,resizable=yes,directories=no,location=no,menubar=no,titlebar=no,toolbar=no` `width=${width},height=${height},left=${left},top=${top},personalbar=no,toolbar=no,scrollbars=yes,resizable=yes,directories=no,location=no,menubar=no,titlebar=no,toolbar=no`
); );
expect(focus).toHaveBeenCalled();
service.openLoginWindow({}, 50000000, 100000000); service.openLoginWindow({}, 50000000, 100000000);
expect(open).toHaveBeenCalledWith( expect(open).toHaveBeenCalledWith(
...@@ -196,8 +201,23 @@ describe('AuthService', () => { ...@@ -196,8 +201,23 @@ describe('AuthService', () => {
); );
}); });
it('must execute windowOpen', () => {
const open = spyOn(service, 'openLoginWindow');
service.windowOpen({
a: 'a',
b: 'b'
}, 1, 2, 3, 4);
expect(open).toHaveBeenCalledWith({
a: 'a',
b: 'b'
}, 1, 2, 4, 3); // Inverted order of last two arguments
});
it('must execute openLoginTab', () => { it('must execute openLoginTab', () => {
const open = spyOn(window, 'open'); const open = spyOn(window, 'open').and.returnValue(window);
const focus = spyOn(window, 'focus');
service.openLoginTab(); service.openLoginTab();
expect(open).toHaveBeenCalledWith( expect(open).toHaveBeenCalledWith(
...@@ -213,6 +233,20 @@ describe('AuthService', () => { ...@@ -213,6 +233,20 @@ describe('AuthService', () => {
`${(service as any)._appURL}/sso?from=http%3A%2F%2Flocalhost%3A9876&a=a&b=b`, `${(service as any)._appURL}/sso?from=http%3A%2F%2Flocalhost%3A9876&a=a&b=b`,
'Sign in to Elixir', 'Sign in to Elixir',
); );
expect(focus).toHaveBeenCalled();
});
it('must execute tabOpen', () => {
const open = spyOn(service, 'openLoginTab');
service.tabOpen({
a: 'a',
b: 'b'
});
expect(open).toHaveBeenCalledWith({
a: 'a',
b: 'b'
});
}); });
it('must create valid single-sign-on URL', () => { it('must create valid single-sign-on URL', () => {
...@@ -223,12 +257,32 @@ describe('AuthService', () => { ...@@ -223,12 +257,32 @@ describe('AuthService', () => {
.toBe(`${(service as any)._appURL}/sso?from=http%3A%2F%2Flocalhost%3A9876&ttl=30&o=3`); .toBe(`${(service as any)._appURL}/sso?from=http%3A%2F%2Flocalhost%3A9876&ttl=30&o=3`);
}); });
it('must limit the single-sign-on time-to-live argument to 1440 seconds', () => { it('must limit the single-sign-on time-to-live argument longer than permitted', () => {
expect(() => {
service.getSSOURL({
'ttl': '' + ((60 * 24) + 1),
'o': '3'
});
})
.toThrow();
});
it('must limit the single-sign-on time-to-live argument longer than the soft limit', () => {
const warn = spyOn(window.console, 'warn');
expect(service.getSSOURL())
.toBe(`${(service as any)._appURL}/sso?from=http%3A%2F%2Flocalhost%3A9876`);
expect(warn).not.toHaveBeenCalled();
expect(service.getSSOURL({}))
.toBe(`${(service as any)._appURL}/sso?from=http%3A%2F%2Flocalhost%3A9876`);
expect(warn).not.toHaveBeenCalled();
expect(service.getSSOURL({ expect(service.getSSOURL({
'ttl': '1441', 'ttl': '' + (60 + 1),
'o': '3' 'o': '3'
})) }))
.toBe(`${(service as any)._appURL}/sso?from=http%3A%2F%2Flocalhost%3A9876&ttl=1440&o=3`); .toBe(`${(service as any)._appURL}/sso?from=http%3A%2F%2Flocalhost%3A9876&ttl=61&o=3`);
expect(warn).toHaveBeenCalled();
}); });
}); });
...@@ -248,18 +302,212 @@ describe('AuthService', () => { ...@@ -248,18 +302,212 @@ describe('AuthService', () => {
}); });
httpController = TestBed.get(HttpTestingController); httpController = TestBed.get(HttpTestingController);
service = TestBed.get(AuthService);
service.user().subscribe(state => user = state);
messageCheck = spyOn((service as any), '_messageIsAcceptable');
keyName = (service as any)._commKeyName;
}); });
afterEach(() => { afterEach(() => {
httpController.verify(); httpController.verify();
}); });
beforeEach(inject([AuthService], (serv: AuthService) => { it('must create AAP account', () => {
service = serv; service.createAAPaccount({
service.user().subscribe(state => user = state); username: 'username',
messageCheck = spyOn((service as any), '_messageIsAcceptable'); password: 'password'
keyName = (service as any)._commKeyName; }).subscribe(success => expect(success).toBe('usr-new'));
}));
const req = httpController.expectOne(
request => {
return request.url === `${(service as any)._authURL}` &&
!request.headers.has('Authorization');
}
);
req.flush('usr-new', {
status: 200,
statusText: 'OK'
});
expect(req.request.method).toBe('POST');
});
it('must fail to create AAP account', () => {
service.createAAPaccount({
username: 'username',
password: 'password'
}).subscribe({
error: error => expect(error).toBeTruthy()
});
const req = httpController.expectOne(
request => {
return request.url === `${(service as any)._authURL}` &&
!request.headers.has('Authorization');
}
);
req.flush('deliberated error', {
status: 500,
statusText: 'Not cool'
});
expect(req.request.method).toBe('POST');
});
it('must login into AAP', () => {
service.logOut();
expect(user).toBeNull('user must not be authenticated at this point');
let loginHasExecuted = false;
let logoutHasExecuted = false;
service.addLogInEventListener(() => loginHasExecuted = true);
service.addLogOutEventListener(() => logoutHasExecuted = true);
service.loginAAP({
username: 'username',
password: 'password'
}).subscribe(success => {
expect(success).toBe(true);
expect(loginHasExecuted).toBe(true);
expect(user).toEqual({
uid: 'usr-1',
name: 'Ed Munden Gras',
nickname: '1',
email: 'test@ebi.ac.uk',
token: VALID_TOKEN_1
}, 'user must have correct details');
});
const req = httpController.expectOne(
request => {
return request.url === `${(service as any)._authURL}` &&
!!(request.headers.get('Authorization') as string).match(/Basic .+/);
}
);
req.flush(VALID_TOKEN_1);
expect(req.request.method).toBe('GET');
service.logOut();
expect(user).toBeNull();
expect(logoutHasExecuted).toBe(true);
});
it('must login into AAP (with options)', () => {
service.logOut();
expect(user).toBeNull('user must not be authenticated at this point');
let loginHasExecuted = false;
let logoutHasExecuted = false;
service.addLogInEventListener(() => loginHasExecuted = true);
service.addLogOutEventListener(() => logoutHasExecuted = true);
service.loginAAP({
username: 'username',
password: 'password'
}, {ttl: '5', o: '6'}).subscribe(success => {
expect(success).toBe(true);
expect(loginHasExecuted).toBe(true);
expect(user).toEqual({
uid: 'usr-1',
name: 'Ed Munden Gras',
nickname: '1',
email: 'test@ebi.ac.uk',
token: VALID_TOKEN_1
}, 'user must have correct details');
});
const req = httpController.expectOne(
request => {
return request.url === `${(service as any)._authURL}?ttl=5&o=6` &&
!!(request.headers.get('Authorization') as string).match(/Basic .+/);
}
);
req.flush(VALID_TOKEN_1);
expect(req.request.method).toBe('GET');
service.logOut();
expect(user).toBeNull();
expect(logoutHasExecuted).toBe(true);
});
it('must fail login into AAP', () => {
service.loginAAP({
username: 'username',
password: 'password'
}).subscribe({
error: error => expect(error).toBeTruthy()
});
const req = httpController.expectOne(
request => {
return request.url === `${(service as any)._authURL}` &&
request.headers.has('Authorization');
}
);
req.flush(VALID_TOKEN_1, {
status: 500,
statusText: 'Not cool'
});
expect(req.request.method).toBe('GET');
expect(user).toBeNull();
});
it('must change AAP password', () => {
service.changePasswordAAP({
username: 'username',
oldPassword: 'oldPassword',
newPassword: 'newPassword'
}).subscribe(success => expect(success).toBe(true));
const req = httpController.expectOne(
request => {
return request.url === `${(service as any)._authURL}` &&
request.headers.has('Authorization');
}
);
req.flush('', {
status: 200,
statusText: 'OK'
});
expect(req.request.method).toBe('PATCH');
});
it('must fail to change AAP password', () => {
service.changePasswordAAP({
username: 'username',
oldPassword: 'oldPassword',
newPassword: 'newPassword'
}).subscribe({
error: error => expect(error).toBeTruthy()
});
const req = httpController.expectOne(
request => {
return request.url === `${(service as any)._authURL}` &&
request.headers.has('Authorization');
}
);
req.flush('', {
status: 401,
statusText: 'Not authorized'
});
expect(req.request.method).toBe('PATCH');
});
it('must be able to login (through private _updateUser method) and logout', () => { it('must be able to login (through private _updateUser method) and logout', () => {
service.logOut(); service.logOut();
...@@ -314,6 +562,7 @@ describe('AuthService', () => { ...@@ -314,6 +562,7 @@ describe('AuthService', () => {
const timeStamp_3 = +(localStorage.getItem(keyName) as string); const timeStamp_3 = +(localStorage.getItem(keyName) as string);
expect(timeStamp_2).toBeLessThan(timeStamp_3); expect(timeStamp_2).toBeLessThan(timeStamp_3);
expect(user).toBeNull('user must not be authenticated at this point'); expect(user).toBeNull('user must not be authenticated at this point');
expect(localStorage.hasOwnProperty(tokenName)).toBe(false);
tick(1); tick(1);
service.logOut(); service.logOut();
...@@ -347,7 +596,7 @@ describe('AuthService', () => { ...@@ -347,7 +596,7 @@ describe('AuthService', () => {
service.logOut(); service.logOut();
}); });
it('must be able call logout events', () => { it('must be able call logout events (via logout method)', () => {
messageCheck.and.returnValue(true); messageCheck.and.returnValue(true);
let hasExecuted = false; let hasExecuted = false;
...@@ -372,9 +621,39 @@ describe('AuthService', () => { ...@@ -372,9 +621,39 @@ describe('AuthService', () => {
expect(hasExecuted).toBe(false); expect(hasExecuted).toBe(false);
service.logOut(); service.logOut();
expect(hasExecuted).toBe(true); expect(hasExecuted).toBe(true);
expect(localStorage.hasOwnProperty(tokenName)).toBe(false);
}); });
it('must be able call logout events (via timeOut)', fakeAsync(() => {
messageCheck.and.returnValue(true);
let hasExecuted = false;
service.logOut();
expect(user).toBeNull('user must not be authenticated at this point');
expect(hasExecuted).toBe(false);
service.addLogOutEventListener(() => hasExecuted = true);
window.dispatchEvent(new MessageEvent('message', {
data: VALID_TOKEN_1
}));
expect(user).not.toBeNull('user must be authenticated at this point');
expect(user).toEqual({
uid: 'usr-1',
name: 'Ed Munden Gras',
nickname: '1',
email: 'test@ebi.ac.uk',
token: VALID_TOKEN_1
}, 'user must have correct details');
expect(hasExecuted).toBe(false);
tick(1000000000000000000000);
expect(hasExecuted).toBe(true);
expect(localStorage.hasOwnProperty(tokenName)).toBe(false);
}));
it('must authenticate only after login from correct window source', () => { it('must authenticate only after login from correct window source', () => {
const close = spyOn(window, 'close');
service.logOut(); service.logOut();
expect(user).toBeNull('user must not be authenticated at this point'); expect(user).toBeNull('user must not be authenticated at this point');
...@@ -389,7 +668,8 @@ describe('AuthService', () => { ...@@ -389,7 +668,8 @@ describe('AuthService', () => {
window.dispatchEvent(new MessageEvent('message', { window.dispatchEvent(new MessageEvent('message', {
data: VALID_TOKEN_1, data: VALID_TOKEN_1,
origin: (service as any)._appURL origin: (service as any)._appURL,
source: window,
})); }));
expect(user).not.toBeNull('user must be authenticated at this point'); expect(user).not.toBeNull('user must be authenticated at this point');
...@@ -400,6 +680,7 @@ describe('AuthService', () => { ...@@ -400,6 +680,7 @@ describe('AuthService', () => {
email: 'test@ebi.ac.uk', email: 'test@ebi.ac.uk',
token: VALID_TOKEN_1 token: VALID_TOKEN_1
}, 'user must have correct details'); }, 'user must have correct details');
expect(close).toHaveBeenCalled();