diff --git a/pom.xml b/pom.xml index bf68978..f04d203 100644 --- a/pom.xml +++ b/pom.xml @@ -204,6 +204,21 @@ test + + de.acosix.alfresco.utility + de.acosix.alfresco.utility.repo + ${acosix.utility.version} + provided + + + + de.acosix.alfresco.utility + de.acosix.alfresco.utility.repo + ${acosix.utility.version} + installable + test + + org.orderofthebee.support-tools support-tools-repo diff --git a/repository/src/main/config/module-context.xml b/repository/src/main/config/module-context.xml index f5d03ed..7155952 100644 --- a/repository/src/main/config/module-context.xml +++ b/repository/src/main/config/module-context.xml @@ -75,6 +75,11 @@ + + + + + diff --git a/repository/src/main/globalConfig/webscripts/extensions/config/acosix-keycloak-extension.xml b/repository/src/main/globalConfig/webscripts/extensions/config/acosix-keycloak-extension.xml new file mode 100644 index 0000000..1415b78 --- /dev/null +++ b/repository/src/main/globalConfig/webscripts/extensions/config/acosix-keycloak-extension.xml @@ -0,0 +1,18 @@ + + + + + ${moduleId} - Web Script Extension + ${project.name} + ${noSnapshotVersion} + true + + + + org.alfresco + de.acosix.keycloak.customisations + + + + + \ No newline at end of file diff --git a/repository/src/main/java/de/acosix/alfresco/keycloak/repo/authentication/KeycloakAuthenticationComponent.java b/repository/src/main/java/de/acosix/alfresco/keycloak/repo/authentication/KeycloakAuthenticationComponent.java index 04f1616..914bb66 100644 --- a/repository/src/main/java/de/acosix/alfresco/keycloak/repo/authentication/KeycloakAuthenticationComponent.java +++ b/repository/src/main/java/de/acosix/alfresco/keycloak/repo/authentication/KeycloakAuthenticationComponent.java @@ -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()) { diff --git a/repository/src/main/java/de/acosix/alfresco/keycloak/repo/roles/NoOpRoleServiceImpl.java b/repository/src/main/java/de/acosix/alfresco/keycloak/repo/roles/NoOpRoleServiceImpl.java index dca056c..e355dfe 100644 --- a/repository/src/main/java/de/acosix/alfresco/keycloak/repo/roles/NoOpRoleServiceImpl.java +++ b/repository/src/main/java/de/acosix/alfresco/keycloak/repo/roles/NoOpRoleServiceImpl.java @@ -27,6 +27,16 @@ import java.util.List; public class NoOpRoleServiceImpl implements RoleService { + /** + * + * {@inheritDoc} + */ + @Override + public List listRoles() + { + return Collections.emptyList(); + } + /** * * {@inheritDoc} @@ -37,6 +47,16 @@ public class NoOpRoleServiceImpl implements RoleService return Collections.emptyList(); } + /** + * + * {@inheritDoc} + */ + @Override + public List listRoles(final boolean realmOnly) + { + return Collections.emptyList(); + } + /** * * {@inheritDoc} @@ -47,6 +67,16 @@ public class NoOpRoleServiceImpl implements RoleService return Collections.emptyList(); } + /** + * + * {@inheritDoc} + */ + @Override + public List listRoles(final String resourceName) + { + return Collections.emptyList(); + } + /** * * {@inheritDoc} diff --git a/repository/src/main/java/de/acosix/alfresco/keycloak/repo/roles/RoleService.java b/repository/src/main/java/de/acosix/alfresco/keycloak/repo/roles/RoleService.java index cd43b7b..a7547b1 100644 --- a/repository/src/main/java/de/acosix/alfresco/keycloak/repo/roles/RoleService.java +++ b/repository/src/main/java/de/acosix/alfresco/keycloak/repo/roles/RoleService.java @@ -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 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 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 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 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 listRoles(String resourceName); + /** * Finds matching roles in a specific resource scope (as far as possible based on configuration). * diff --git a/repository/src/main/java/de/acosix/alfresco/keycloak/repo/roles/RoleServiceImpl.java b/repository/src/main/java/de/acosix/alfresco/keycloak/repo/roles/RoleServiceImpl.java index 51b8c99..26f081f 100644 --- a/repository/src/main/java/de/acosix/alfresco/keycloak/repo/roles/RoleServiceImpl.java +++ b/repository/src/main/java/de/acosix/alfresco/keycloak/repo/roles/RoleServiceImpl.java @@ -215,6 +215,16 @@ public class RoleServiceImpl implements InitializingBean, RoleService this.hiddenMappedRoles = hiddenMappedRoles; } + /** + * + * {@inheritDoc} + */ + @Override + public List listRoles() + { + return this.doFindRoles(null, false); + } + /** * * {@inheritDoc} @@ -222,7 +232,18 @@ public class RoleServiceImpl implements InitializingBean, RoleService @Override public List findRoles(final String shortNameFilter) { - return this.findRoles(shortNameFilter, false); + ParameterCheck.mandatoryString("shortNameFilter", shortNameFilter); + return this.doFindRoles(shortNameFilter, false); + } + + /** + * + * {@inheritDoc} + */ + @Override + public List listRoles(final boolean realmOnly) + { + return this.doFindRoles(null, realmOnly); } /** @@ -231,6 +252,35 @@ public class RoleServiceImpl implements InitializingBean, RoleService */ @Override public List findRoles(final String shortNameFilter, final boolean realmOnly) + { + ParameterCheck.mandatoryString("shortNameFilter", shortNameFilter); + return this.doFindRoles(shortNameFilter, realmOnly); + } + + /** + * + * {@inheritDoc} + */ + @Override + public List listRoles(final String resourceName) + { + ParameterCheck.mandatory("resourceName", resourceName); + return this.doFindRoles(resourceName, null); + } + + /** + * + * {@inheritDoc} + */ + @Override + public List findRoles(final String resourceName, final String shortNameFilter) + { + ParameterCheck.mandatory("resourceName", resourceName); + ParameterCheck.mandatoryString("shortNameFilter", shortNameFilter); + return this.doFindRoles(resourceName, shortNameFilter); + } + + protected List doFindRoles(final String shortNameFilter, final boolean realmOnly) { final List roles; @@ -254,7 +304,7 @@ public class RoleServiceImpl implements InitializingBean, RoleService if (!realmOnly && this.processResourceRoles) { this.resourceRoleNameMapper.keySet().stream().forEach(resourceName -> { - final List resourceRoles = this.findRoles(resourceName, shortNameFilter); + final List resourceRoles = this.doFindRoles(resourceName, shortNameFilter); roles.addAll(resourceRoles); }); } @@ -275,15 +325,8 @@ public class RoleServiceImpl implements InitializingBean, RoleService return roles; } - /** - * - * {@inheritDoc} - */ - @Override - public List findRoles(final String resourceName, final String shortNameFilter) + protected List doFindRoles(final String resourceName, final String shortNameFilter) { - ParameterCheck.mandatory("resourceName", resourceName); - List roles; if (this.enabled && !this.processResourceRoles) diff --git a/repository/src/main/java/de/acosix/alfresco/keycloak/repo/roles/ScriptRoleService.java b/repository/src/main/java/de/acosix/alfresco/keycloak/repo/roles/ScriptRoleService.java new file mode 100644 index 0000000..e9d0bc6 --- /dev/null +++ b/repository/src/main/java/de/acosix/alfresco/keycloak/repo/roles/ScriptRoleService.java @@ -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 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 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 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 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 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 roles = this.roleService.findRoles(resourceName, shortNameFilter); + final Scriptable roleArray = this.makeRoleArray(roles); + return roleArray; + } + + protected Scriptable makeRoleArray(final List roles) + { + final Scriptable sitesArray = Context.getCurrentContext().newArray(this.getScope(), roles.toArray(new Object[0])); + return sitesArray; + } +} diff --git a/repository/src/main/java/de/acosix/alfresco/keycloak/repo/web/scripts/RolesGet.java b/repository/src/main/java/de/acosix/alfresco/keycloak/repo/web/scripts/RolesGet.java index e449407..4b9819f 100644 --- a/repository/src/main/java/de/acosix/alfresco/keycloak/repo/web/scripts/RolesGet.java +++ b/repository/src/main/java/de/acosix/alfresco/keycloak/repo/web/scripts/RolesGet.java @@ -103,7 +103,9 @@ public class RolesGet extends DeclarativeWebScript implements InitializingBean maxItems = Integer.parseInt(maxItemsParam); } - final List roles = this.roleService.findRoles(shortNameFilterParam); + final List roles = shortNameFilterParam != null && !shortNameFilterParam.trim().isEmpty() + ? this.roleService.findRoles(shortNameFilterParam) + : this.roleService.listRoles(); if (roles.isEmpty()) { diff --git a/repository/src/main/webscripts/de/acosix/keycloak/customisations/slingshot/documentlibrary/permissions.get.js b/repository/src/main/webscripts/de/acosix/keycloak/customisations/slingshot/documentlibrary/permissions.get.js new file mode 100644 index 0000000..cbae554 --- /dev/null +++ b/repository/src/main/webscripts/de/acosix/keycloak/customisations/slingshot/documentlibrary/permissions.get.js @@ -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(); diff --git a/repository/src/main/webscripts/de/acosix/keycloak/customisations/slingshot/documentlibrary/permissions.post.json.js b/repository/src/main/webscripts/de/acosix/keycloak/customisations/slingshot/documentlibrary/permissions.post.json.js new file mode 100644 index 0000000..6250f21 --- /dev/null +++ b/repository/src/main/webscripts/de/acosix/keycloak/customisations/slingshot/documentlibrary/permissions.post.json.js @@ -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(); diff --git a/share/pom.xml b/share/pom.xml index 150b686..c1ba75c 100644 --- a/share/pom.xml +++ b/share/pom.xml @@ -88,6 +88,12 @@ installable + + de.acosix.alfresco.utility + de.acosix.alfresco.utility.repo + installable + + de.acosix.alfresco.utility de.acosix.alfresco.utility.core.share diff --git a/share/src/main/java/de/acosix/alfresco/keycloak/share/web/KeycloakAuthenticationFilter.java b/share/src/main/java/de/acosix/alfresco/keycloak/share/web/KeycloakAuthenticationFilter.java index 909df8c..ff0e2b3 100644 --- a/share/src/main/java/de/acosix/alfresco/keycloak/share/web/KeycloakAuthenticationFilter.java +++ b/share/src/main/java/de/acosix/alfresco/keycloak/share/web/KeycloakAuthenticationFilter.java @@ -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) diff --git a/share/src/main/site-webscripts/de/acosix/keycloak/customisations/components/manage-permissions/manage-permissions.get.html.ftl b/share/src/main/site-webscripts/de/acosix/keycloak/customisations/components/manage-permissions/manage-permissions.get.html.ftl new file mode 100644 index 0000000..77bf5f9 --- /dev/null +++ b/share/src/main/site-webscripts/de/acosix/keycloak/customisations/components/manage-permissions/manage-permissions.get.html.ftl @@ -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"/> + \ No newline at end of file diff --git a/share/src/main/site-webscripts/de/acosix/keycloak/customisations/components/people-finder/authority-query.get.js b/share/src/main/site-webscripts/de/acosix/keycloak/customisations/components/people-finder/authority-query.get.js new file mode 100644 index 0000000..9822cfe --- /dev/null +++ b/share/src/main/site-webscripts/de/acosix/keycloak/customisations/components/people-finder/authority-query.get.js @@ -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(); diff --git a/share/src/main/webapp/.gitkeep b/share/src/main/webapp/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/share/src/main/webapp/acosix/keycloak/components/manage-permissions/manage-permissions.js b/share/src/main/webapp/acosix/keycloak/components/manage-permissions/manage-permissions.js new file mode 100644 index 0000000..b7bcc34 --- /dev/null +++ b/share/src/main/webapp/acosix/keycloak/components/manage-permissions/manage-permissions.js @@ -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 = 'icon'; + }; + }; + } +}()); diff --git a/share/src/test/docker/alfresco/web-extension/share-config-custom.xml b/share/src/test/docker/alfresco/web-extension/share-config-custom.xml index 4570606..68c43a2 100644 --- a/share/src/test/docker/alfresco/web-extension/share-config-custom.xml +++ b/share/src/test/docker/alfresco/web-extension/share-config-custom.xml @@ -21,18 +21,10 @@ alfrescoCookie Alfresco Connector - Connects to an Alfresco instance using cookie-based authentication + Connects to an Alfresco instance using cookie-based authentication and awareness of OIDC bearer tokens de.acosix.alfresco.keycloak.share.remote.BearerTokenAwareSlingshotAlfrescoConnector - - alfrescoHeader - Alfresco Connector - Connects to an Alfresco instance using header and cookie-based authentication - de.acosix.alfresco.keycloak.share.remote.BearerTokenAwareSlingshotAlfrescoConnector - SsoUserHeader - - alfresco Alfresco - user access @@ -47,7 +39,7 @@ alfresco-feed Alfresco Feed Alfresco Feed - supports basic HTTP authentication via the EndPointProxyServlet - alfrescoHeader + alfrescoCookie http://repository:8080/alfresco/wcs true user @@ -58,10 +50,8 @@ alfresco-api alfresco Alfresco Public API - user access - Access to Alfresco Repository Public API that require user authentication. - This makes use of the authentication that is provided by parent 'alfresco' endpoint. - - alfrescoHeader + Access to Alfresco Repository Public API that require user authentication. This makes use of the authentication that is provided by parent 'alfresco' endpoint. + alfrescoCookie http://repository:8080/alfresco/api user true diff --git a/share/src/test/docker/repository-it.xml b/share/src/test/docker/repository-it.xml index 1d203a3..62e2e4c 100644 --- a/share/src/test/docker/repository-it.xml +++ b/share/src/test/docker/repository-it.xml @@ -70,6 +70,8 @@ de.acosix.alfresco.utility:de.acosix.alfresco.utility.core.repo.quartz2:* ${project.groupId}:de.acosix.alfresco.keycloak.repo.deps:* de.acosix.alfresco.utility:de.acosix.alfresco.utility.core.repo:jar:installable:* + + de.acosix.alfresco.utility:de.acosix.alfresco.utility.repo:jar:installable:* ${project.groupId}:de.acosix.alfresco.keycloak.repo:jar:installable:* test