From cf2e4b4313e18171d1bcead0b7f12f8e74f05d4c Mon Sep 17 00:00:00 2001 From: Vincenzo Cestone Date: Fri, 29 Apr 2022 18:29:30 +0200 Subject: [PATCH] Added entitlement in d4s-boot and removed keycloak-authz.js dependency --- boot/d4s-boot.js | 127 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 105 insertions(+), 22 deletions(-) diff --git a/boot/d4s-boot.js b/boot/d4s-boot.js index 819142f..eb10601 100644 --- a/boot/d4s-boot.js +++ b/boot/d4s-boot.js @@ -20,6 +20,8 @@ window.customElements.define('d4s-boot-2', class extends HTMLElement { #locked = true #queue = [] #interval = null + #config = null + #rpt = null constructor() { super() @@ -41,19 +43,18 @@ window.customElements.define('d4s-boot-2', class extends HTMLElement { console.log("Keycloak loaded") return this.initKeycloak() - }).then((authenticated) => { + }).then(authenticated => { if (!authenticated) { throw "Failed to authenticate" } console.log("Keycloak initialized and user authenticated") + //console.log("Token exp: " + this.expirationDate(this.#keycloak.tokenParsed.exp)) + //if an audience is provided then perform also authorization if (this.#audience) { - return this.loadKeycloakAuthorization().then(() => { - this.#authorization = new KeycloakAuthorization(this.#keycloak) - console.log("Keycloak authorization loaded and initialized", this.#authorization) - }) + return this.loadConfig() } else { - return Promise.resolve() + Promise.resolve() } }).then(() => { @@ -90,20 +91,6 @@ window.customElements.define('d4s-boot-2', class extends HTMLElement { return this.#keycloak.init({onLoad: 'login-required', checkLoginIframe: false }) } - loadKeycloakAuthorization() { - return new Promise((resolve, reject) => { - if (typeof KeycloakAuthorization === 'undefined') { - const authz = document.createElement('script') - authz.src = this.#url + '/js/keycloak-authz.js' - authz.type = 'text/javascript' - authz.addEventListener('load', resolve) - document.head.appendChild(authz) - } else { - resolve() - } - }) - } - startStateChecker() { this.#interval = window.setInterval(() => { if (this.#locked) { @@ -116,13 +103,15 @@ window.customElements.define('d4s-boot-2', class extends HTMLElement { if (this.#audience) { console.log("Checking entitlement for audience", this.#audience) const audience = encodeURIComponent(this.#audience) - return this.#authorization.entitlement(audience) + return this.entitlement(audience) } else { return Promise.resolve(this.#keycloak.token) } }).then(token => { console.log("Authorized") + console.log("Token exp: " + this.expirationDate(this.parseJwt(token).exp)) + //console.log("Datetime: " + (new Date())) //transform all queued requests to fetches console.log("All pending requests to promises") let promises = this.#queue.map(r => { @@ -160,7 +149,7 @@ window.customElements.define('d4s-boot-2', class extends HTMLElement { const parseJwt = this.parseJwt const expDt = this.expirationDate const audience = encodeURIComponent(this.#audience) - this.#authorization.entitlement(audience).then(function (rpt) { + this.entitlement(audience).then(function (rpt) { // onGrant callback function. // If authorization was successful you'll receive an RPT // with the necessary permissions to access the resource server @@ -195,6 +184,100 @@ window.customElements.define('d4s-boot-2', class extends HTMLElement { } } + loadConfig() { + const kc = this.#keycloak + return new Promise((resolve, reject) => { + fetch(kc.authServerUrl + '/realms/' + kc.realm + '/.well-known/uma2-configuration') + .then(response => response.json()) + .then(json => { + this.#config = json + resolve(true) + }) + .catch(err => reject("Failed to fetch uma2-configuration from server: " + err)) + }) + } + + /** + * Refactor of entitlement in '/js/keycloak-authz.js' + * (dependency no longer needed) + */ + entitlement(resourceServerId, authorizationRequest) { + return new Promise((resolve, reject) => { + const keycloak = this.#keycloak + const config = this.#config + if (!keycloak || !config) { + reject("Keycloak not initialized") + + } else { + if (!authorizationRequest) { + authorizationRequest = {} + } + var params = "grant_type=urn:ietf:params:oauth:grant-type:uma-ticket&client_id=" + keycloak.clientId + if (authorizationRequest.claimToken) { + params += "&claim_token=" + authorizationRequest.claimToken + if (authorizationRequest.claimTokenFormat) { + params += "&claim_token_format=" + authorizationRequest.claimTokenFormat + } + } + + params += "&audience=" + resourceServerId + + var permissions = authorizationRequest.permissions + + if (!permissions) { + permissions = [] + } + + for (var i = 0; i < permissions.length; i++) { + var resource = permissions[i] + var permission = resource.id + + if (resource.scopes && resource.scopes.length > 0) { + permission += "#" + for (j = 0; j < resource.scopes.length; j++) { + var scope = resource.scopes[j] + if (permission.indexOf('#') != permission.length - 1) { + permission += "," + } + permission += scope + } + } + + params += "&permission=" + permission + } + + var metadata = authorizationRequest.metadata + if (metadata) { + if (metadata.responseIncludeResourceName) { + params += "&response_include_resource_name=" + metadata.responseIncludeResourceName + } + if (metadata.responsePermissionsLimit) { + params += "&response_permissions_limit=" + metadata.responsePermissionsLimit + } + } + + if (this.#rpt) { + params += "&rpt=" + this.#rpt + } + + fetch(config.token_endpoint, { + method: 'POST', + headers: { + "Content-type" : "application/x-www-form-urlencoded", + "Authorization" : "Bearer " + this.#keycloak.token + }, + body: params + }) + .then(response => response.json()) + .then(json => { + this.#rpt = json.access_token + resolve(this.#rpt) + }) + .catch(err => reject(err)) + } + }) + } + static get observedAttributes() { return ["url", "realm", "gateway", "redirect-url", "context"]; }