Automatically retrieve configuration for authorization
Closes #14562 Signed-off-by: Jon Koops <jonkoops@gmail.com>
This commit is contained in:
parent
7bdd10d77d
commit
7657e71be1
4 changed files with 127 additions and 29 deletions
|
@ -25,6 +25,10 @@ const keycloak = new Keycloak({
|
||||||
});
|
});
|
||||||
|
|
||||||
const authorization = new KeycloakAuthorization(keycloak);
|
const authorization = new KeycloakAuthorization(keycloak);
|
||||||
|
|
||||||
|
await keycloak.init();
|
||||||
|
|
||||||
|
// Now you can use the authorization object to interact with the server.
|
||||||
----
|
----
|
||||||
|
|
||||||
The `keycloak-js/authz` library provides two main features:
|
The `keycloak-js/authz` library provides two main features:
|
||||||
|
|
|
@ -9,3 +9,12 @@ Starting from this release, when `--cache-config-file` is not set, the default I
|
||||||
The embedded `work` cache needs to be configured as a `replicated-cache` for cache invalidation to work as expected.
|
The embedded `work` cache needs to be configured as a `replicated-cache` for cache invalidation to work as expected.
|
||||||
|
|
||||||
Starting from this release, {project_name} check this at startup and will fail to start if it is not configured as such.
|
Starting from this release, {project_name} check this at startup and will fail to start if it is not configured as such.
|
||||||
|
|
||||||
|
= Deprecated APIs for JavaScript Authorization client
|
||||||
|
|
||||||
|
The following APIs for the JavaScript Authorization client are deprecated and will be removed in the next major release:
|
||||||
|
|
||||||
|
- The `ready` property on the `KeycloakAuthorization` instance.
|
||||||
|
- The `init()` method on the `KeycloakAuthorization` instance.
|
||||||
|
|
||||||
|
These APIs are no longer needed as initialization is done automatically on demand when calling methods on the `KeycloakAuthorization` instance. You can safely remove any code that depends on these APIs.
|
||||||
|
|
10
js/libs/keycloak-js/lib/keycloak-authz.d.ts
vendored
10
js/libs/keycloak-js/lib/keycloak-authz.d.ts
vendored
|
@ -102,8 +102,18 @@ declare class KeycloakAuthorization {
|
||||||
rpt: any;
|
rpt: any;
|
||||||
config: { rpt_endpoint: string };
|
config: { rpt_endpoint: string };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the `KeycloakAuthorization` instance.
|
||||||
|
* @deprecated Initialization now happens automatically, calling this method is no longer required.
|
||||||
|
*/
|
||||||
init(): void;
|
init(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A promise that resolves when the `KeycloakAuthorization` instance is initialized.
|
||||||
|
* @deprecated Initialization now happens automatically, using this property is no longer required.
|
||||||
|
*/
|
||||||
|
ready: Promise<void>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method enables client applications to better integrate with resource servers protected by a Keycloak
|
* This method enables client applications to better integrate with resource servers protected by a Keycloak
|
||||||
* policy enforcer using UMA protocol.
|
* policy enforcer using UMA protocol.
|
||||||
|
|
|
@ -20,35 +20,45 @@ var KeycloakAuthorization = function (keycloak, options) {
|
||||||
var _instance = this;
|
var _instance = this;
|
||||||
this.rpt = null;
|
this.rpt = null;
|
||||||
|
|
||||||
var resolve = function () {};
|
// Only here for backwards compatibility, as the configuration is now loaded on demand.
|
||||||
var reject = function () {};
|
// See:
|
||||||
|
// - https://github.com/keycloak/keycloak/pull/6619
|
||||||
|
// - https://issues.redhat.com/browse/KEYCLOAK-10894
|
||||||
|
// TODO: Remove both `ready` property and `init` method in a future version
|
||||||
|
Object.defineProperty(this, 'ready', {
|
||||||
|
get() {
|
||||||
|
console.warn("The 'ready' property is deprecated and will be removed in a future version. Initialization now happens automatically, using this property is no longer required.");
|
||||||
|
return Promise.resolve();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
this.init = () => {
|
||||||
|
console.warn("The 'init()' method is deprecated and will be removed in a future version. Initialization now happens automatically, calling this method is no longer required.");
|
||||||
|
};
|
||||||
|
|
||||||
// detects if browser supports promises
|
/** @type {Promise<unknown> | undefined} */
|
||||||
if (typeof Promise !== "undefined" && Promise.toString().indexOf("[native code]") !== -1) {
|
let configPromise;
|
||||||
this.ready = new Promise(function (res, rej) {
|
|
||||||
resolve = res;
|
|
||||||
reject = rej;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.init = function () {
|
/**
|
||||||
var request = new XMLHttpRequest();
|
* Initializes the configuration or re-uses the existing one if present.
|
||||||
|
* @returns {Promise<void>} A promise that resolves when the configuration is loaded.
|
||||||
request.open('GET', keycloak.authServerUrl + '/realms/' + keycloak.realm + '/.well-known/uma2-configuration');
|
*/
|
||||||
request.onreadystatechange = function () {
|
async function initializeConfigIfNeeded() {
|
||||||
if (request.readyState == 4) {
|
if (_instance.config) {
|
||||||
if (request.status == 200) {
|
return _instance.config;
|
||||||
_instance.config = JSON.parse(request.responseText);
|
|
||||||
resolve();
|
|
||||||
} else {
|
|
||||||
console.error('Could not obtain configuration from server.');
|
|
||||||
reject();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
request.send(null);
|
if (configPromise) {
|
||||||
};
|
return await configPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!keycloak.didInitialize) {
|
||||||
|
throw new Error('The Keycloak instance has not been initialized yet.');
|
||||||
|
}
|
||||||
|
|
||||||
|
configPromise = loadConfig(keycloak.authServerUrl, keycloak.realm);
|
||||||
|
_instance.config = await configPromise;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method enables client applications to better integrate with resource servers protected by a Keycloak
|
* This method enables client applications to better integrate with resource servers protected by a Keycloak
|
||||||
|
@ -57,7 +67,14 @@ var KeycloakAuthorization = function (keycloak, options) {
|
||||||
* The authorization request must be provided with a ticket.
|
* The authorization request must be provided with a ticket.
|
||||||
*/
|
*/
|
||||||
this.authorize = function (authorizationRequest) {
|
this.authorize = function (authorizationRequest) {
|
||||||
this.then = function (onGrant, onDeny, onError) {
|
this.then = async function (onGrant, onDeny, onError) {
|
||||||
|
try {
|
||||||
|
await initializeConfigIfNeeded();
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error, onError);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (authorizationRequest && authorizationRequest.ticket) {
|
if (authorizationRequest && authorizationRequest.ticket) {
|
||||||
var request = new XMLHttpRequest();
|
var request = new XMLHttpRequest();
|
||||||
|
|
||||||
|
@ -121,7 +138,14 @@ var KeycloakAuthorization = function (keycloak, options) {
|
||||||
* Obtains all entitlements from a Keycloak Server based on a given resourceServerId.
|
* Obtains all entitlements from a Keycloak Server based on a given resourceServerId.
|
||||||
*/
|
*/
|
||||||
this.entitlement = function (resourceServerId, authorizationRequest) {
|
this.entitlement = function (resourceServerId, authorizationRequest) {
|
||||||
this.then = function (onGrant, onDeny, onError) {
|
this.then = async function (onGrant, onDeny, onError) {
|
||||||
|
try {
|
||||||
|
await initializeConfigIfNeeded();
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error, onError);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var request = new XMLHttpRequest();
|
var request = new XMLHttpRequest();
|
||||||
|
|
||||||
request.open('POST', _instance.config.token_endpoint, true);
|
request.open('POST', _instance.config.token_endpoint, true);
|
||||||
|
@ -213,9 +237,60 @@ var KeycloakAuthorization = function (keycloak, options) {
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
this.init(this);
|
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtains the configuration from the server.
|
||||||
|
* @param {string} serverUrl The URL of the Keycloak server.
|
||||||
|
* @param {string} realm The realm name.
|
||||||
|
* @returns {Promise<unknown>} A promise that resolves when the configuration is loaded.
|
||||||
|
*/
|
||||||
|
async function loadConfig(serverUrl, realm) {
|
||||||
|
const url = `${serverUrl}/realms/${encodeURIComponent(realm)}/.well-known/uma2-configuration`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await fetchJSON(url);
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error('Could not obtain configuration from server.', { cause: error });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches the JSON data from the given URL.
|
||||||
|
* @param {string} url The URL to fetch the data from.
|
||||||
|
* @returns {Promise<unknown>} A promise that resolves when the data is loaded.
|
||||||
|
*/
|
||||||
|
async function fetchJSON(url) {
|
||||||
|
let response;
|
||||||
|
|
||||||
|
try {
|
||||||
|
response = await fetch(url);
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error('Server did not respond.', { cause: error });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Server responded with an invalid status.');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await response.json();
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error('Server responded with invalid JSON.', { cause: error });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {unknown} error
|
||||||
|
* @param {((error: unknown) => void) | undefined} handler
|
||||||
|
*/
|
||||||
|
function handleError(error, handler) {
|
||||||
|
if (handler) {
|
||||||
|
handler(error);
|
||||||
|
} else {
|
||||||
|
console.error(message, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default KeycloakAuthorization;
|
export default KeycloakAuthorization;
|
||||||
|
|
Loading…
Reference in a new issue