Enable roles for authority lookup / permission management

This commit is contained in:
AFaust
2020-02-16 15:01:59 +01:00
parent 0f974c9f1d
commit 146f91f011
19 changed files with 526 additions and 42 deletions

View File

@@ -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>

View File

@@ -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)

View File

@@ -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"/>
</@>

View File

@@ -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();

View File

@@ -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" />';
};
};
}
}());

View File

@@ -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>

View File

@@ -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>