UNPKG

ngx-keycloak-authz-lib

Version:
462 lines (453 loc) 19.5 kB
import * as i0 from '@angular/core'; import { Injectable, Directive, Input, NgModule } from '@angular/core'; import * as i1$1 from 'keycloak-angular'; import { __awaiter } from 'tslib'; import { throwError } from 'rxjs'; import { catchError, map } from 'rxjs/operators'; import * as i1 from '@angular/common/http'; import { HttpParams, HttpHeaders } from '@angular/common/http'; import t from 'typy'; import { CommonModule } from '@angular/common'; /** * Custom parameter codec to correctly handle the plus sign in parameter * values. See https://github.com/angular/angular/issues/18261 */ class ParameterCodec { encodeKey(key) { return encodeURIComponent(key); } encodeValue(value) { return encodeURIComponent(value); } decodeKey(key) { return decodeURIComponent(key); } decodeValue(value) { return decodeURIComponent(value); } } const PARAMETER_CODEC = new ParameterCodec(); class KeycloakAuthorizationService { constructor(http) { this.http = http; this.authConfig = null; this.keycloakConfig = null; /** * Contains the RPT-Token after successfull Get-Entitlement-Call */ this._rpt = null; /** * Contains all permissions decoded from the RPT-Token after successful Get-Entitlement-Call */ this._permissions = []; } newParams() { return new HttpParams({ encoder: PARAMETER_CODEC }); } /** * Handles the class values initialization. * * @param options */ initServiceValues({ loadPermissionsInStartup = true, defaultResourceServerId = null }) { this._loadPermissionsInStartup = loadPermissionsInStartup; this._defaultResourceServerId = defaultResourceServerId; } /** * KeycloakAuthorization initialization. It should be called to initialize the adapter. * Options is a object with 2 main parameters: config and initOptions. The first one * will be used to connect to Keycloak. The second one are options to initialize the * keycloak authorization instance. * * @param options * config: an object with the following content: * - url: Keycloak json URL * - realm: realm name * - clientId: client id * * initOptions: * - defaultResourceServerId: specifies the default resource-server * - loadPermissionsInStartup: if set to true, load all permissions for default resource-server at initializiation of adapter * * */ init(options) { const { config, initOptions } = options; this.initServiceValues(initOptions); this.keycloakConfig = config; //console.log(this._defaultResourceServerId) return new Promise((resolve, reject) => { this.http.get(this.keycloakConfig.url + '/realms/' + this.keycloakConfig.realm + '/.well-known/uma2-configuration').subscribe((res) => __awaiter(this, void 0, void 0, function* () { this.authConfig = res; if ((this._defaultResourceServerId) && (this._loadPermissionsInStartup)) { try { yield this.getAuthorizations(this._defaultResourceServerId, {}); } catch (err) { reject("Error getting authorizations for resource-server-id " + this._defaultResourceServerId); } } resolve(res); }), err => { let msg = "Fehler bei der Initialisierung des Keycloak-Authorization-Services"; reject(msg); }); }); } /** * Checks if user has the required access to a resource and/or scope. * * @param authorization-check object * - rsname : Name of the resource * - scope : name of the scope * * * @returns boolean true if user has access, false if not */ checkAuthorization(authorization) { return this.hasAuthorization(authorization); } /** * Internal method to check if user has the required access to a resource and/or scope * * @param authorization * * @returns boolean true if user has access, false if not */ hasAuthorization(authorization) { let checkForResource = t(authorization, 'rsname').safeObject; let checkForScope = t(authorization, 'scope').safeObject; if (!t(this._permissions).isEmptyArray) { let filteredResource = this._permissions.find(t => { if (t.rsname == checkForResource) return t; }); //No access to resource if (!t(filteredResource).isObject) { //console.log("no access to resource granted"); return false; } //access to resource granted and no scope checking required if (checkForScope == undefined) { //console.log("no scope checking required.required auth is present - hooray"); return true; } //scope checking required, but resource has no scope defined if (t(filteredResource.scopes).isEmptyArray) { //console.log("scope checking required, but no scopes defined for resource"); return false; } let filteredScope = filteredResource.scopes.find(t => { if (t == checkForScope) return t; }); //no access to scope if (t(filteredScope).isUndefined) { //console.log("required scope not found"); return false; } //console.log("required auth is present - hooray"); return true; } //console.log("no permissions loaded (yet)"); return false; } /** * Return an array of all permissions present for the logged-in user * * @returns Array of permissions */ getPermissions() { return this._permissions; } /** * Gets authorizations for resource-server from keycloak. Also stores the permissions for future use * * @param resourceServerId * The resource server for which the entitlements of the current user are checked * * @param authorizationRequest * * @returns Authorizations-Object */ getAuthorizations(resourceServerId, authorizationRequest) { let perm = new Promise((resolve, reject) => { this.getEntitlement(resourceServerId, authorizationRequest) .subscribe(res => { try { let permissions = []; if (res.access_token) { this._rpt = res.access_token; let decoded = this.decodeToken(res.access_token); if (decoded.authorization) { if (decoded.authorization.permissions) { permissions = decoded.authorization.permissions; } } } this._permissions = permissions; resolve(permissions); } catch (error) { reject(error); } }, error => { let msg = "Unable to get entitlements"; reject(msg); }); }); return perm; } /** * Gets entitlement fron resource-server from keycloak * * @param resourceServerId * The resource server for which the entitlements of the current user are checked * * @param authorizationRequest * * @returns Object with RPT-Token containing the authorizations/entitlement */ getEntitlement(resourceServerId, authorizationRequest) { let __params = this.newParams(); let __headers = new HttpHeaders(); let __body = null; __headers = __headers.set("Content-type", "application/x-www-form-urlencoded"); if (!authorizationRequest) { authorizationRequest = {}; } __params = __params.set('grant_type', 'urn:ietf:params:oauth:grant-type:uma-ticket'); __params = __params.set('client_id', this.keycloakConfig.clientId); __params = __params.set('audience', resourceServerId); if (authorizationRequest.claimToken) { __params = __params.set('claim_token', authorizationRequest.claimToken); if (authorizationRequest.claimTokenFormat) { __params = __params.set('claim_token_format', authorizationRequest.claimTokenFormat); } } var permissions = authorizationRequest.permissions; if (!permissions) { permissions = []; } for (let i = 0; i < permissions.length; i++) { var resource = permissions[i]; var permission = resource.id; if (resource.scopes && resource.scopes.length > 0) { permission += "#"; for (let j = 0; j < resource.scopes.length; j++) { var scope = resource.scopes[j]; if (permission.indexOf('#') != permission.length - 1) { permission += ","; } permission += scope; } } __params = __params.append('permission', permission); } var metadata = authorizationRequest.metadata; if (metadata) { if (metadata.responseIncludeResourceName) { __params = __params.set('response_include_resource_name', metadata.responseIncludeResourceName); } if (metadata.responsePermissionsLimit) { __params = __params.set('response_permissions_limit', metadata.responsePermissionsLimit.toString()); } } if (this._rpt) { __params = __params.set('rpt', this._rpt); } return this.http.post(this.authConfig.token_endpoint, __params.toString(), { headers: __headers, responseType: 'json' }) .pipe(catchError(this.handleError), map((_r) => { //console.log(_r); return _r; })); } /** * Decodes RPT-Token * * @param str - enoded token string * * @returns Decoded jwt-token object * */ decodeToken(str) { str = str.split('.')[1]; str = str.replace('/-/g', '+'); str = str.replace('/_/g', '/'); switch (str.length % 4) { case 0: break; case 2: str += '=='; break; case 3: str += '='; break; default: throw 'Invalid token'; } str = (str + '===').slice(0, str.length + (str.length % 4)); str = str.replace(/-/g, '+').replace(/_/g, '/'); str = decodeURIComponent(escape(atob(str))); str = JSON.parse(str); return str; } handleError(error) { if (error.error instanceof ErrorEvent) { // A client-side or network error occurred. Handle it accordingly. console.error('An error occurred:', error.error.message); } else { // The backend returned an unsuccessful response code. // The response body may contain clues as to what went wrong, console.error(`Backend returned code ${error.status}, ` + `body was: ${error.error}`); } // return an observable with a user-facing error message return throwError('Something bad happened; please try again later.'); } ; } KeycloakAuthorizationService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.2.16", ngImport: i0, type: KeycloakAuthorizationService, deps: [{ token: i1.HttpClient }], target: i0.ɵɵFactoryTarget.Injectable }); KeycloakAuthorizationService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "12.2.16", ngImport: i0, type: KeycloakAuthorizationService, providedIn: 'root' }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.16", ngImport: i0, type: KeycloakAuthorizationService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }], ctorParameters: function () { return [{ type: i1.HttpClient }]; } }); class EnableForKeycloakAuthorizationDirective { constructor(element, keycloakAngular, authService) { this.element = element; this.keycloakAngular = keycloakAngular; this.authService = authService; } ngOnInit() { this.noAuthPresentAction(); let authCheck = {}; let requiredScope = null; if (this.requiredAuthorization.includes("#")) { let authArr = this.requiredAuthorization.split("#"); authCheck = { rsname: authArr[0], scope: authArr[1] }; } else { authCheck = { rsname: this.requiredAuthorization }; } this.keycloakAngular.isLoggedIn().then(res => { if (this.authService.checkAuthorization(authCheck)) { this.authPresentAction(); } }); } authPresentAction() { this.element.nativeElement.disabled = false; } noAuthPresentAction() { this.element.nativeElement.disabled = true; } } EnableForKeycloakAuthorizationDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.2.16", ngImport: i0, type: EnableForKeycloakAuthorizationDirective, deps: [{ token: i0.ElementRef }, { token: i1$1.KeycloakService }, { token: KeycloakAuthorizationService }], target: i0.ɵɵFactoryTarget.Directive }); EnableForKeycloakAuthorizationDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "12.0.0", version: "12.2.16", type: EnableForKeycloakAuthorizationDirective, selector: "[enableForKeycloakAuthorization]", inputs: { requiredAuthorization: ["enableForKeycloakAuthorization", "requiredAuthorization"] }, ngImport: i0 }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.16", ngImport: i0, type: EnableForKeycloakAuthorizationDirective, decorators: [{ type: Directive, args: [{ selector: '[enableForKeycloakAuthorization]' }] }], ctorParameters: function () { return [{ type: i0.ElementRef }, { type: i1$1.KeycloakService }, { type: KeycloakAuthorizationService }]; }, propDecorators: { requiredAuthorization: [{ type: Input, args: ['enableForKeycloakAuthorization'] }] } }); /** * A simple guard implementation out of the box. This class should be inherited and * implemented by the application. The only method that should be implemented is #isAccessAllowed. * The reason for this is that the authorization flow is usually not unique, so in this way you will * have more freedom to customize your authorization flow. */ class KeycloakAuthzAuthGuard { constructor(router, keycloakAngular, keycloakAuth) { this.router = router; this.keycloakAngular = keycloakAngular; this.keycloakAuth = keycloakAuth; /** * Indicates if the user is authenticated or not. */ /** * Indicates if the user is authenticated or not. */ this.authenticated = false; /** * Roles of the logged user. It contains the clientId and realm user roles. */ /** * Roles of the logged user. It contains the clientId and realm user roles. */ this.permissions = []; } /** * CanActivate checks if the user is logged in and get the full list of authorizations * that ave been retrieved so far of the logged user. This values are set to * authenticated and permissions properties. * * @param route * @param state */ canActivate(route, state) { return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () { try { this.authenticated = yield this.keycloakAngular.isLoggedIn(); this.permissions = this.keycloakAuth.getPermissions(); const result = yield this.isAccessAllowed(route, state); resolve(result); } catch (error) { reject('An error happened during access validation. Details:' + error); } })); } } class CoreModule { } CoreModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.2.16", ngImport: i0, type: CoreModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); CoreModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "12.0.0", version: "12.2.16", ngImport: i0, type: CoreModule, declarations: [EnableForKeycloakAuthorizationDirective], imports: [CommonModule], exports: [EnableForKeycloakAuthorizationDirective] }); CoreModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "12.2.16", ngImport: i0, type: CoreModule, imports: [[ CommonModule ]] }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.16", ngImport: i0, type: CoreModule, decorators: [{ type: NgModule, args: [{ declarations: [EnableForKeycloakAuthorizationDirective], imports: [ CommonModule ], exports: [ EnableForKeycloakAuthorizationDirective ] }] }] }); class NgxKeycloakAuthzLibModule { } NgxKeycloakAuthzLibModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.2.16", ngImport: i0, type: NgxKeycloakAuthzLibModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); NgxKeycloakAuthzLibModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "12.0.0", version: "12.2.16", ngImport: i0, type: NgxKeycloakAuthzLibModule, imports: [CoreModule], exports: [CoreModule] }); NgxKeycloakAuthzLibModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "12.2.16", ngImport: i0, type: NgxKeycloakAuthzLibModule, imports: [[CoreModule], CoreModule] }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.16", ngImport: i0, type: NgxKeycloakAuthzLibModule, decorators: [{ type: NgModule, args: [{ declarations: [], imports: [CoreModule], exports: [CoreModule] }] }] }); /* * Public API Surface of ngx-keycloak-authz-lib */ /** * Generated bundle index. Do not edit. */ export { CoreModule, EnableForKeycloakAuthorizationDirective, KeycloakAuthorizationService, KeycloakAuthzAuthGuard, NgxKeycloakAuthzLibModule }; //# sourceMappingURL=ngx-keycloak-authz-lib.js.map