mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-07-24 17:32:15 +00:00
[ADF-2795] SSO implicitflow (#3332)
* Enable OAUTH2 * Create SSO services * SSO improvements * Rollback sso login change * Add SSO configuration from Setting component * Refactoring * Remove login ECM/BPM toggle and move use the userpreference instead of store * fix host setting unit test * Fix unit test missing instance * use the Js api oauth * add logout component and clean sso not used class * fix dependencies cicle * add translation settings * fix style setting page * clean * JS APi should receive the oauth config from the userPreference and not from the config file * change login if SSO is present * missing spaces * add sso test in login component * add logout directive new properties test * Improve host setting and remove library reference * fix login test * Remove unused code * Fix authentication unit test * fix authguard unit test * fix csrf check login component * fix unit test core and demo shell * remove
This commit is contained in:
committed by
Eugenio Romano
parent
3a6c12e624
commit
f8e92b2fb0
@@ -1,6 +1,6 @@
|
||||
<div class="adf-login-content" [style.background-image]="'url(' + backgroundImageUrl + ')'">
|
||||
<div class="ie11FixerParent">
|
||||
<div class="ie11FixerChild">
|
||||
<div class="ie11FixerParent">
|
||||
<div class="ie11FixerChild">
|
||||
|
||||
<mat-card class="adf-login-card-wide">
|
||||
<form id="adf-login-form" [formGroup]="form" (submit)="onSubmit(form.value)" autocomplete="off">
|
||||
@@ -10,107 +10,128 @@
|
||||
<div class="adf-alfresco-logo">
|
||||
<!--HEADER TEMPLATE-->
|
||||
<ng-template *ngIf="headerTemplate"
|
||||
ngFor [ngForOf]="[data]"
|
||||
[ngForTemplate]="headerTemplate">
|
||||
ngFor [ngForOf]="[data]"
|
||||
[ngForTemplate]="headerTemplate">
|
||||
</ng-template>
|
||||
<img *ngIf="!headerTemplate" class="adf-img-logo" [src]="logoImageUrl"
|
||||
alt="{{'LOGIN.LOGO' | translate }}">
|
||||
alt="{{'LOGIN.LOGO' | translate }}">
|
||||
</div>
|
||||
</mat-card-title>
|
||||
</mat-card-header>
|
||||
|
||||
<mat-card-content class="adf-login-controls">
|
||||
|
||||
<!--ERRORS AREA-->
|
||||
<div class="adf-error-container">
|
||||
<div *ngIf="isError" id="login-error" data-automation-id="login-error"
|
||||
class="error adf-error-message">
|
||||
<mat-icon class="error-icon">warning</mat-icon>
|
||||
<span class="login-error-message">{{errorMsg | translate }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--USERNAME FIELD-->
|
||||
<div class="adf-login__field" [ngClass]="{'is-invalid': isErrorStyle(form.controls.username)}">
|
||||
<mat-form-field class="adf-full-width" floatPlaceholder="never" color="primary">
|
||||
<input matInput placeholder="{{'LOGIN.LABEL.USERNAME' | translate }}"
|
||||
type="text"
|
||||
class="adf-full-width"
|
||||
[formControl]="form.controls['username']"
|
||||
autocapitalize="none"
|
||||
id="username"
|
||||
data-automation-id="username"
|
||||
(blur)="trimUsername($event)"
|
||||
tabindex="1">
|
||||
</mat-form-field>
|
||||
|
||||
<span class="adf-login-validation" for="username" *ngIf="formError.username">
|
||||
<span id="username-error" class="adf-login-error" data-automation-id="username-error">{{formError.username | translate }}</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!--PASSWORD FIELD-->
|
||||
<div class="adf-login__field">
|
||||
<mat-form-field class="adf-full-width" floatPlaceholder="never" color="primary">
|
||||
<input matInput placeholder="{{'LOGIN.LABEL.PASSWORD' | translate }}"
|
||||
type="password"
|
||||
[formControl]="form.controls['password']"
|
||||
id="password"
|
||||
data-automation-id="password"
|
||||
tabindex="2">
|
||||
<mat-icon *ngIf="isPasswordShow" matSuffix class="adf-login-password-icon"
|
||||
data-automation-id="hide_password" (click)="toggleShowPassword()">visibility
|
||||
</mat-icon>
|
||||
<mat-icon *ngIf="!isPasswordShow" matSuffix class="adf-login-password-icon"
|
||||
data-automation-id="show_password" (click)="toggleShowPassword()">visibility_off
|
||||
</mat-icon>
|
||||
</mat-form-field>
|
||||
<span class="adf-login-validation" for="password" *ngIf="formError.password">
|
||||
<span id="password-required" class="adf-login-error"
|
||||
data-automation-id="password-required">{{formError.password | translate }}</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!--CUSTOM CONTENT-->
|
||||
<ng-content></ng-content>
|
||||
|
||||
<br>
|
||||
<button type="submit" id="login-button" tabindex="3"
|
||||
class="adf-login-button"
|
||||
mat-raised-button color="primary"
|
||||
[class.isChecking]="actualLoginStep === LoginSteps.Checking"
|
||||
[class.isWelcome]="actualLoginStep === LoginSteps.Welcome"
|
||||
data-automation-id="login-button" [disabled]="!form.valid">
|
||||
|
||||
<span *ngIf="actualLoginStep === LoginSteps.Landing" class="adf-login-button-label">{{ 'LOGIN.BUTTON.LOGIN' | translate }}</span>
|
||||
|
||||
<div *ngIf="actualLoginStep === LoginSteps.Checking" class="adf-interactive-login-label">
|
||||
<span class="adf-login-button-label">{{ 'LOGIN.BUTTON.CHECKING' | translate }}</span>
|
||||
<div class="adf-login-spinner-container">
|
||||
<mat-spinner id="checking-spinner" class="adf-login-checking-spinner" [diameter]="25"></mat-spinner>
|
||||
<div *ngIf="!implicitFlow">
|
||||
<!--ERRORS AREA-->
|
||||
<div class="adf-error-container">
|
||||
<div *ngIf="isError" id="login-error" data-automation-id="login-error"
|
||||
class="error adf-error-message">
|
||||
<mat-icon class="error-icon">warning</mat-icon>
|
||||
<span class="login-error-message">{{errorMsg | translate }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--USERNAME FIELD-->
|
||||
<div class="adf-login__field"
|
||||
[ngClass]="{'is-invalid': isErrorStyle(form.controls.username)}">
|
||||
<mat-form-field class="adf-full-width" floatPlaceholder="never" color="primary">
|
||||
<input matInput placeholder="{{'LOGIN.LABEL.USERNAME' | translate }}"
|
||||
type="text"
|
||||
class="adf-full-width"
|
||||
[formControl]="form.controls['username']"
|
||||
autocapitalize="none"
|
||||
id="username"
|
||||
data-automation-id="username"
|
||||
(blur)="trimUsername($event)"
|
||||
tabindex="1">
|
||||
</mat-form-field>
|
||||
|
||||
<div *ngIf="actualLoginStep === LoginSteps.Welcome" class="adf-interactive-login-label">
|
||||
<span class="adf-login-button-label">{{ 'LOGIN.BUTTON.WELCOME' | translate }}</span>
|
||||
<mat-icon class="welcome-icon">done</mat-icon>
|
||||
<span class="adf-login-validation" for="username" *ngIf="formError.username">
|
||||
<span id="username-error" class="adf-login-error" data-automation-id="username-error">{{formError.username | translate }}</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
</button>
|
||||
<div *ngIf="showRememberMe" class="adf-login__remember-me">
|
||||
<mat-checkbox id="adf-login-remember" color="primary" class="adf-login-rememberme" [checked]="rememberMe"
|
||||
(change)="rememberMe = !rememberMe">{{ 'LOGIN.LABEL.REMEMBER' | translate }}
|
||||
</mat-checkbox>
|
||||
<!--PASSWORD FIELD-->
|
||||
<div class="adf-login__field">
|
||||
<mat-form-field class="adf-full-width" floatPlaceholder="never" color="primary">
|
||||
<input matInput placeholder="{{'LOGIN.LABEL.PASSWORD' | translate }}"
|
||||
type="password"
|
||||
[formControl]="form.controls['password']"
|
||||
id="password"
|
||||
data-automation-id="password"
|
||||
tabindex="2">
|
||||
<mat-icon *ngIf="isPasswordShow" matSuffix class="adf-login-password-icon"
|
||||
data-automation-id="hide_password" (click)="toggleShowPassword()">
|
||||
visibility
|
||||
</mat-icon>
|
||||
<mat-icon *ngIf="!isPasswordShow" matSuffix class="adf-login-password-icon"
|
||||
data-automation-id="show_password" (click)="toggleShowPassword()">
|
||||
visibility_off
|
||||
</mat-icon>
|
||||
</mat-form-field>
|
||||
<span class="adf-login-validation" for="password" *ngIf="formError.password">
|
||||
<span id="password-required" class="adf-login-error"
|
||||
data-automation-id="password-required">{{formError.password | translate }}</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!--CUSTOM CONTENT-->
|
||||
<ng-content></ng-content>
|
||||
|
||||
<br>
|
||||
<button type="submit" id="login-button" tabindex="3"
|
||||
class="adf-login-button"
|
||||
mat-raised-button color="primary"
|
||||
[class.isChecking]="actualLoginStep === LoginSteps.Checking"
|
||||
[class.isWelcome]="actualLoginStep === LoginSteps.Welcome"
|
||||
data-automation-id="login-button" [disabled]="!form.valid">
|
||||
|
||||
<span *ngIf="actualLoginStep === LoginSteps.Landing" class="adf-login-button-label">{{ 'LOGIN.BUTTON.LOGIN' | translate }}</span>
|
||||
|
||||
<div *ngIf="actualLoginStep === LoginSteps.Checking"
|
||||
class="adf-interactive-login-label">
|
||||
<span
|
||||
class="adf-login-button-label">{{ 'LOGIN.BUTTON.CHECKING' | translate }}</span>
|
||||
<div class="adf-login-spinner-container">
|
||||
<mat-spinner id="checking-spinner" class="adf-login-checking-spinner"
|
||||
[diameter]="25"></mat-spinner>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div *ngIf="actualLoginStep === LoginSteps.Welcome" class="adf-interactive-login-label">
|
||||
<span class="adf-login-button-label">{{ 'LOGIN.BUTTON.WELCOME' | translate }}</span>
|
||||
<mat-icon class="welcome-icon">done</mat-icon>
|
||||
</div>
|
||||
|
||||
</button>
|
||||
<div *ngIf="showRememberMe" class="adf-login__remember-me">
|
||||
<mat-checkbox id="adf-login-remember" color="primary" class="adf-login-rememberme"
|
||||
[checked]="rememberMe"
|
||||
(change)="rememberMe = !rememberMe">{{ 'LOGIN.LABEL.REMEMBER' | translate
|
||||
}}
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="implicitFlow">
|
||||
<button type="button" (click)="implicitLogin()" id="login-button-sso" tabindex="1"
|
||||
class="adf-login-button"
|
||||
mat-raised-button color="primary"
|
||||
data-automation-id="login-button-sso">
|
||||
<span class="adf-login-button-label">{{ 'LOGIN.BUTTON.SSO' | translate }}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</mat-card-content>
|
||||
|
||||
<mat-card-actions *ngIf="footerTemplate || showLoginActions">
|
||||
|
||||
<div class="adf-login-action-container">
|
||||
<!--FOOTER TEMPLATE-->
|
||||
<ng-template *ngIf="footerTemplate"
|
||||
ngFor [ngForOf]="[data]"
|
||||
[ngForTemplate]="footerTemplate">
|
||||
ngFor [ngForOf]="[data]"
|
||||
[ngForTemplate]="footerTemplate">
|
||||
</ng-template>
|
||||
<div class="adf-login-action" *ngIf="!footerTemplate && showLoginActions">
|
||||
<div id="adf-login-action-left" class="adf-login-action-left">
|
||||
@@ -131,5 +152,5 @@
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -58,7 +58,7 @@ describe('LoginComponent', () => {
|
||||
imports: [CoreTestingModule]
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
beforeEach(async(() => {
|
||||
fixture = TestBed.createComponent(LoginComponent);
|
||||
|
||||
element = fixture.nativeElement;
|
||||
@@ -66,15 +66,17 @@ describe('LoginComponent', () => {
|
||||
component.showRememberMe = true;
|
||||
component.showLoginActions = true;
|
||||
|
||||
usernameInput = element.querySelector('#username');
|
||||
passwordInput = element.querySelector('#password');
|
||||
|
||||
authService = TestBed.get(AuthenticationService);
|
||||
router = TestBed.get(Router);
|
||||
userPreferences = TestBed.get(UserPreferencesService);
|
||||
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
usernameInput = element.querySelector('#username');
|
||||
passwordInput = element.querySelector('#password');
|
||||
});
|
||||
}));
|
||||
|
||||
afterEach(() => {
|
||||
fixture.destroy();
|
||||
@@ -98,7 +100,7 @@ describe('LoginComponent', () => {
|
||||
});
|
||||
|
||||
it('should redirect to route on successful login', () => {
|
||||
spyOn(authService, 'login').and.returnValue(Observable.of({ type: 'type', ticket: 'ticket'}));
|
||||
spyOn(authService, 'login').and.returnValue(Observable.of({ type: 'type', ticket: 'ticket' }));
|
||||
const redirect = '/home';
|
||||
component.successRoute = redirect;
|
||||
spyOn(router, 'navigate');
|
||||
@@ -107,10 +109,10 @@ describe('LoginComponent', () => {
|
||||
});
|
||||
|
||||
it('should redirect to previous route state on successful login', () => {
|
||||
spyOn(authService, 'login').and.returnValue(Observable.of({ type: 'type', ticket: 'ticket'}));
|
||||
spyOn(authService, 'login').and.returnValue(Observable.of({ type: 'type', ticket: 'ticket' }));
|
||||
const redirect = '/home';
|
||||
component.successRoute = redirect;
|
||||
authService.setRedirect({ provider: 'ECM', navigation: ['some-route'] } );
|
||||
authService.setRedirect({ provider: 'ECM', navigation: ['some-route'] });
|
||||
|
||||
spyOn(router, 'navigate');
|
||||
|
||||
@@ -158,7 +160,7 @@ describe('LoginComponent', () => {
|
||||
});
|
||||
|
||||
it('should be changed to the "welcome key" after a successful login attempt', () => {
|
||||
spyOn(authService, 'login').and.returnValue(Observable.of({ type: 'type', ticket: 'ticket'}));
|
||||
spyOn(authService, 'login').and.returnValue(Observable.of({ type: 'type', ticket: 'ticket' }));
|
||||
loginWithCredentials('fake-username', 'fake-password');
|
||||
|
||||
expect(getLoginButtonText()).toEqual('LOGIN.BUTTON.WELCOME');
|
||||
@@ -382,7 +384,7 @@ describe('LoginComponent', () => {
|
||||
});
|
||||
|
||||
it('should return success event after the login have succeeded', (done) => {
|
||||
spyOn(authService, 'login').and.returnValue(Observable.of({ type: 'type', ticket: 'ticket'}));
|
||||
spyOn(authService, 'login').and.returnValue(Observable.of({ type: 'type', ticket: 'ticket' }));
|
||||
|
||||
component.providers = 'ECM';
|
||||
expect(component.isError).toBe(false);
|
||||
@@ -514,7 +516,7 @@ describe('LoginComponent', () => {
|
||||
|
||||
expect(component.isError).toBe(false);
|
||||
expect(event).toEqual(
|
||||
new LoginSuccessEvent({type: 'type', ticket: 'ticket'}, 'fake-username', null)
|
||||
new LoginSuccessEvent({ type: 'type', ticket: 'ticket' }, 'fake-username', null)
|
||||
);
|
||||
});
|
||||
|
||||
@@ -583,4 +585,47 @@ describe('LoginComponent', () => {
|
||||
|
||||
loginWithCredentials('fake-username', 'fake-password');
|
||||
}));
|
||||
|
||||
describe('SSO', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
userPreferences.oauthConfig = { implicitFlow: true };
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
userPreferences.oauthConfig = null;
|
||||
});
|
||||
|
||||
it('should not show login username and password if SSO implicit flow is active', async(() => {
|
||||
spyOn(authService, 'isOauth').and.returnValue(true);
|
||||
|
||||
component.ngOnInit();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(element.querySelector('#username')).toBeNull();
|
||||
expect(element.querySelector('#password')).toBeNull();
|
||||
|
||||
}));
|
||||
|
||||
it('should not show the login base auth button', async(() => {
|
||||
spyOn(authService, 'isOauth').and.returnValue(true);
|
||||
userPreferences.oauthConfig = { implicitFlow: true };
|
||||
|
||||
component.ngOnInit();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(element.querySelector('#login-button')).toBeNull();
|
||||
}));
|
||||
|
||||
it('should show the login SSO button', async(() => {
|
||||
spyOn(authService, 'isOauth').and.returnValue(true);
|
||||
userPreferences.oauthConfig = { implicitFlow: true };
|
||||
|
||||
component.ngOnInit();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(element.querySelector('#login-button-sso')).toBeDefined();
|
||||
}));
|
||||
|
||||
});
|
||||
});
|
||||
|
@@ -120,6 +120,8 @@ export class LoginComponent implements OnInit {
|
||||
@Output()
|
||||
executeSubmit = new EventEmitter<LoginSubmitEvent>();
|
||||
|
||||
implicitFlow: boolean = false;
|
||||
|
||||
form: FormGroup;
|
||||
isError: boolean = false;
|
||||
errorMsg: string;
|
||||
@@ -154,6 +156,12 @@ export class LoginComponent implements OnInit {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
if (this.authService.isOauth()) {
|
||||
if (this.userPreferences.oauthConfig && this.userPreferences.oauthConfig.implicitFlow) {
|
||||
this.implicitFlow = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.hasCustomFiledsValidation()) {
|
||||
this.form = this._fb.group(this.fieldsValidation);
|
||||
} else {
|
||||
@@ -178,7 +186,7 @@ export class LoginComponent implements OnInit {
|
||||
this.settingsService.csrfDisabled = this.disableCsrf;
|
||||
|
||||
this.disableError();
|
||||
const args = new LoginSubmitEvent({controls : { username : this.form.controls.username} });
|
||||
const args = new LoginSubmitEvent({ controls: { username: this.form.controls.username } });
|
||||
this.executeSubmit.emit(args);
|
||||
|
||||
if (args.defaultPrevented) {
|
||||
@@ -188,6 +196,10 @@ export class LoginComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
implicitLogin() {
|
||||
this.authService.ssoImplictiLogin();
|
||||
}
|
||||
|
||||
/**
|
||||
* The method check the error in the form and push the error in the formError object
|
||||
* @param data
|
||||
|
Reference in New Issue
Block a user