ngx-keycloak-authz-lib
Version:
Bibliothèque d'authorisation Keycloak pour Angular
462 lines (453 loc) • 19.5 kB
JavaScript
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