mirror of
https://github.com/bmlong137/alfresco-keycloak.git
synced 2025-09-10 14:11:09 +00:00
Enable roles for authority lookup / permission management
This commit is contained in:
15
pom.xml
15
pom.xml
@@ -204,6 +204,21 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>de.acosix.alfresco.utility</groupId>
|
||||
<artifactId>de.acosix.alfresco.utility.repo</artifactId>
|
||||
<version>${acosix.utility.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>de.acosix.alfresco.utility</groupId>
|
||||
<artifactId>de.acosix.alfresco.utility.repo</artifactId>
|
||||
<version>${acosix.utility.version}</version>
|
||||
<classifier>installable</classifier>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.orderofthebee.support-tools</groupId>
|
||||
<artifactId>support-tools-repo</artifactId>
|
||||
|
@@ -75,6 +75,11 @@
|
||||
<constructor-arg value="cache.${moduleId}.ticketTokenCache" />
|
||||
</bean>
|
||||
|
||||
<bean id="${moduleId}.ScriptRoleService" parent="baseJavaScriptExtension" class="${project.artifactId}.roles.ScriptRoleService">
|
||||
<property name="extensionName" value="keycloakRoles" />
|
||||
<property name="roleService" ref="${moduleId}.RoleService" />
|
||||
</bean>
|
||||
|
||||
<bean id="webscript.de.acosix.keycloak.roles.get" class="${project.artifactId}.web.scripts.RolesGet" parent="webscript">
|
||||
<property name="roleService" ref="${moduleId}.RoleService" />
|
||||
</bean>
|
||||
|
@@ -0,0 +1,18 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<extension>
|
||||
<modules>
|
||||
<module>
|
||||
<id>${moduleId} - Web Script Extension</id>
|
||||
<description>${project.name}</description>
|
||||
<version>${noSnapshotVersion}</version>
|
||||
<auto-deploy>true</auto-deploy>
|
||||
|
||||
<customizations>
|
||||
<customization>
|
||||
<targetPackageRoot>org.alfresco</targetPackageRoot>
|
||||
<sourcePackageRoot>de.acosix.keycloak.customisations</sourcePackageRoot>
|
||||
</customization>
|
||||
</customizations>
|
||||
</module>
|
||||
</modules>
|
||||
</extension>
|
@@ -256,10 +256,6 @@ public class KeycloakAuthenticationComponent extends AbstractAuthenticationCompo
|
||||
public RefreshableAccessTokenHolder checkAndRefreshTicketToken(final RefreshableAccessTokenHolder ticketToken)
|
||||
throws AuthenticationException
|
||||
{
|
||||
if (this.failExpiredTicketTokens && ticketToken.isExpired())
|
||||
{
|
||||
throw new AuthenticationException("Keycloak access token has expired - authentication ticket is no longer valid");
|
||||
}
|
||||
|
||||
RefreshableAccessTokenHolder result = null;
|
||||
if (ticketToken.canRefresh() && ticketToken.shouldRefresh(this.deployment.getTokenMinimumTimeToLive()))
|
||||
@@ -289,6 +285,10 @@ public class KeycloakAuthenticationComponent extends AbstractAuthenticationCompo
|
||||
throw new AuthenticationException("Failed to refresh Keycloak authentication", ioex);
|
||||
}
|
||||
}
|
||||
else if (this.failExpiredTicketTokens && ticketToken.isExpired())
|
||||
{
|
||||
throw new AuthenticationException("Keycloak access token has expired - authentication ticket is no longer valid");
|
||||
}
|
||||
|
||||
if (result != null || !ticketToken.isExpired())
|
||||
{
|
||||
|
@@ -27,6 +27,16 @@ import java.util.List;
|
||||
public class NoOpRoleServiceImpl implements RoleService
|
||||
{
|
||||
|
||||
/**
|
||||
*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public List<Role> listRoles()
|
||||
{
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* {@inheritDoc}
|
||||
@@ -37,6 +47,16 @@ public class NoOpRoleServiceImpl implements RoleService
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public List<Role> listRoles(final boolean realmOnly)
|
||||
{
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* {@inheritDoc}
|
||||
@@ -47,6 +67,16 @@ public class NoOpRoleServiceImpl implements RoleService
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public List<Role> listRoles(final String resourceName)
|
||||
{
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* {@inheritDoc}
|
||||
|
@@ -25,6 +25,13 @@ import java.util.List;
|
||||
public interface RoleService
|
||||
{
|
||||
|
||||
/**
|
||||
* Retrieves roles in the main realm and/or resource scopes (as far as possible based on configuration).
|
||||
*
|
||||
* @return the list of roles
|
||||
*/
|
||||
List<Role> listRoles();
|
||||
|
||||
/**
|
||||
* Finds matching roles in the main realm and/or resource scopes (as far as possible based on configuration).
|
||||
*
|
||||
@@ -35,6 +42,17 @@ public interface RoleService
|
||||
*/
|
||||
List<Role> findRoles(String shortNameFilter);
|
||||
|
||||
/**
|
||||
* Retrieves roles in the main realm and/or resource scopes (as far as possible based on configuration).
|
||||
*
|
||||
* @param realmOnly
|
||||
* {@code true} if the list operation should only consider the main realm, or {@code false} if both realm and resource
|
||||
* scopes are allowed to be listed
|
||||
*
|
||||
* @return the list of roles
|
||||
*/
|
||||
List<Role> listRoles(boolean realmOnly);
|
||||
|
||||
/**
|
||||
* Finds matching roles in the main realm and/or resource scopes (as far as possible based on configuration).
|
||||
*
|
||||
@@ -48,6 +66,16 @@ public interface RoleService
|
||||
*/
|
||||
List<Role> findRoles(String shortNameFilter, boolean realmOnly);
|
||||
|
||||
/**
|
||||
* Retrieves roles in a specific resource scope (as far as possible based on configuration).
|
||||
*
|
||||
* @param resourceName
|
||||
* the name of the resource for which to retrieve roles
|
||||
*
|
||||
* @return the list of roles
|
||||
*/
|
||||
List<Role> listRoles(String resourceName);
|
||||
|
||||
/**
|
||||
* Finds matching roles in a specific resource scope (as far as possible based on configuration).
|
||||
*
|
||||
|
@@ -215,6 +215,16 @@ public class RoleServiceImpl implements InitializingBean, RoleService
|
||||
this.hiddenMappedRoles = hiddenMappedRoles;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public List<Role> listRoles()
|
||||
{
|
||||
return this.doFindRoles(null, false);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* {@inheritDoc}
|
||||
@@ -222,7 +232,18 @@ public class RoleServiceImpl implements InitializingBean, RoleService
|
||||
@Override
|
||||
public List<Role> findRoles(final String shortNameFilter)
|
||||
{
|
||||
return this.findRoles(shortNameFilter, false);
|
||||
ParameterCheck.mandatoryString("shortNameFilter", shortNameFilter);
|
||||
return this.doFindRoles(shortNameFilter, false);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public List<Role> listRoles(final boolean realmOnly)
|
||||
{
|
||||
return this.doFindRoles(null, realmOnly);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -231,6 +252,35 @@ public class RoleServiceImpl implements InitializingBean, RoleService
|
||||
*/
|
||||
@Override
|
||||
public List<Role> findRoles(final String shortNameFilter, final boolean realmOnly)
|
||||
{
|
||||
ParameterCheck.mandatoryString("shortNameFilter", shortNameFilter);
|
||||
return this.doFindRoles(shortNameFilter, realmOnly);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public List<Role> listRoles(final String resourceName)
|
||||
{
|
||||
ParameterCheck.mandatory("resourceName", resourceName);
|
||||
return this.doFindRoles(resourceName, null);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public List<Role> findRoles(final String resourceName, final String shortNameFilter)
|
||||
{
|
||||
ParameterCheck.mandatory("resourceName", resourceName);
|
||||
ParameterCheck.mandatoryString("shortNameFilter", shortNameFilter);
|
||||
return this.doFindRoles(resourceName, shortNameFilter);
|
||||
}
|
||||
|
||||
protected List<Role> doFindRoles(final String shortNameFilter, final boolean realmOnly)
|
||||
{
|
||||
final List<Role> roles;
|
||||
|
||||
@@ -254,7 +304,7 @@ public class RoleServiceImpl implements InitializingBean, RoleService
|
||||
if (!realmOnly && this.processResourceRoles)
|
||||
{
|
||||
this.resourceRoleNameMapper.keySet().stream().forEach(resourceName -> {
|
||||
final List<Role> resourceRoles = this.findRoles(resourceName, shortNameFilter);
|
||||
final List<Role> resourceRoles = this.doFindRoles(resourceName, shortNameFilter);
|
||||
roles.addAll(resourceRoles);
|
||||
});
|
||||
}
|
||||
@@ -275,15 +325,8 @@ public class RoleServiceImpl implements InitializingBean, RoleService
|
||||
return roles;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public List<Role> findRoles(final String resourceName, final String shortNameFilter)
|
||||
protected List<Role> doFindRoles(final String resourceName, final String shortNameFilter)
|
||||
{
|
||||
ParameterCheck.mandatory("resourceName", resourceName);
|
||||
|
||||
List<Role> roles;
|
||||
|
||||
if (this.enabled && !this.processResourceRoles)
|
||||
|
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
* Copyright 2019 - 2020 Acosix GmbH
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
package de.acosix.alfresco.keycloak.repo.roles;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.alfresco.repo.jscript.BaseScopableProcessorExtension;
|
||||
import org.alfresco.util.PropertyCheck;
|
||||
import org.mozilla.javascript.Context;
|
||||
import org.mozilla.javascript.Scriptable;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
|
||||
/**
|
||||
* This service exposes mapped Keycloak roles to scripts running within the Repository-tier script processor, e.g. web script controllers.
|
||||
*
|
||||
* @author Axel Faust
|
||||
*/
|
||||
public class ScriptRoleService extends BaseScopableProcessorExtension implements InitializingBean
|
||||
{
|
||||
|
||||
protected RoleService roleService;
|
||||
|
||||
/**
|
||||
*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void afterPropertiesSet()
|
||||
{
|
||||
PropertyCheck.mandatory(this, "roleService", this.roleService);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param roleService
|
||||
* the roleService to set
|
||||
*/
|
||||
public void setRoleService(final RoleService roleService)
|
||||
{
|
||||
this.roleService = roleService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves roles in the main realm and/or resource scopes (as far as possible based on configuration).
|
||||
*
|
||||
* @return the list of roles
|
||||
*/
|
||||
public Scriptable listRoles()
|
||||
{
|
||||
final List<Role> roles = this.roleService.listRoles();
|
||||
final Scriptable roleArray = this.makeRoleArray(roles);
|
||||
return roleArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds matching roles in the main realm and/or resource scopes (as far as possible based on configuration).
|
||||
*
|
||||
* @param shortNameFilter
|
||||
* name pattern on which to filter groups - the filter will be applied on both the original Keycloak and the mapped Alfresco
|
||||
* role name, and a match in either will result the role to be considered a match
|
||||
* @return the list of matching roles
|
||||
*/
|
||||
public Scriptable findRoles(final String shortNameFilter)
|
||||
{
|
||||
final List<Role> roles = this.roleService.findRoles(shortNameFilter);
|
||||
final Scriptable roleArray = this.makeRoleArray(roles);
|
||||
return roleArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves roles in the main realm and/or resource scopes (as far as possible based on configuration).
|
||||
*
|
||||
* @param realmOnly
|
||||
* {@code true} if the list operation should only consider the main realm, or {@code false} if both realm and resource
|
||||
* scopes are allowed to be listed
|
||||
*
|
||||
* @return the list of roles
|
||||
*/
|
||||
public Scriptable listRoles(final boolean realmOnly)
|
||||
{
|
||||
final List<Role> roles = this.roleService.listRoles(realmOnly);
|
||||
final Scriptable roleArray = this.makeRoleArray(roles);
|
||||
return roleArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds matching roles in the main realm and/or resource scopes (as far as possible based on configuration).
|
||||
*
|
||||
* @param shortNameFilter
|
||||
* name pattern on which to filter groups - the filter will be applied on both the original Keycloak and the mapped Alfresco
|
||||
* role name, and a match in either will result the role to be considered a match
|
||||
* @param realmOnly
|
||||
* {@code true} if the search operation should only consider the main realm, or {@code false} if both realm and resource
|
||||
* scopes are allowed to be searched
|
||||
* @return the list of matching roles
|
||||
*/
|
||||
public Scriptable findRoles(final String shortNameFilter, final boolean realmOnly)
|
||||
{
|
||||
final List<Role> roles = this.roleService.findRoles(shortNameFilter, realmOnly);
|
||||
final Scriptable roleArray = this.makeRoleArray(roles);
|
||||
return roleArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves roles in a specific resource scope (as far as possible based on configuration).
|
||||
*
|
||||
* @param resourceName
|
||||
* the name of the resource for which to retrieve roles
|
||||
*
|
||||
* @return the list of roles
|
||||
*/
|
||||
public Scriptable listRoles(final String resourceName)
|
||||
{
|
||||
final List<Role> roles = this.roleService.listRoles(resourceName);
|
||||
final Scriptable roleArray = this.makeRoleArray(roles);
|
||||
return roleArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds matching roles in a specific resource scope (as far as possible based on configuration).
|
||||
*
|
||||
* @param resourceName
|
||||
* the name of the resource for which to retrieve roles
|
||||
* @param shortNameFilter
|
||||
* name pattern on which to filter groups - the filter will be applied on both the original Keycloak and the mapped Alfresco
|
||||
* role name, and a match in either will result the role to be considered a match
|
||||
* @return the list of matching roles
|
||||
*/
|
||||
public Scriptable findRoles(final String resourceName, final String shortNameFilter)
|
||||
{
|
||||
final List<Role> roles = this.roleService.findRoles(resourceName, shortNameFilter);
|
||||
final Scriptable roleArray = this.makeRoleArray(roles);
|
||||
return roleArray;
|
||||
}
|
||||
|
||||
protected Scriptable makeRoleArray(final List<Role> roles)
|
||||
{
|
||||
final Scriptable sitesArray = Context.getCurrentContext().newArray(this.getScope(), roles.toArray(new Object[0]));
|
||||
return sitesArray;
|
||||
}
|
||||
}
|
@@ -103,7 +103,9 @@ public class RolesGet extends DeclarativeWebScript implements InitializingBean
|
||||
maxItems = Integer.parseInt(maxItemsParam);
|
||||
}
|
||||
|
||||
final List<Role> roles = this.roleService.findRoles(shortNameFilterParam);
|
||||
final List<Role> roles = shortNameFilterParam != null && !shortNameFilterParam.trim().isEmpty()
|
||||
? this.roleService.findRoles(shortNameFilterParam)
|
||||
: this.roleService.listRoles();
|
||||
|
||||
if (roles.isEmpty())
|
||||
{
|
||||
|
@@ -0,0 +1,57 @@
|
||||
/* global keycloakRoles: false */
|
||||
|
||||
var keycloakRolesHash;
|
||||
|
||||
function process(permissions)
|
||||
{
|
||||
var idx, permissionObj, authority, keycloakRolesArr, jdx, role;
|
||||
|
||||
for (idx = 0; idx < permissions.length; idx++)
|
||||
{
|
||||
permissionObj = permissions[idx];
|
||||
authority = permissionObj.authority.name;
|
||||
|
||||
if (authority && /^ROLE_.+$/.test(authority))
|
||||
{
|
||||
// lazy init
|
||||
if (!keycloakRolesHash)
|
||||
{
|
||||
keycloakRolesArr = keycloakRoles.listRoles();
|
||||
keycloakRolesHash = {};
|
||||
for (jdx = 0; jdx < keycloakRolesArr.length; jdx++)
|
||||
{
|
||||
keycloakRolesHash[keycloakRolesArr[jdx].name] = keycloakRolesArr[jdx];
|
||||
}
|
||||
}
|
||||
|
||||
// only process if role mapped from Keycloak
|
||||
if (keycloakRolesHash.hasOwnProperty(authority))
|
||||
{
|
||||
role = keycloakRolesHash[authority];
|
||||
if (role)
|
||||
{
|
||||
// enhance permissionObj.authority to at least add displayName
|
||||
// may/will still look like a user in UI which only differentiates groups / users
|
||||
permissionObj.authority = {
|
||||
name : authority,
|
||||
fullName : authority,
|
||||
shortName : authority.substring(5),
|
||||
displayName : role.description || role.keycloakName
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function main()
|
||||
{
|
||||
var permissions = model.data;
|
||||
|
||||
process(permissions.direct);
|
||||
process(permissions.inherited);
|
||||
|
||||
model.data = permissions;
|
||||
}
|
||||
|
||||
main();
|
@@ -0,0 +1,44 @@
|
||||
/* global keycloakRoles: false */
|
||||
function main()
|
||||
{
|
||||
var nodeRef, node, permissions, idx, permissionObj, add, authority, permission, keycloakRolesHash, keycloakRolesArr, jdx;
|
||||
|
||||
nodeRef = url.templateArgs.store_type + '://' + url.templateArgs.store_id + '/' + url.templateArgs.id;
|
||||
// normally not a fan of Alfresco utils object, but needed here for consistency with base script (there via parse-args.lib.js import)
|
||||
node = utils.resolveNodeReference(nodeRef);
|
||||
|
||||
permissions = json.getJSONArray('permissions');
|
||||
for (idx = 0; idx < permissions.length(); idx++)
|
||||
{
|
||||
permissionObj = permissions.getJSONObject(idx);
|
||||
add = !permissionObj.has('remove') || !permissionObj.getBoolean('remove');
|
||||
|
||||
if (add)
|
||||
{
|
||||
authority = permissionObj.getString('authority');
|
||||
permission = permissionObj.getString('role');
|
||||
|
||||
if (/^ROLE_.+$/.test(authority))
|
||||
{
|
||||
// lazy init
|
||||
if (!keycloakRolesHash)
|
||||
{
|
||||
keycloakRolesArr = keycloakRoles.listRoles();
|
||||
keycloakRolesHash = {};
|
||||
for (jdx = 0; jdx < keycloakRolesArr.length; jdx++)
|
||||
{
|
||||
keycloakRolesHash[keycloakRolesArr[jdx].name] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// only process if role mapped from Keycloak
|
||||
if (keycloakRolesHash.hasOwnProperty(authority))
|
||||
{
|
||||
node.setPermission(permission, authority);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
@@ -88,6 +88,12 @@
|
||||
<classifier>installable</classifier>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>de.acosix.alfresco.utility</groupId>
|
||||
<artifactId>de.acosix.alfresco.utility.repo</artifactId>
|
||||
<classifier>installable</classifier>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>de.acosix.alfresco.utility</groupId>
|
||||
<artifactId>de.acosix.alfresco.utility.core.share</artifactId>
|
||||
|
@@ -392,6 +392,10 @@ public class KeycloakAuthenticationFilter implements DependencyInjectedFilter, I
|
||||
|
||||
this.continueFilterChain(context, request, response, chain);
|
||||
}
|
||||
else if (res.isCommitted())
|
||||
{
|
||||
LOGGER.debug("Response has already been committed by skip condition-check - not processing it any further");
|
||||
}
|
||||
else
|
||||
{
|
||||
this.processKeycloakAuthenticationAndActions(context, req, res, chain);
|
||||
@@ -819,34 +823,34 @@ public class KeycloakAuthenticationFilter implements DependencyInjectedFilter, I
|
||||
|
||||
if (!this.externalAuthEnabled || !this.filterEnabled)
|
||||
{
|
||||
LOGGER.debug("Skipping doFilter as filter and/or external authentication are not enabled");
|
||||
LOGGER.debug("Skipping processKeycloakAuthenticationAndActions as filter and/or external authentication are not enabled");
|
||||
skip = true;
|
||||
}
|
||||
else if (this.keycloakDeployment == null)
|
||||
{
|
||||
LOGGER.debug("Skipping doFilter as Keycloak adapter was not properly initialised");
|
||||
LOGGER.debug("Skipping processKeycloakAuthenticationAndActions as Keycloak adapter was not properly initialised");
|
||||
skip = true;
|
||||
}
|
||||
else if (servletRequestUri.matches(KEYCLOAK_ACTION_URL_PATTERN))
|
||||
{
|
||||
LOGGER.debug("Explicitly not skipping doFilter as Keycloak action URL is being called");
|
||||
LOGGER.debug("Explicitly not skipping processKeycloakAuthenticationAndActions as Keycloak action URL is being called");
|
||||
}
|
||||
else if (req.getParameter("state") != null && req.getParameter("code") != null && this.hasStateCookie(req))
|
||||
{
|
||||
LOGGER.debug(
|
||||
"Explicitly not skipping doFilter as state and code query parameters of OAuth2 redirect as well as state cookie are present");
|
||||
"Explicitly not skipping processKeycloakAuthenticationAndActions as state and code query parameters of OAuth2 redirect as well as state cookie are present");
|
||||
}
|
||||
else if (authHeader != null && authHeader.toLowerCase(Locale.ENGLISH).startsWith("bearer "))
|
||||
{
|
||||
LOGGER.debug("Explicitly not skipping doFilter as Bearer authorization header is present");
|
||||
LOGGER.debug("Explicitly not skipping processKeycloakAuthenticationAndActions as Bearer authorization header is present");
|
||||
}
|
||||
else if (authHeader != null && authHeader.toLowerCase(Locale.ENGLISH).startsWith("basic "))
|
||||
{
|
||||
LOGGER.debug("Explicitly not skipping doFilter as Basic authorization header is present");
|
||||
LOGGER.debug("Explicitly not skipping processKeycloakAuthenticationAndActions as Basic authorization header is present");
|
||||
}
|
||||
else if (authHeader != null)
|
||||
{
|
||||
LOGGER.debug("Skipping doFilter as non-OIDC / non-Basic authorization header is present");
|
||||
LOGGER.debug("Skipping processKeycloakAuthenticationAndActions as non-OIDC / non-Basic authorization header is present");
|
||||
skip = true;
|
||||
}
|
||||
else if (currentSession != null && AuthenticationUtil.isAuthenticated(req))
|
||||
@@ -858,7 +862,11 @@ public class KeycloakAuthenticationFilter implements DependencyInjectedFilter, I
|
||||
}
|
||||
else
|
||||
{
|
||||
LOGGER.debug("Skipping doFilter as non-Keycloak-authenticated session is already established");
|
||||
// TODO Validate via custom /touch to check if session is still valid
|
||||
// custom => handle potential 302 instead of 401 response from Keycloak-enabled backend
|
||||
// custom => deal with redirect host being unknown (similar to our auth-server-url vs. directAuthHost case)
|
||||
LOGGER.debug(
|
||||
"Skipping processKeycloakAuthenticationAndActions as non-Keycloak-authenticated session is already established");
|
||||
skip = true;
|
||||
}
|
||||
}
|
||||
@@ -868,26 +876,26 @@ public class KeycloakAuthenticationFilter implements DependencyInjectedFilter, I
|
||||
final String noauth = proxyMatcher.group(2);
|
||||
if (noauth != null && !noauth.trim().isEmpty())
|
||||
{
|
||||
LOGGER.debug("Skipping doFilter as proxy servlet to noauth endpoint {} is being called");
|
||||
LOGGER.debug("Skipping processKeycloakAuthenticationAndActions as proxy servlet to noauth endpoint {} is being called");
|
||||
skip = true;
|
||||
}
|
||||
else if (!endpoint.equals(this.primaryEndpoint)
|
||||
&& (this.secondaryEndpoints == null || !this.secondaryEndpoints.contains(endpoint)))
|
||||
{
|
||||
LOGGER.debug(
|
||||
"Skipping doFilter on proxy servlet call as endpoint {} has not been configured as a primary / secondary endpoint to handle");
|
||||
"Skipping processKeycloakAuthenticationAndActions on proxy servlet call as endpoint {} has not been configured as a primary / secondary endpoint to handle");
|
||||
skip = true;
|
||||
}
|
||||
}
|
||||
else if (PAGE_SERVLET_PATH.equals(servletPath) && (LOGIN_PATH_INFORMATION.equals(pathInfo)
|
||||
|| (pathInfo == null && LOGIN_PAGE_TYPE_PARAMETER_VALUE.equals(req.getParameter(PAGE_TYPE_PARAMETER_NAME)))))
|
||||
{
|
||||
LOGGER.debug("Skipping doFilter as login page was explicitly requested");
|
||||
LOGGER.debug("Skipping processKeycloakAuthenticationAndActions as login page was explicitly requested");
|
||||
skip = true;
|
||||
}
|
||||
else if (this.isNoAuthPage(req))
|
||||
{
|
||||
LOGGER.debug("Skipping doFilter as requested page does not require authentication");
|
||||
LOGGER.debug("Skipping processKeycloakAuthenticationAndActions as requested page does not require authentication");
|
||||
skip = true;
|
||||
}
|
||||
|
||||
@@ -928,7 +936,7 @@ public class KeycloakAuthenticationFilter implements DependencyInjectedFilter, I
|
||||
boolean skip = false;
|
||||
if (currentSession != null)
|
||||
{
|
||||
LOGGER.debug("Skipping doFilter as Keycloak-authentication session is still valid");
|
||||
LOGGER.debug("Skipping processKeycloakAuthenticationAndActions as Keycloak-authentication session is still valid");
|
||||
skip = true;
|
||||
|
||||
if (keycloakAccount instanceof OidcKeycloakAccount)
|
||||
|
@@ -0,0 +1,3 @@
|
||||
<@markup id="keycloak-js" target="js" action="after">
|
||||
<@script src="${url.context}/res/acosix/keycloak/components/manage-permissions/manage-permissions.js" group="manage-permissions"/>
|
||||
</@>
|
@@ -0,0 +1,45 @@
|
||||
function main()
|
||||
{
|
||||
var requestedAuthorityType, filter, maxResults, url, response, responseObj, authorities, idx;
|
||||
|
||||
requestedAuthorityType = args.authorityType ? String(args.authorityType).toLowerCase() : 'all';
|
||||
filter = args.filter ? String(args.filter) : null;
|
||||
maxResults = args.maxResults ? parseInt(String(args.maxResults)) : 0;
|
||||
|
||||
if (requestedAuthorityType === 'all')
|
||||
{
|
||||
url = '/acosix/api/keycloak/roles';
|
||||
if (maxResults > 0)
|
||||
{
|
||||
url += '?maxItems=' + maxResults;
|
||||
}
|
||||
if (filter)
|
||||
{
|
||||
url += url.indexOf('?') === -1 ? '?' : '&';
|
||||
url += 'shortNameFilter=' + encodeURIComponent(filter);
|
||||
}
|
||||
response = remote.call(url);
|
||||
if (response.status.code === 200)
|
||||
{
|
||||
responseObj = JSON.parse(response.text);
|
||||
authorities = model.authorities;
|
||||
|
||||
for (idx = 0; idx < responseObj.data.length; idx++)
|
||||
{
|
||||
// UI likely cannot handle authorityType ROLE, which would be semantically correct
|
||||
authorities.push({
|
||||
authorityType : 'GROUP',
|
||||
shortName : responseObj.data[idx].shortName,
|
||||
fullName : responseObj.data[idx].fullName,
|
||||
displayName : responseObj.data[idx].displayName,
|
||||
description : responseObj.data[idx].fullName,
|
||||
metadata : {}
|
||||
});
|
||||
}
|
||||
|
||||
model.authorities = authorities;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
@@ -0,0 +1,35 @@
|
||||
(function()
|
||||
{
|
||||
var Dom = YAHOO.util.Dom;
|
||||
|
||||
if (Alfresco.component.ManagePermissions)
|
||||
{
|
||||
Alfresco.component.ManagePermissions.prototype.fnRenderCellAuthorityIcon = function Acosix_Keycloak_ManagePermissions_fnRenderCellAuthorityIcon()
|
||||
{
|
||||
return function Acosix_Keycloak_ManagePermissions_renderCellAuthorityIcon(elCell, oRecord, oColumn)
|
||||
{
|
||||
var authority, isGroupLike, iconUrl;
|
||||
|
||||
Dom.setStyle(elCell, 'width', oColumn.width + 'px');
|
||||
Dom.setStyle(elCell.parentNode, 'width', oColumn.width + 'px');
|
||||
|
||||
authority = oRecord.getData('authority');
|
||||
// main modification - treat ROLE just like a group, because any number of users can belong to a role
|
||||
isGroupLike = /^(GROUP|ROLE)_.*/.test(authority.name);
|
||||
// end main modification
|
||||
iconUrl = Alfresco.constants.URL_RESCONTEXT + 'components/images/' + (isGroupLike ? 'group' : 'no-user-photo') + '-64.png';
|
||||
|
||||
if (authority.avatar && authority.avatar.length !== 0)
|
||||
{
|
||||
iconUrl = Alfresco.constants.PROXY_URI + authority.avatar + '?c=queue&ph=true';
|
||||
}
|
||||
else if (authority.iconUrl)
|
||||
{
|
||||
// As passed-back from the Authority Finder component
|
||||
iconUrl = authority.iconUrl;
|
||||
}
|
||||
elCell.innerHTML = '<img class="icon32" src="' + iconUrl + '" alt="icon" />';
|
||||
};
|
||||
};
|
||||
}
|
||||
}());
|
@@ -21,18 +21,10 @@
|
||||
<connector>
|
||||
<id>alfrescoCookie</id>
|
||||
<name>Alfresco Connector</name>
|
||||
<description>Connects to an Alfresco instance using cookie-based authentication</description>
|
||||
<description>Connects to an Alfresco instance using cookie-based authentication and awareness of OIDC bearer tokens</description>
|
||||
<class>de.acosix.alfresco.keycloak.share.remote.BearerTokenAwareSlingshotAlfrescoConnector</class>
|
||||
</connector>
|
||||
|
||||
<connector>
|
||||
<id>alfrescoHeader</id>
|
||||
<name>Alfresco Connector</name>
|
||||
<description>Connects to an Alfresco instance using header and cookie-based authentication</description>
|
||||
<class>de.acosix.alfresco.keycloak.share.remote.BearerTokenAwareSlingshotAlfrescoConnector</class>
|
||||
<userHeader>SsoUserHeader</userHeader>
|
||||
</connector>
|
||||
|
||||
<endpoint>
|
||||
<id>alfresco</id>
|
||||
<name>Alfresco - user access</name>
|
||||
@@ -47,7 +39,7 @@
|
||||
<id>alfresco-feed</id>
|
||||
<name>Alfresco Feed</name>
|
||||
<description>Alfresco Feed - supports basic HTTP authentication via the EndPointProxyServlet</description>
|
||||
<connector-id>alfrescoHeader</connector-id>
|
||||
<connector-id>alfrescoCookie</connector-id>
|
||||
<endpoint-url>http://repository:8080/alfresco/wcs</endpoint-url>
|
||||
<basic-auth>true</basic-auth>
|
||||
<identity>user</identity>
|
||||
@@ -58,10 +50,8 @@
|
||||
<id>alfresco-api</id>
|
||||
<parent-id>alfresco</parent-id>
|
||||
<name>Alfresco Public API - user access</name>
|
||||
<description>Access to Alfresco Repository Public API that require user authentication.
|
||||
This makes use of the authentication that is provided by parent 'alfresco' endpoint.
|
||||
</description>
|
||||
<connector-id>alfrescoHeader</connector-id>
|
||||
<description>Access to Alfresco Repository Public API that require user authentication. This makes use of the authentication that is provided by parent 'alfresco' endpoint. </description>
|
||||
<connector-id>alfrescoCookie</connector-id>
|
||||
<endpoint-url>http://repository:8080/alfresco/api</endpoint-url>
|
||||
<identity>user</identity>
|
||||
<external-auth>true</external-auth>
|
||||
|
@@ -70,6 +70,8 @@
|
||||
<include>de.acosix.alfresco.utility:de.acosix.alfresco.utility.core.repo.quartz2:*</include>
|
||||
<include>${project.groupId}:de.acosix.alfresco.keycloak.repo.deps:*</include>
|
||||
<include>de.acosix.alfresco.utility:de.acosix.alfresco.utility.core.repo:jar:installable:*</include>
|
||||
<!-- full acosix-utility repo module required for extension to repository-tier permissions.post.json.js to take effect -->
|
||||
<include>de.acosix.alfresco.utility:de.acosix.alfresco.utility.repo:jar:installable:*</include>
|
||||
<include>${project.groupId}:de.acosix.alfresco.keycloak.repo:jar:installable:*</include>
|
||||
</includes>
|
||||
<scope>test</scope>
|
||||
|
Reference in New Issue
Block a user