keycloak-connect
Version:
Keycloak Connect Middleware
160 lines (134 loc) • 4.48 kB
JavaScript
/*
* Copyright 2016 Red Hat Inc. All rights reserved.
*
* 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.
*/
'use strict'
function handlePermissions (permissions, callback) {
for (let i = 0; i < permissions.length; i++) {
const expected = permissions[i].split(':')
const resource = expected[0]
let scope
if (expected.length > 1) {
scope = expected[1]
}
const r = callback(resource, scope)
if (r === false) {
return r
}
}
return true
}
/**
* Construct a policy enforcer.
*
* @param {Config} config Config object.
*
* @constructor
*/
function Enforcer (keycloak, config) {
this.keycloak = keycloak
this.config = config || {}
if (!this.config.response_mode) {
this.config.response_mode = 'permissions'
}
if (!this.config.resource_server_id) {
this.config.resource_server_id = this.keycloak.getConfig().clientId
}
}
Enforcer.prototype.enforce = function enforce (expectedPermissions) {
const keycloak = this.keycloak
const config = this.config
if (typeof expectedPermissions === 'string') {
expectedPermissions = [expectedPermissions]
}
return function (request, response, next) {
if (!expectedPermissions || expectedPermissions.length === 0) {
return next()
}
const authzRequest = {
audience: config.resource_server_id,
response_mode: config.response_mode
}
handlePermissions(expectedPermissions, function (resource, scope) {
if (!authzRequest.permissions) {
authzRequest.permissions = []
}
const permission = { id: resource }
if (scope) {
permission.scopes = [scope]
}
authzRequest.permissions.push(permission)
})
if (request.kauth && request.kauth.grant) {
if (handlePermissions(expectedPermissions, function (resource, scope) {
if (!request.kauth.grant.access_token.hasPermission(resource, scope)) {
return false
}
})) {
return next()
}
}
if (config.claims) {
const claims = config.claims(request)
if (claims) {
authzRequest.claim_token = Buffer.from(JSON.stringify(claims)).toString('base64')
authzRequest.claim_token_format = 'urn:ietf:params:oauth:token-type:jwt'
}
}
if (config.response_mode === 'permissions') {
return keycloak.checkPermissions(authzRequest, request, function (permissions) {
if (handlePermissions(expectedPermissions, function (resource, scope) {
if (!permissions || permissions.length === 0) {
return false
}
for (let j = 0; j < permissions.length; j++) {
const permission = permissions[j]
if (permission.rsid === resource || permission.rsname === resource) {
if (scope) {
if (permission.scopes && permission.scopes.length > 0) {
if (!permission.scopes.includes(scope)) {
return false
}
break
}
return false
}
}
}
})) {
request.permissions = permissions
return next()
}
return keycloak.accessDenied(request, response, next)
}).catch(function () {
return keycloak.accessDenied(request, response, next)
})
} else if (config.response_mode === 'token') {
authzRequest.response_mode = undefined
return keycloak.checkPermissions(authzRequest, request).then(function (grant) {
if (handlePermissions(expectedPermissions, function (resource, scope) {
if (!grant.access_token.hasPermission(resource, scope)) {
return false
}
})) {
return next()
}
return keycloak.accessDenied(request, response, next)
}).catch(function () {
return keycloak.accessDenied(request, response, next)
})
}
}
}
module.exports = Enforcer