| /** |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| import {Inject, Injectable} from '@angular/core'; |
| import {Actions, Effect} from '@ngrx/effects'; |
| import {Observable} from 'rxjs/Observable'; |
| import {Action, Store} from '@ngrx/store'; |
| import {of} from 'rxjs/observable/of'; |
| import * as securityActions from '../security.actions'; |
| import {AuthenticationService} from '../../../sevices/security/authn/authentication.service'; |
| import {PermissionId} from '../../../sevices/security/authz/permission-id.type'; |
| import {FimsPermission} from '../../../sevices/security/authz/fims-permission.model'; |
| import {Permission} from '../../../sevices/identity/domain/permission.model'; |
| import {PermittableGroupIdMapper} from '../../../sevices/security/authz/permittable-group-id-mapper'; |
| import * as fromRoot from '../../index'; |
| import {IdentityService} from '../../../sevices/identity/identity.service'; |
| import {Password} from '../../../sevices/identity/domain/password.model'; |
| |
| @Injectable() |
| export class SecurityApiEffects { |
| |
| @Effect() |
| login$: Observable<Action> = this.actions$ |
| .ofType(securityActions.LOGIN) |
| .map((action: securityActions.LoginAction) => action.payload) |
| .mergeMap(payload => |
| this.authenticationService.login(payload.tenant, payload.username, payload.password) |
| .map(authentication => Object.assign({}, { |
| username: payload.username, |
| tenant: payload.tenant, |
| authentication: authentication |
| })) |
| .map(successPayload => new securityActions.LoginSuccessAction(successPayload)) |
| .catch((error) => of(new securityActions.LoginFailAction(error))) |
| ); |
| |
| @Effect() |
| loadPermissions$: Observable<Action> = this.actions$ |
| .ofType(securityActions.LOGIN_SUCCESS) |
| .map((action: securityActions.LoginSuccessAction) => action.payload) |
| .mergeMap(payload => |
| this.fetchPermissions(payload.tenant, payload.username, payload.authentication.accessToken) |
| .map(permissions => new securityActions.PermissionUpdateSuccessAction(permissions)) |
| .catch(error => of(new securityActions.PermissionUpdateFailAction(error))) |
| ); |
| |
| @Effect() |
| startRefreshTokenTimer$: Observable<Action> = this.actions$ |
| .ofType(securityActions.LOGIN_SUCCESS) |
| .map((action: securityActions.LoginSuccessAction) => action.payload) |
| .map(payload => new Date(payload.authentication.refreshTokenExpiration).getTime()) |
| .map(refreshTokenExpirationMillies => new Date(refreshTokenExpirationMillies - this.tokenExpiryBuffer)) |
| .map(delay => new securityActions.RefreshTokenStartTimerAction(delay)); |
| |
| @Effect() |
| logout$: Observable<Action> = this.actions$ |
| .ofType(securityActions.LOGOUT) |
| .mergeMap(() => this.store.select(fromRoot.getAuthenticationState).take(1)) |
| .mergeMap(state => |
| this.authenticationService.logout(state.tenant, state.username, state.authentication.accessToken) |
| .map(() => new securityActions.LogoutSuccessAction()) |
| .catch((error) => of(new securityActions.LogoutSuccessAction())) |
| ); |
| |
| @Effect() |
| refreshToken$: Observable<Action> = this.actions$ |
| .ofType(securityActions.REFRESH_ACCESS_TOKEN) |
| .mergeMap(() => this.store.select(fromRoot.getAuthenticationState).take(1)) |
| .mergeMap(state => |
| this.authenticationService.refreshAccessToken(state.tenant) |
| .map(authentication => new securityActions.RefreshAccessTokenSuccessAction(authentication)) |
| .catch((error) => of(new securityActions.RefreshAccessTokenFailAction(error))) |
| ); |
| |
| @Effect() |
| startAccessTokenRefreshTimerAfterLogin$: Observable<Action> = this.actions$ |
| .ofType(securityActions.LOGIN_SUCCESS) |
| .map((action: securityActions.LoginSuccessAction) => action.payload) |
| .map(payload => new Date(payload.authentication.accessTokenExpiration).getTime()) |
| .map(accessTokenExpirationMillies => new Date(accessTokenExpirationMillies - this.tokenExpiryBuffer)) |
| .map(dueTime => new securityActions.RefreshAccessTokenStartTimerAction(dueTime)); |
| |
| @Effect() |
| startAccessTokenRefreshTimerAfterRefresh$: Observable<Action> = this.actions$ |
| .ofType(securityActions.REFRESH_ACCESS_TOKEN_SUCCESS) |
| .map((action: securityActions.RefreshAccessTokenSuccessAction) => action.payload) |
| .map(payload => new Date(payload.accessTokenExpiration).getTime()) |
| .map(accessTokenExpirationMillies => new Date(accessTokenExpirationMillies - this.tokenExpiryBuffer)) |
| .map(dueTime => new securityActions.RefreshAccessTokenStartTimerAction(dueTime)); |
| |
| @Effect() |
| refreshAccessTokenStartTimer$: Observable<Action> = this.actions$ |
| .ofType(securityActions.REFRESH_ACCESS_TOKEN_START_TIMER) |
| .map((action: securityActions.RefreshAccessTokenStartTimerAction) => action.payload) |
| .mergeMap(dueTime => |
| Observable.timer(dueTime) |
| .switchMap(() => of(new securityActions.RefreshAccessTokenAction())) |
| ); |
| |
| @Effect() |
| refreshTokenStartTimer$: Observable<Action> = this.actions$ |
| .ofType(securityActions.REFRESH_TOKEN_START_TIMER) |
| .map((action: securityActions.RefreshTokenStartTimerAction) => action.payload) |
| .mergeMap(dueTime => |
| Observable.timer(dueTime) |
| .switchMap(() => of(new securityActions.LogoutAction())) |
| ); |
| |
| @Effect() |
| changePassword$: Observable<Action> = this.actions$ |
| .ofType(securityActions.CHANGE_PASSWORD) |
| .map((action: securityActions.ChangePasswordAction) => action.payload) |
| .mergeMap(payload => |
| this.identityService.changePassword(payload.username, new Password(payload.password)) |
| .map(() => new securityActions.ChangePasswordSuccessAction()) |
| .catch(error => of(new securityActions.ChangePasswordFailAction(error))) |
| ); |
| |
| @Effect() |
| logoutOnPasswordChange$: Observable<Action> = this.actions$ |
| .ofType(securityActions.CHANGE_PASSWORD_SUCCESS) |
| .mergeMap(() => Observable.of(new securityActions.LogoutAction())); |
| |
| private fetchPermissions(tenantId: string, username: string, accessToken: string): Observable<FimsPermission[]> { |
| return this.authenticationService.getUserPermissions(tenantId, username, accessToken) |
| .flatMap((permissions: Permission[]) => Observable.from(permissions)) |
| .map((permission: Permission) => this.mapPermissions(permission)) |
| .reduce((acc: FimsPermission[], permissions: FimsPermission[]) => acc.concat(permissions), []); |
| } |
| |
| private mapPermissions(permission: Permission): FimsPermission[] { |
| const result: FimsPermission[] = []; |
| const descriptor = this.idMapper.map(permission.permittableEndpointGroupIdentifier); |
| |
| if (descriptor) { |
| const internalKey: PermissionId = descriptor.id; |
| |
| for (const operation of permission.allowedOperations){ |
| result.push({ |
| id: internalKey, |
| accessLevel: operation |
| }); |
| } |
| } |
| |
| return result; |
| } |
| |
| constructor(private actions$: Actions, private identityService: IdentityService, private authenticationService: AuthenticationService, |
| private idMapper: PermittableGroupIdMapper, @Inject('tokenExpiryBuffer') private tokenExpiryBuffer: number, |
| private store: Store<fromRoot.State>) {} |
| } |