[KEYCLOAK-3152] - Keycloak Authorization JS Adapter

This commit is contained in:
Pedro Igor 2016-06-22 14:28:02 -03:00
parent 8402cedd82
commit 905421a292
3 changed files with 251 additions and 27 deletions

View file

@ -58,6 +58,25 @@
<goal>minify</goal> <goal>minify</goal>
</goals> </goals>
</execution> </execution>
<execution>
<id>min-authz-js</id>
<phase>compile</phase>
<configuration>
<charset>utf-8</charset>
<webappSourceDir>${basedir}/src/main/resources</webappSourceDir>
<jsSourceDir>.</jsSourceDir>
<jsSourceFiles>
<jsSourceFile>keycloak-authz.js</jsSourceFile>
</jsSourceFiles>
<webappTargetDir>${project.build.directory}/classes</webappTargetDir>
<jsTargetDir>.</jsTargetDir>
<jsFinalFile>keycloak-authz.js</jsFinalFile>
</configuration>
<goals>
<goal>minify</goal>
</goals>
</execution>
</executions> </executions>
</plugin> </plugin>
</plugins> </plugins>

View file

@ -0,0 +1,170 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed 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.
*
*/
(function( window, undefined ) {
var KeycloakAuthorization = function (keycloak) {
var _instance = this;
this.rpt = null;
this.init = function () {
var request = new XMLHttpRequest();
request.open('GET', keycloak.authServerUrl + '/realms/' + keycloak.realm + '/.well-known/uma-configuration');
request.onreadystatechange = function () {
if (request.readyState == 4) {
if (request.status == 200) {
_instance.config = JSON.parse(request.responseText);
} else {
console.error('Could not obtain configuration from server.');
}
}
}
request.send(null);
};
/**
* This method enables client applications to better integrate with resource servers protected by a Keycloak
* policy enforcer.
*
* In this case, the resource server will respond with a 401 status code and a WWW-Authenticate header holding the
* necessary information to ask a Keycloak server for authorization data using both UMA and Entitlement protocol,
* depending on how the policy enforcer at the resource server was configured.
*/
this.authorize = function (wwwAuthenticateHeader) {
this.then = function (onGrant, onDeny, onError) {
if (wwwAuthenticateHeader.startsWith('UMA')) {
var params = wwwAuthenticateHeader.split(',');
for (i = 0; i < params.length; i++) {
var param = params[i].split('=');
if (param[0] == 'ticket') {
var request = new XMLHttpRequest();
request.open('POST', _instance.config.rpt_endpoint, true);
request.setRequestHeader('Content-Type', 'application/json')
request.setRequestHeader('Authorization', 'Bearer ' + keycloak.token)
request.onreadystatechange = function () {
if (request.readyState == 4) {
var status = request.status;
if (status >= 200 && status < 300) {
var rpt = JSON.parse(request.responseText).rpt;
_instance.rpt = rpt;
onGrant(rpt);
} else if (status == 403) {
if (onDeny) {
onDeny();
} else {
console.error('Authorization request was denied by the server.');
}
} else {
if (onError) {
onError();
} else {
console.error('Could not obtain authorization data from server.');
}
}
}
};
var ticket = param[1].substring(1, param[1].length - 1).trim();
request.send(JSON.stringify(
{
ticket: ticket,
rpt: _instance.rpt
}
));
}
}
} else if (wwwAuthenticateHeader.startsWith('KC_ETT')) {
var params = wwwAuthenticateHeader.substring('KC_ETT'.length).trim().split(',');
var clientId = null;
for (i = 0; i < params.length; i++) {
var param = params[i].split('=');
if (param[0] == 'realm') {
clientId = param[1].substring(1, param[1].length - 1).trim();
}
}
_instance.entitlement(clientId).then(onGrant, onDeny, onError);
}
};
/**
* Obtains all entitlements from a Keycloak Server based on a give resourceServerId.
*/
this.entitlement = function (resourceSeververId) {
this.then = function (onGrant, onDeny, onError) {
var request = new XMLHttpRequest();
request.open('GET', keycloak.authServerUrl + '/realms/' + keycloak.realm + '/authz/entitlement/' + resourceSeververId, true);
request.setRequestHeader('Authorization', 'Bearer ' + keycloak.token)
request.onreadystatechange = function () {
if (request.readyState == 4) {
var status = request.status;
if (status >= 200 && status < 300) {
var rpt = JSON.parse(request.responseText).rpt;
_instance.rpt = rpt;
onGrant(rpt);
} else if (status == 403) {
if (onDeny) {
onDeny();
} else {
console.error('Authorization request was denied by the server.');
}
} else {
if (onError) {
onError();
} else {
console.error('Could not obtain authorization data from server.');
}
}
}
};
request.send(null);
};
return this;
};
return this;
};
this.init(this);
};
if ( typeof module === "object" && module && typeof module.exports === "object" ) {
module.exports = KeycloakAuthorization;
} else {
window.KeycloakAuthorization = KeycloakAuthorization;
if ( typeof define === "function" && define.amd ) {
define( "keycloak-authorization", [], function () { return KeycloakAuthorization; } );
}
}
})( window );

