diff --git a/README.md b/README.md index 9ec3ac3..34d4c99 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ # About -This addon aims to provide a Keycloak-related extensions / customisations to the out-of-the-box Alfresco authentication and authorization functionalities for the Alfresco Repository and Share tiers. +This addon aims to provide a Keycloak-related extensions / customisations to the out-of-the-box Alfresco authentication and authorisation functionalities for the Alfresco Repository and Share tiers. ## Compatbility @@ -10,14 +10,43 @@ This module is built to be compatible with Alfresco 6.0 and above. It may be use ## Features -At this time, only the Share sub-module of this project provides a feature enhancement. +The Repository sub-module provides a custom authentication subsystem dealing with Keycloak (separate to Alfresco's default `identity-service`) and customisations which support: + +- user + password login via `AuthenticationService.authenticate` / `AuthenticationComponent.authenticate` +- `Bearer` token authentication using a client-obtained access token +- redirection to Keycloak login UI and OIDC authentication flow (SSO), if client not already authenticated in session, no pre-emptive details provided in request and API requires authentication +- mapping of person properties on authentication from user access / identity token +- mapping of authorities from user access token (for purpose of permission / role checks) +- handling Keycloak back-channel requests + - back-channel logout requests from Keycloak (i.e. SSO including "single sign-out") + - bulk-invalidation of access tokens issued before a certain point in time + - availability test / basic validation + - JWKS (public key) update +- active user / group synchronisation against Keycloak's directory (which may include users / groups synchronised from multiple federated directories) +- Java service, JavaScript service and web script to expose roles mapped from Keycloak for retrieval (e.g. to be used in permission management) The Share sub-module provides a Keycloak-based filter and customisations that support: -- enhancement of the login dialog to allow users to perform an external authentication via a Keycloak server as an alternative to the password-based login -- enforcement of an external authentication via a Keycloak server for all users via a SSO filter enhancement -- back-channel logout and other operations actively initiated by a Keycloak server, e.g. invalidating all authentication tokens issued after a specific point in time -- active logout which also logs out the user centrally on the Keycloak server +- redirection to Keycloak login UI and OIDC authentication flow (SSO), if client not already authenticated in session, no pre-emptive details provided in request and SSO authentication required/enforced via configuration +- enhancement of the login dialog to allow users to perform an alternative, external authentication via Keycloak +- handling Keycloak back-channel requests + - back-channel logout requests from Keycloak (i.e. SSO including "single sign-out") + - bulk-invalidation of access tokens issued before a certain point in time + - availability test / basic validation + - JWKS (public key) update +- Share logout action triggering a Keycloak logout (logging user out of other applications handled by Keycloak if those support Keycloak back-channel logout requests) +- [RFC 8693 OAuth 2.0 Token Exchange](https://tools.ietf.org/html/rfc8693) (a [preview functionality in Keycloak](https://www.keycloak.org/docs/latest/securing_apps/#_token-exchange) to properly delegate the Share-tier authentication to the Repository, if signed on via Keycloak SSO + +# Configuration + +The configuration of both the Keycloak server and this module offer a large number of properties to adjust, and various modes of operation. Therefore, the following sub-documents have been created to provide details and guides: + +- [Getting Started (Simple Configuration)](./docs/Simple-Configuration.md) +- Repository Configuration Reference + - [Keycloak Subsystem](./docs/Reference-Repository-Subsystem.md) + - [Keycloak Adapter](./docs/Reference-Repository-Adapter.md) + - [Extension API](./docs/Reference-Repository-Extension.md) +- [Share Configuration Reference](./docs/Reference-Repository.md) # Build @@ -26,7 +55,7 @@ This project uses a Maven build using templates from the [Acosix Alfresco Maven] ## Maven toolchains By inheritance from the Acosix Alfresco Maven framework, this project uses the [Maven Toolchains plugin](http://maven.apache.org/plugins/maven-toolchains-plugin/) to allow potential cross-compilation against different Java versions. This plugin is used to avoid potentially inconsistent compiler and library versions compared to when only the source/target compiler options of the Maven compiler plugin are set, which (as an example) has caused issues with some Alfresco releases in the past where Alfresco compiled for Java 7 using the Java 8 libraries. -In order to build the project it is necessary to provide a basic toolchain configuration via the user specific Maven configuration home (usually ~/.m2/). That file (toolchains.xml) only needs to list the path to a compatible JDK for the Java version required by this project. The following is a sample file defining a Java 7 and 8 development kit. +In order to build the project it is necessary to provide a basic toolchain configuration via the user specific Maven configuration home (usually ~/.m2/). That file (toolchains.xml) only needs to list the path to a compatible JDK for the Java version required by this project. The following is a sample file defining a Java 8 and 11 development kit. ```xml @@ -38,17 +67,18 @@ In order to build the project it is necessary to provide a basic toolchain confi oracle - C:\Program Files\Java\jdk1.8.0_112 + C:\Program Files\Java\jdk1.8.0_231 jdk - 1.7 + 1.11 oracle + openjdk11 - C:\Program Files\Java\jdk1.7.0_80 + C:\Program Files\Java\jdk-11.0.2 @@ -64,7 +94,7 @@ In a default build using ```mvn clean install```, this project will build the ex mvn clean install -Ddocker.tests.enabled=true ``` -This project currently does not contain any integration tests, as a proper setup which includes Keycloak has not yet been achieved. +This project currently does not yet contain any specific integration tests but does use integration tests to verify Alfresco correctly starts up with the addon installed. ## Dependencies @@ -77,7 +107,9 @@ This module depends on the following projects / libraries: - keycloak-authz-client - Acosix Alfresco Utility (Apache License, Version 2.0) - core extension -The Share AMP of this project includes all the Keycloak library JARs that the extension depends on. The Acosix Alfresco Utility project provides the core extension for Alfresco Content Services as a separate artifact from the full module, which needs to be installed in Alfresco Content Services before the AMP of this project can be installed. +All Keycloak dependencies are aggregated into single uber-JAR / shaded dependency library for the Repository and Share respectively. This aggregation is handled via the sub-modules `repository-dependencies` and `share-dependencies`. This has been done to isolate this addon from whatever version of Keycloak libraries Alfresco pre-packages to support its `identity-service` authentication subsystem. These aggregated libraries are included in the respective AMPs of this project and only need to be installed separately if the simple JAR deployment method is used to install the modules of this addon. + +The Acosix Alfresco Utility project provides the core extension for Alfresco Content Services as a separate artifact from the full module, which needs to be installed in Alfresco Content Services before the AMP of this project can be installed. When the installable JAR produced by the build of this project is used for installation, the developer / user is responsible to either manually install all the required components / libraries provided by the listed projects, or use a build system to collect all relevant direct / transitive dependencies. **Note**: The Acosix Alfresco Utility project is also built using templates from the Acosix Alfresco Maven project, and as such produces similar artifacts. Automatic resolution and collection of (transitive) dependencies using Maven / Gradle will resolve the Java *classes* JAR as a dependency, and **not** the installable (Simple Alfresco Module) variant. It is recommended to exclude Acosix Alfresco Utility from transitive resolution and instead include it directly / explicitly. diff --git a/docs/Simple-Configuration.md b/docs/Simple-Configuration.md new file mode 100644 index 0000000..4b519bd --- /dev/null +++ b/docs/Simple-Configuration.md @@ -0,0 +1,190 @@ +# Getting Started (Simple Configuration) + +This section provides the most basic configuration required to use this addon in combination with a Keycloak server (Keycloak version 11.0.2 used as a reference). + +## Keycloak + +This section will only cover the Keycloak configuration specific to use of this addon. It is recommended that the Keycloak documentation and any available best practices in the wider community are checked in order to create a fully production-ready Keycloak deployment, including e.g. database integration, SSL termination, reverse proxying... + +### Enable Token Exchange Feature + +This configuration change must be performed if you want Share to use the RFC 8693 OAuth 2.0 Token Exchange functionality to delegate the user authentication to the Repository backend. This feature is not enabled in Keycloak by default. If this feature is not enabled in Keycloak, the configuration of Share needs to be adapted to also disable its RFC 8693 OAuth 2.0 Token Exchange support. + +In order to enable the feature in Keycloak, the `profile.properties` file needs to be created / modified in the server's configuration folder structure. In the default Keycloak Docker image, the path to this file should be `/opt/jboss/keycloak/standalone/configuration/profile.properties`. The file must contain an entry in the form of `feature.token_exchange=enabled` to enable the RFC 8693 functionality. + +### Realm / Client Configuration + +Keycloak provides the ability to [import a realm and client configuration from JSON configuration files](https://www.keycloak.org/docs/latest/server_admin/#_export_import), though most non-specialist users / administrators will likely configure the realm and any clients via the Keycloak administration UI. Since there are countless configuration options that can be defined / changed on every level, this section will not provide a literal configuration example, but rather highlight which configuration properties are most relevant for the use of this addon. Note that UI labels may change between Keycloak versions. + +Two clients must be created for the Alfresco Repository and Share. The following configuration values should be set: + +- "Access Type" `confidential` +- "Standard Flow Enabled" `On` +- "Direct Access Grants Enabled" `On` (if simple user + password login without SSO should be supported) +- "Service Accounts Enabled" `On` (on the client for Alfresco Repository, if active user / group synchronisation *or* the service/web script to expose roles for use e.g. in permission mangement should be supported) +- "Root URL" to the base URL for each application, e.g. `https://acme.com/alfresco` +- "Valid Redirect URLs" `/*` +- "Base URL" `/` +- "Admin URL" to the base URL for each application with the following added sub-path (if Keycloak back-channel requests should be supported) + - `/wcs/keycloak` on the client for Alfresco Repository + - `/page/keycloak` on the client Alfresco Share +- "Web Origins" `/` +- "Credentials" => "Client Authenticator" `Client Id and Secret` (validation based on shared secret) + - the generated secret for each client needs to be stored for use in Alfresco configuration files +- "Client Scopes" => "Setup" => "Default Client Scopes" + - `email` and `profile` (on the client for Alfresco Repository, if mapping of person from access / identity tokens should be supported) + - `roles` (on the client for Alfresco Repository, if mapping of authorities from Keycloak roles should be supported) +- "Mappers" => "Add Builtin" `groups` (on the client for Alfresco Repository, if mapping of authorities from Keycloak gropus should be supported) +- "Service Account Roles" (on the client for Alfresco Repository, if active user / group synchronisation *or* the service/web script to expose roles for use e.g. in permission mangement should be supported) + - Assign client roles `query-groups`, `query-users`, `view-users` and `view-clients` on the client `realm-management` + - Assign client roles `view-profile` and `manage-account` on the client `account` + +If the RFC 8693 OAuth 2.0 Token Exchange functionality is to be used for delegation of user authentication from Share to the Repository, an authorisation policy needs to be defined on the pre-existing client `realm-management`. The necessary elements can all be configured in the "Authorization" tab in the configuration of that client. The following elements must be created (if not pre-existing) and/or associated with one another. + +- "Authorization Scopes" `token-exchange` +- "Resources" `client.resource.<idOfRepositoryClient>` + - "Type" `Client` + - "Scopes" `view`, `map-roles-client-scope`, `configure`, `map-roles`, `manage`, `token-exchange`, `map-roles-composite` (`token-exchange` is required for the feature, the others are typically created by default when an optional Keycloak feature for simplified authorisation management is used - if these do not exist, they can be manually created in "Authorization Scopes") +- "Permissions" + - `view.permission.client.<idOfRepositoryClient>` + - `map-roles-client-scope.permission.client.<idOfRepositoryClient>` + - `configure.permission.client.<idOfRepositoryClient>` + - `map-roles.permission.client.<idOfRepositoryClient>` + - `manage.permission.client.<idOfRepositoryClient>` + - `token-exchange.permission.client.<idOfRepositoryClient>` + - `map-roles-composite.permission.client.<idOfRepositoryClient>` +- "Policies" `<idOfRepositoryClient>-token-exchange` + - "Logic" `Positive` + - "Clients" `<idOfShareClient>` + +### Roles / Groups + +Unless disabled, the Repository module of this addon can synchronise users / groups, and map groups or roles from the access / identity token as authorities of the user. In the default configuration of the module, all users and groups are synchronised, all roles defined as realm-level roles will be mapped as `ROLE_KEYCLOAK_<realm>_<role>`, and all client roles of the Alfresco Repository client are mapped as `ROLE_KEYCLOAK_<realm>_<idOfRepositoryClient>_<role>. The following special cases are handled by default with regards to Alfresco Repository client roles (all of these roles do not exist by default and must be created if they are to be used): + +- `admin` mapped as `ROLE_ADMINISTRATOR` +- `guest` mapped as `ROLE_GUEST` +- `model-admin` mapped as `GROUP_MODEL_ADMINISTRATORS` +- `search-admin` mapped as `GROUP_SEARCH_ADMINISTRATORS` +- `site-admin` mapped as `GROUP_SITE_ADMINISTRATORS` + +Additionally, these client roles need to be assigned to a user or group, directly or indirectly via another (realm) role, in order to take effect when authenticating to Alfresco via Keycloak. + +The scope of users / gropus to be synchronised can be restricted in the default configuration via optional group containment conditions. In order to use these, one or more groups needs to be created in the realm configuration which will either directly or indirectly contain all the users / groups to be synchronised. The paths or IDs of these groups needs to be stored for use in Alfresco configuration. + +## Alfresco Repository + +The Keycloak authentication subsystem is enabled by putting a single instance of it in the authentication chain property, e.g. by specifying + +``` +authentication.chain=alfrescoNtlm1:alfrescoNtlm,keycloak1:keycloak +``` + +in the `alfresco-global.properties` file, or via other supported means of configuration (e.g. -D flags in `JAVA_OPTS` in Docker-based deployments). Since it rarely (if ever) makes sense to have more than one instance of the Keycloak authentication subsystem in the chain, all configuration properties specific for this type of subsystem can also be set in the `alfresco-global.properties` file, though it is generally recommended (Acosix recommendation, not necessarily documented as such by Alfresco) to use the proper subsystem configuration paths. For the above authentication chain, custom configuration properties files can be place in the configuration path `alfresco/extension/subsystems/Authentication/keycloak/keycloak1/*.properties`. + +The following core configuration properties can be set (more extensive list in the [reference](./Reference-Repository-Subsystem.md)), with only the `keycloak.adapter.auth-server-url`, `...realm`, `...resource`, and `...credentials.secret` being absolutely required for a minimal configuration (Note: whenever `...` is used as a prefix, it refers to the prefix of the previous full-length property): + +| Property | Default Value | Description | +| --- | ---: | --- | +| `keycloak.authentication.enabled` | `true` | Flag enabling authentication support via this subsystem instance | +| `...sso.enabled` | `true` | Flag enabling single sign-on (SSO) authentication support via this subsystem instance | +| `...handlePublicApi` | `false` | Flag enabling inclusion of the Public ReST API in SSO handling - disabled by default as all other means of SSO handling in Alfresco typically do not (fully) cover the Public ReST API | +| `...allowTicketLogons` | `true` | Flag enabling support of Alfresco authentication tickets in the SSO handling logic | +| `...allowHttpBasicLogon` | `true` | Flag enabling support of HTTP Basic authentication in the SSO handling logic, mapping to the simple user + password authentication via this subsystems `AuthenticationComponent` | +| `...allowUserNamePasswordLogin` | `true` | Flag enabling support of user + password authentication via this subsystems `AuthenticationComponent` | +| `...mapAuthorities` | `true` | Flag enabling mapping of authorities from access / identity tokens (supported for both SSO and user + password authentication) | +| `...mapPersonPropertiesOnLogin` | `true` | Flag enabling mapping of person attributes from access / identity tokens (supported for both SSO and user + password authentication) | +| `keycloak.synchronization.enabled` | `true` | Flag enabling synchronisation support via this subsystem instance | +| `...userFilter.containedInGroup.property.groupPaths` | | Comma-separated list of group paths (e.g. `/Group A/Group B,/Group A/Group C`) to use in filtering which users are synchronised to Alfresco (by default - configured separately - any match qualifies, and transitive containment is considered) | +| `...userFilter.containedInGroup.property.groupIds` | | Comma-separated list of group IDs to use in filtering which users are synchronised to Alfresco (by default - configured separately - any match qualifies, and transitive containment is considered) | +| `...groupFilter.containedInGroup.property.groupPaths` | | Comma-separated list of group paths (e.g. `/Group A/Group B,/Group A/Group C`) to use in filtering which groups are synchronised to Alfresco (by default - configured separately - any match qualifies, and transitive containment is considered) | +| `...groupFilter.containedInGroup.property.groupIds` | | Comma-separated list of group IDs to use in filtering which groups are synchronised to Alfresco (by default - configured separately - any match qualifies, and transitive containment is considered) | +| `keycloak.adapter.auth-server-url` | `http://localhost:8180/auth` | Publically resolvable base URL to the Keycloak server to be used in redirect URLs and remote calls | +| `keycloak.authentication.directAuthHost` | | Alternative base URL for the Keycloak server (excluding path) to be used for calls from Alfresco to Keycloak - useful e.g. in scenarios where the regular `auth-server-url` can not be resolved by the Alfresco Repository host or round-trips via a public gateway / proxy should be avoided | +| `keycloak.adapter.realm` | `alfresco` | Technical name of the Keycloak realm | +| `keycloak.adapter.resource` | `alfresco` | Technical name of the client set up for the Alfresco Repository in the realm | +| `keycloak.adapter.keycloak.adapter.credentials.secret` | | Shared secret for validation of authorisation codes / access tokens | +| `keycloak.adapter.verify-token-audience` | `true` | Flag enabling validation of the audience specified in an access token - must be disabled if Share or any other application which authenticates users via Keycloak is not delegating user authentication using RFC 8693 OAuth 2.0 Token Exchange | + +## Alfresco Share + +The Keycloak support in Alfresco Share is configured via the standard `share-config-custom.xml` file. Just by isntalling the module into Share and its packaged module default file, support for Keycloak authentication is pre-enabled. By configuring an additional `Keycloak` configuration section in the `share-config-custom.xml`, the default configuration can be complemented / modified. Its configuration is split into two sub-elements - the `keycloak-auth-config`, which handles configuration specific to features implemented as part of this addon, and `keycloak-adapter-config`, which handles configuration relating to the Keycloak adapter library used to integrate with the Keycloak server. The latter section shares / supports identical configuration properties as the `keycloak.adapter.???` properties in the Repository subsystem. +The following showcases an example configuration block: + +```xml + + + true + true + false + true + + + + http://localhost:8180/auth + alfresco + alfresco-share + + secret + ... + + + +``` + +| `keycloak-auth-config` key | Default Value | Description | +| --- | ---: | --- | +| `enhance-login-form` | `true` | Flag enabling the inclusion of an additional "Log in via SSO" button in the Share login form | +| `enable-sso-filter` | `true` | Flag enabling single sign-on (SSO) authentication support | +| `force-keycloak-sso` | `false` | Flag enabling forced SSO, meaning the user is automatically redirected to Keycloak for authentication instead of being shown the Share login form | +| `perform-token-exchange` | `true` | Flag enabling the use of RFC 8693 OAuth 2.0 Token Exchange for delegating user authentication to the Alfresco Repository | + +**Note**: When the `enable-sso-filter` is set to `true`, the Keycloak authentication subsystem must be enabled on the Alfresco Repository for correct operation. + +Similar to Alfresco's out-of-the-box SSO mechanisms for Share, the use of Keycloak for SSO requires that the Remote endpoint configuration be changed to use the `/alfresco/wcs` endpoint instead of the default `/alfresco/s` endpoint. Additionally, a special connector must be used to properly use the access token to authenticate against the Alfresco Repository. This can all be done by adding a section like the following: + +```xml + + + + alfrescoCookie + Alfresco Connector + Connects to an Alfresco instance using cookie-based authentication and awareness of Keycloak access tokens + de.acosix.alfresco.keycloak.share.remote.AccessTokenAwareSlingshotAlfrescoConnector + + + + alfresco + Alfresco - user access + Access to Alfresco Repository WebScripts that require user authentication + alfrescoCookie + http://localhost:8080/alfresco/wcs + user + true + + + + alfresco-feed + Alfresco Feed + Alfresco Feed - supports basic HTTP authentication via the EndPointProxyServlet + alfrescoCookie + http://localhost:8080/alfresco/wcs + true + user + true + + + + 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. + + alfrescoCookie + http://localhost:8080/alfresco/api + user + true + + + +``` \ 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 74e133a..fd04f85 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 @@ -376,9 +376,10 @@ public class KeycloakAuthenticationComponent extends AbstractAuthenticationCompo if (currentAuthentication instanceof UsernamePasswordAuthenticationToken) { GrantedAuthority[] grantedAuthorities = currentAuthentication.getAuthorities(); - final List grantedAuthoritiesL = new ArrayList<>(Arrays.asList(grantedAuthorities)); - mappedAuthorities.stream().map(GrantedAuthorityImpl::new).forEach(grantedAuthoritiesL::add); + final List grantedAuthoritiesL = mappedAuthorities.stream().map(GrantedAuthorityImpl::new) + .collect(Collectors.toList()); + grantedAuthoritiesL.addAll(Arrays.asList(grantedAuthorities)); grantedAuthorities = grantedAuthoritiesL.toArray(new GrantedAuthority[0]); ((UsernamePasswordAuthenticationToken) currentAuthentication).setAuthorities(grantedAuthorities); diff --git a/repository/src/test/docker/test-realm.json b/repository/src/test/docker/test-realm.json index 4abc0ee..3e5eaa6 100644 --- a/repository/src/test/docker/test-realm.json +++ b/repository/src/test/docker/test-realm.json @@ -80,6 +80,8 @@ "manage-account" ], "realm-management": [ + "query-groups", + "query-users", "view-users", "view-clients" ]