View file

@ -44,35 +44,82 @@ public class JsResource {
@GET @GET
@Path("/keycloak.js") @Path("/keycloak.js")
@Produces("text/javascript") @Produces("text/javascript")
public Response getJs() { public Response getKeycloakJs() {
InputStream inputStream = getClass().getClassLoader().getResourceAsStream("keycloak.js"); return getJs("keycloak.js");
if (inputStream != null) {
CacheControl cacheControl = new CacheControl();
cacheControl.setNoTransform(false);
cacheControl.setMaxAge(Config.scope("theme").getInt("staticMaxAge", -1));
return Response.ok(inputStream).type("text/javascript").cacheControl(cacheControl).build();
} else {
return Response.status(Response.Status.NOT_FOUND).build();
}
} }
@GET @GET
@Path("/{version}/keycloak.js") @Path("/{version}/keycloak.js")
@Produces("text/javascript") @Produces("text/javascript")
public Response getJsWithVersion(@PathParam("version") String version) { public Response getKeycloakJsWithVersion(@PathParam("version") String version) {
if (!version.equals(Version.RESOURCES_VERSION)) { if (!version.equals(Version.RESOURCES_VERSION)) {
return Response.status(Response.Status.NOT_FOUND).build(); return Response.status(Response.Status.NOT_FOUND).build();
} }
return getJs(); return getKeycloakJs();
} }
@GET @GET
@Path("/keycloak.min.js") @Path("/keycloak.min.js")
@Produces("text/javascript") @Produces("text/javascript")
public Response getMinJs() { public Response getKeycloakMinJs() {
InputStream inputStream = getClass().getClassLoader().getResourceAsStream("keycloak.min.js"); return getJs("keycloak.min.js");
}
@GET
@Path("/{version}/keycloak.min.js")
@Produces("text/javascript")
public Response getKeycloakMinJsWithVersion(@PathParam("version") String version) {
if (!version.equals(Version.RESOURCES_VERSION)) {
return Response.status(Response.Status.NOT_FOUND).build();
}
return getKeycloakMinJs();
}
/**
* Get keycloak-authz.js file for javascript clients
*
* @return
*/
@GET
@Path("/keycloak-authz.js")
@Produces("text/javascript")
public Response getKeycloakAuthzJs() {
return getJs("keycloak-authz.js");
}
@GET
@Path("/{version}/keycloak-authz.js")
@Produces("text/javascript")
public Response getKeycloakAuthzJsWithVersion(@PathParam("version") String version) {
if (!version.equals(Version.RESOURCES_VERSION)) {
return Response.status(Response.Status.NOT_FOUND).build();
}
return getKeycloakAuthzJs();
}
@GET
@Path("/keycloak-authz.min.js")
@Produces("text/javascript")
public Response getKeycloakAuthzMinJs() {
return getJs("keycloak-authz.min.js");
}
@GET
@Path("/{version}/keycloak-authz.min.js")
@Produces("text/javascript")
public Response getKeycloakAuthzMinJsWithVersion(@PathParam("version") String version) {
if (!version.equals(Version.RESOURCES_VERSION)) {
return Response.status(Response.Status.NOT_FOUND).build();
}
return getKeycloakAuthzMinJs();
}
private Response getJs(String name) {
InputStream inputStream = getClass().getClassLoader().getResourceAsStream(name);
if (inputStream != null) { if (inputStream != null) {
CacheControl cacheControl = new CacheControl(); CacheControl cacheControl = new CacheControl();
cacheControl.setNoTransform(false); cacheControl.setNoTransform(false);
@ -83,16 +130,4 @@ public class JsResource {
return Response.status(Response.Status.NOT_FOUND).build(); return Response.status(Response.Status.NOT_FOUND).build();
} }
} }
@GET
@Path("/{version}/keycloak.min.js")
@Produces("text/javascript")
public Response getMinJsWithVersion(@PathParam("version") String version) {
if (!version.equals(Version.RESOURCES_VERSION)) {
return Response.status(Response.Status.NOT_FOUND).build();
}
return getMinJs();
}
} }