mirror of
https://github.com/bmlong137/alfresco-keycloak.git
synced 2025-05-12 21:24:43 +00:00
Initial version
This commit is contained in:
commit
2db4aaddbe
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
* text=auto
|
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
.settings/
|
||||||
|
bin/
|
||||||
|
.project
|
||||||
|
.classpath
|
||||||
|
|
||||||
|
target/
|
7
.travis.yml
Normal file
7
.travis.yml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
sudo: false
|
||||||
|
language: java
|
||||||
|
jdk:
|
||||||
|
- openjdk8
|
||||||
|
cache:
|
||||||
|
directories:
|
||||||
|
- $HOME/.m2
|
201
LICENSE
Normal file
201
LICENSE
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright 2019 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.
|
99
README.md
Normal file
99
README.md
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
[](https://travis-ci.org/Acosix/alfresco-keycloak)
|
||||||
|
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
## Compatbility
|
||||||
|
|
||||||
|
This module is built to be compatible with Alfresco 6.0 and above. It may be used on either Community or Enterprise Edition.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
At this time, only the Share sub-module of this project provides a feature enhancement.
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
# Build
|
||||||
|
|
||||||
|
This project uses a Maven build using templates from the [Acosix Alfresco Maven](https://github.com/Acosix/alfresco-maven) project and produces module AMPs, regular Java *classes* JARs, JavaDoc and source attachment JARs, as well as installable (Simple Alfresco Module) JAR artifacts for the Alfresco Content Services and Share extensions. If the installable JAR artifacts are used for installing this module, developers / users are advised to consult the 'Dependencies' section of this README.
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
|
<toolchains xmlns="http://maven.apache.org/TOOLCHAINS/1.1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/TOOLCHAINS/1.1.0 http://maven.apache.org/xsd/toolchains-1.1.0.xsd">
|
||||||
|
<toolchain>
|
||||||
|
<type>jdk</type>
|
||||||
|
<provides>
|
||||||
|
<version>1.8</version>
|
||||||
|
<vendor>oracle</vendor>
|
||||||
|
</provides>
|
||||||
|
<configuration>
|
||||||
|
<jdkHome>C:\Program Files\Java\jdk1.8.0_112</jdkHome>
|
||||||
|
</configuration>
|
||||||
|
</toolchain>
|
||||||
|
<toolchain>
|
||||||
|
<type>jdk</type>
|
||||||
|
<provides>
|
||||||
|
<version>1.7</version>
|
||||||
|
<vendor>oracle</vendor>
|
||||||
|
</provides>
|
||||||
|
<configuration>
|
||||||
|
<jdkHome>C:\Program Files\Java\jdk1.7.0_80</jdkHome>
|
||||||
|
</configuration>
|
||||||
|
</toolchain>
|
||||||
|
</toolchains>
|
||||||
|
```
|
||||||
|
|
||||||
|
The master branch requires Java 8.
|
||||||
|
|
||||||
|
## Docker-based integration tests
|
||||||
|
|
||||||
|
In a default build using ```mvn clean install```, this project will build the extension for Alfresco Content Services, executing regular unit-tests without running integration tests. The integration tests of this project are based on Docker and require a Docker engine to run the necessary components (PostgreSQL database as well as Alfresco Content Services). Since a Docker engine may not be available in all environments of interested community members / collaborators, the integration tests have been made optional. A full build, including integration tests, can be run by executing
|
||||||
|
|
||||||
|
```
|
||||||
|
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.
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
This module depends on the following projects / libraries:
|
||||||
|
|
||||||
|
- various [Keycloak]https://github.com/keycloak/keycloak) adapter and client libraries (Apache License, Version 2.0)
|
||||||
|
- keycloak-adapter-core
|
||||||
|
- keycloak-servlet-adapter-spi
|
||||||
|
- keycloak-servlet-filter-adapter
|
||||||
|
- 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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
## Using SNAPSHOT builds
|
||||||
|
|
||||||
|
In order to use a pre-built SNAPSHOT artifact published to the Open Source Sonatype Repository Hosting site, the artifact repository may need to be added to the POM, global settings.xml or an artifact repository proxy server. The following is the XML snippet for inclusion in a POM file.
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<repositories>
|
||||||
|
<repository>
|
||||||
|
<id>ossrh</id>
|
||||||
|
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
|
||||||
|
<snapshots>
|
||||||
|
<enabled>true</enabled>
|
||||||
|
</snapshots>
|
||||||
|
</repository>
|
||||||
|
</repositories>
|
||||||
|
```
|
200
pom.xml
Normal file
200
pom.xml
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
Copyright 2019 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.
|
||||||
|
-->
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>de.acosix.alfresco.maven</groupId>
|
||||||
|
<artifactId>de.acosix.alfresco.maven.project.parent-6.1.2</artifactId>
|
||||||
|
<version>1.2.0</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<groupId>de.acosix.alfresco.keycloak</groupId>
|
||||||
|
<artifactId>de.acosix.alfresco.keycloak.parent</artifactId>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
<packaging>pom</packaging>
|
||||||
|
|
||||||
|
<name>Acosix Alfresco Keycloak - Parent</name>
|
||||||
|
<description>Addon to provide Keycloak-related customisations / extensions to out-of-the-box Alfresco authentication and authorization functionality</description>
|
||||||
|
<url>https://github.com/Acosix/alfresco-keycloak</url>
|
||||||
|
|
||||||
|
<licenses>
|
||||||
|
<license>
|
||||||
|
<name>Apache License, Version 2.0</name>
|
||||||
|
<url>https://www.apache.org/licenses/LICENSE-2.0.txt</url>
|
||||||
|
</license>
|
||||||
|
</licenses>
|
||||||
|
|
||||||
|
<scm>
|
||||||
|
<connection>scm:git:git@github.com:Acosix/alfresco-keycloak.git</connection>
|
||||||
|
<developerConnection>scm:git:git@github.com:Acosix/alfresco-keycloak.git</developerConnection>
|
||||||
|
<url>git@github.com:Acosix/alfresco-keycloak.git</url>
|
||||||
|
</scm>
|
||||||
|
|
||||||
|
<developers>
|
||||||
|
<developer>
|
||||||
|
<id>AFaust</id>
|
||||||
|
<name>Axel Faust</name>
|
||||||
|
<email>axel.faust@acosix.de</email>
|
||||||
|
<organization>Acosix GmbH</organization>
|
||||||
|
<roles>
|
||||||
|
<role>Founder</role>
|
||||||
|
<role>Architect</role>
|
||||||
|
<role>Developer</role>
|
||||||
|
</roles>
|
||||||
|
<properties>
|
||||||
|
<twitter>twitter.com/ReluctantBird83</twitter>
|
||||||
|
</properties>
|
||||||
|
</developer>
|
||||||
|
</developers>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<messages.path>acosix/keycloak</messages.path>
|
||||||
|
<messages.packageId>acosix.keycloak</messages.packageId>
|
||||||
|
<moduleId>acosix-keycloak</moduleId>
|
||||||
|
|
||||||
|
<!-- Java 7 is out of support -->
|
||||||
|
<maven.compiler.source>1.8</maven.compiler.source>
|
||||||
|
<maven.compiler.target>1.8</maven.compiler.target>
|
||||||
|
|
||||||
|
<keycloak.version>6.0.1</keycloak.version>
|
||||||
|
<!-- lowest common denominator of Repository / Share in 6.1 -->
|
||||||
|
<apache.httpclient.version>4.5.1</apache.httpclient.version>
|
||||||
|
<apache.httpcore.version>4.4.3</apache.httpcore.version>
|
||||||
|
|
||||||
|
<acosix.utility.version>1.0.7.0</acosix.utility.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencyManagement>
|
||||||
|
<dependencies>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-common</artifactId>
|
||||||
|
<version>${keycloak.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-core</artifactId>
|
||||||
|
<version>${keycloak.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-adapter-core</artifactId>
|
||||||
|
<version>${keycloak.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-adapter-spi</artifactId>
|
||||||
|
<version>${keycloak.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-servlet-adapter-spi</artifactId>
|
||||||
|
<version>${keycloak.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-servlet-filter-adapter</artifactId>
|
||||||
|
<version>${keycloak.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-authz-client</artifactId>
|
||||||
|
<version>${keycloak.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- HttpClient already bundled by both Repository and Share web apps -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.httpcomponents</groupId>
|
||||||
|
<artifactId>httpclient</artifactId>
|
||||||
|
<version>${apache.httpclient.version}</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.httpcomponents</groupId>
|
||||||
|
<artifactId>httpcore</artifactId>
|
||||||
|
<version>${apache.httpcore.version}</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>de.acosix.alfresco.utility</groupId>
|
||||||
|
<artifactId>de.acosix.alfresco.utility.common</artifactId>
|
||||||
|
<version>${acosix.utility.version}</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>de.acosix.alfresco.utility</groupId>
|
||||||
|
<artifactId>de.acosix.alfresco.utility.core.repo</artifactId>
|
||||||
|
<version>${acosix.utility.version}</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>de.acosix.alfresco.utility</groupId>
|
||||||
|
<artifactId>de.acosix.alfresco.utility.core.repo</artifactId>
|
||||||
|
<version>${acosix.utility.version}</version>
|
||||||
|
<classifier>installable</classifier>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>de.acosix.alfresco.utility</groupId>
|
||||||
|
<artifactId>de.acosix.alfresco.utility.core.share</artifactId>
|
||||||
|
<version>${acosix.utility.version}</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>de.acosix.alfresco.utility</groupId>
|
||||||
|
<artifactId>de.acosix.alfresco.utility.core.share</artifactId>
|
||||||
|
<version>${acosix.utility.version}</version>
|
||||||
|
<classifier>installable</classifier>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</dependencyManagement>
|
||||||
|
|
||||||
|
<repositories>
|
||||||
|
<repository>
|
||||||
|
<id>ossrh</id>
|
||||||
|
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
|
||||||
|
<snapshots>
|
||||||
|
<enabled>true</enabled>
|
||||||
|
</snapshots>
|
||||||
|
</repository>
|
||||||
|
</repositories>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
|
||||||
|
</build>
|
||||||
|
|
||||||
|
<modules>
|
||||||
|
<module>repository</module>
|
||||||
|
<module>share</module>
|
||||||
|
</modules>
|
||||||
|
</project>
|
2
repository/file-mapping.properties
Normal file
2
repository/file-mapping.properties
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
include.default=true
|
||||||
|
/web=/
|
6
repository/module.properties
Normal file
6
repository/module.properties
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
module.id=${moduleId}
|
||||||
|
module.title=${project.name}
|
||||||
|
module.description=${project.description}
|
||||||
|
module.version=${noSnapshotVersion}
|
||||||
|
|
||||||
|
module.repo.version.min=5
|
49
repository/pom.xml
Normal file
49
repository/pom.xml
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
|
<!--
|
||||||
|
Copyright 2019 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.
|
||||||
|
-->
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>de.acosix.alfresco.keycloak</groupId>
|
||||||
|
<artifactId>de.acosix.alfresco.keycloak.parent</artifactId>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>de.acosix.alfresco.keycloak.repo</artifactId>
|
||||||
|
<name>Acosix Alfresco Keycloak - Repository Module</name>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.alfresco</groupId>
|
||||||
|
<artifactId>alfresco-repository</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>io.fabric8</groupId>
|
||||||
|
<artifactId>docker-maven-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
1
repository/src/main/config/log4j.properties
Normal file
1
repository/src/main/config/log4j.properties
Normal file
@ -0,0 +1 @@
|
|||||||
|
log4j.logger.${project.artifactId}=INFO
|
24
repository/src/main/config/module-context.xml
Normal file
24
repository/src/main/config/module-context.xml
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
|
<!--
|
||||||
|
Copyright 2019 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.
|
||||||
|
-->
|
||||||
|
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="
|
||||||
|
http://www.springframework.org/schema/beans
|
||||||
|
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</beans>
|
0
repository/src/main/globalConfig/.gitkeep
Normal file
0
repository/src/main/globalConfig/.gitkeep
Normal file
0
repository/src/main/messages/.gitkeep
Normal file
0
repository/src/main/messages/.gitkeep
Normal file
0
repository/src/main/resources/.gitkeep
Normal file
0
repository/src/main/resources/.gitkeep
Normal file
0
repository/src/main/webapp/.gitkeep
Normal file
0
repository/src/main/webapp/.gitkeep
Normal file
0
repository/src/main/webscripts/.gitkeep
Normal file
0
repository/src/main/webscripts/.gitkeep
Normal file
2
share/file-mapping.properties
Normal file
2
share/file-mapping.properties
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
include.default=true
|
||||||
|
/web=/
|
4
share/module.properties
Normal file
4
share/module.properties
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
module.id=${moduleId}
|
||||||
|
module.title=${project.name}
|
||||||
|
module.description=${project.description}
|
||||||
|
module.version=${noSnapshotVersion}
|
113
share/pom.xml
Normal file
113
share/pom.xml
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
|
<!--
|
||||||
|
Copyright 2019 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.
|
||||||
|
-->
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>de.acosix.alfresco.keycloak</groupId>
|
||||||
|
<artifactId>de.acosix.alfresco.keycloak.parent</artifactId>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>de.acosix.alfresco.keycloak.share</artifactId>
|
||||||
|
<name>Acosix Alfresco Keycloak - Share Module</name>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.alfresco</groupId>
|
||||||
|
<artifactId>share</artifactId>
|
||||||
|
<classifier>classes</classifier>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>javax.servlet</groupId>
|
||||||
|
<artifactId>javax.servlet-api</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-adapter-core</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-servlet-adapter-spi</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-servlet-filter-adapter</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-authz-client</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>de.acosix.alfresco.utility</groupId>
|
||||||
|
<artifactId>de.acosix.alfresco.utility.core.repo</artifactId>
|
||||||
|
<classifier>installable</classifier>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>de.acosix.alfresco.utility</groupId>
|
||||||
|
<artifactId>de.acosix.alfresco.utility.core.share</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>de.acosix.alfresco.utility</groupId>
|
||||||
|
<artifactId>de.acosix.alfresco.utility.core.share</artifactId>
|
||||||
|
<classifier>installable</classifier>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>${project.groupId}</groupId>
|
||||||
|
<artifactId>de.acosix.alfresco.keycloak.repo</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
<classifier>installable</classifier>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<pluginManagement>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>de.thetaphi</groupId>
|
||||||
|
<artifactId>forbiddenapis</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<excludes>
|
||||||
|
<exclude>**/KeycloakAdapterConfigElement.class</exclude>
|
||||||
|
</excludes>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</pluginManagement>
|
||||||
|
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>net.alchim31.maven</groupId>
|
||||||
|
<artifactId>yuicompressor-maven-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
55
share/src/main/assembly/amp.xml
Normal file
55
share/src/main/assembly/amp.xml
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
|
<!--
|
||||||
|
Copyright 2019 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.
|
||||||
|
-->
|
||||||
|
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 http://maven.apache.org/xsd/assembly-1.1.3.xsd">
|
||||||
|
<id>amp</id>
|
||||||
|
<formats>
|
||||||
|
<format>amp</format>
|
||||||
|
</formats>
|
||||||
|
<includeBaseDirectory>false</includeBaseDirectory>
|
||||||
|
<componentDescriptors>
|
||||||
|
<componentDescriptor>assemblies/amp-lib-component.xml</componentDescriptor>
|
||||||
|
<componentDescriptor>assemblies/amp-config-component.xml</componentDescriptor>
|
||||||
|
<componentDescriptor>assemblies/amp-messages-component.xml</componentDescriptor>
|
||||||
|
<componentDescriptor>assemblies/amp-repo-webscript-component.xml</componentDescriptor>
|
||||||
|
<componentDescriptor>assemblies/amp-surf-webscript-component.xml</componentDescriptor>
|
||||||
|
<componentDescriptor>assemblies/amp-templates-component.xml</componentDescriptor>
|
||||||
|
<componentDescriptor>assemblies/amp-webapp-component.xml</componentDescriptor>
|
||||||
|
</componentDescriptors>
|
||||||
|
<fileSets>
|
||||||
|
<fileSet>
|
||||||
|
<directory>${project.basedir}</directory>
|
||||||
|
<outputDirectory></outputDirectory>
|
||||||
|
<includes>
|
||||||
|
<include>*.properties</include>
|
||||||
|
</includes>
|
||||||
|
<filtered>true</filtered>
|
||||||
|
<lineEnding>crlf</lineEnding>
|
||||||
|
</fileSet>
|
||||||
|
</fileSets>
|
||||||
|
<dependencySets>
|
||||||
|
<dependencySet>
|
||||||
|
<outputDirectory>lib</outputDirectory>
|
||||||
|
<includes>
|
||||||
|
<include>org.keycloak:*</include>
|
||||||
|
<include>org.jboss.logging:*</include>
|
||||||
|
<include>org.bouncycastle:*</include>
|
||||||
|
<include>com.fasterxml.jackson.core:*</include>
|
||||||
|
</includes>
|
||||||
|
</dependencySet>
|
||||||
|
</dependencySets>
|
||||||
|
</assembly>
|
60
share/src/main/config/default-config.xml
Normal file
60
share/src/main/config/default-config.xml
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
<?xml version='1.0' encoding='UTF-8' ?>
|
||||||
|
<!--
|
||||||
|
Copyright 2019 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.
|
||||||
|
-->
|
||||||
|
<alfresco-config>
|
||||||
|
|
||||||
|
<plug-ins>
|
||||||
|
<element-readers>
|
||||||
|
<element-reader element-name="keycloak-auth-config"
|
||||||
|
class="${project.artifactId}.config.KeycloakAuthenticationConfigElementReader" />
|
||||||
|
<element-reader element-name="keycloak-adapter-config"
|
||||||
|
class="${project.artifactId}.config.KeycloakAdapterConfigElementReader" />
|
||||||
|
</element-readers>
|
||||||
|
</plug-ins>
|
||||||
|
|
||||||
|
<!-- sensible default configuration (similar to Repository identity-service-authentication.properties -->
|
||||||
|
<config evaluator="string-compare" condition="Keycloak">
|
||||||
|
<keycloak-auth-config>
|
||||||
|
<enhance-login-form>true</enhance-login-form>
|
||||||
|
<enable-sso-filter>true</enable-sso-filter>
|
||||||
|
<force-keycloak-sso>false</force-keycloak-sso>
|
||||||
|
<!-- use 8443 as default SSL redirect based on Tomcat default server.xml configuration -->
|
||||||
|
<ssl-redirect-port>8443</ssl-redirect-port>
|
||||||
|
<body-buffer-limit>10485760</body-buffer-limit>
|
||||||
|
<session-mapper-limit>1000</session-mapper-limit>
|
||||||
|
</keycloak-auth-config>
|
||||||
|
<keycloak-adapter-config>
|
||||||
|
<auth-server-url>http://localhost:8180/auth</auth-server-url>
|
||||||
|
<realm>alfresco</realm>
|
||||||
|
<resource>alfresco</resource>
|
||||||
|
<ssl-required>none</ssl-required>
|
||||||
|
<!-- other than content-app / Identity Service, Share must/should be a confidential client to exchange code for access token + refresh -->
|
||||||
|
<public-client>false</public-client>
|
||||||
|
<credentials>
|
||||||
|
<provider>secret</provider>
|
||||||
|
</credentials>
|
||||||
|
</keycloak-adapter-config>
|
||||||
|
</config>
|
||||||
|
|
||||||
|
<!-- add to the global configuration -->
|
||||||
|
<config evaluator="string-compare">
|
||||||
|
<user>
|
||||||
|
<!-- make sure groups of a user are kept up-to-date in at least 60 seconds intervals (lazily refreshed on next request) -->
|
||||||
|
<cached-user-groups-timeout>60000</cached-user-groups-timeout>
|
||||||
|
</user>
|
||||||
|
</config>
|
||||||
|
|
||||||
|
</alfresco-config>
|
1
share/src/main/config/log4j.properties
Normal file
1
share/src/main/config/log4j.properties
Normal file
@ -0,0 +1 @@
|
|||||||
|
log4j.logger.${project.artifactId}=INFO
|
20
share/src/main/config/module-config.xml
Normal file
20
share/src/main/config/module-config.xml
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<?xml version='1.0' encoding='UTF-8' ?>
|
||||||
|
<!--
|
||||||
|
Copyright 2019 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.
|
||||||
|
-->
|
||||||
|
<alfresco-config>
|
||||||
|
|
||||||
|
|
||||||
|
</alfresco-config>
|
77
share/src/main/config/module-context.xml
Normal file
77
share/src/main/config/module-context.xml
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
|
<!--
|
||||||
|
Copyright 2019 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.
|
||||||
|
-->
|
||||||
|
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="
|
||||||
|
http://www.springframework.org/schema/beans
|
||||||
|
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
|
||||||
|
|
||||||
|
<bean class="de.acosix.alfresco.utility.share.spring.WebFrameworkConfigSourceInserter">
|
||||||
|
<property name="afterConfigSources">
|
||||||
|
<list>
|
||||||
|
<value>classpath:alfresco/share-config.xml</value>
|
||||||
|
</list>
|
||||||
|
</property>
|
||||||
|
<property name="beforeConfigSources">
|
||||||
|
<list>
|
||||||
|
<value>classpath:alfresco/web-extension/share-config-custom.xml</value>
|
||||||
|
<value>jar:*!/META-INF/share-config-custom.xml</value>
|
||||||
|
</list>
|
||||||
|
</property>
|
||||||
|
<property name="configSources">
|
||||||
|
<list>
|
||||||
|
<value>classpath:alfresco/module/${moduleId}/default-config.xml</value>
|
||||||
|
</list>
|
||||||
|
</property>
|
||||||
|
</bean>
|
||||||
|
|
||||||
|
<bean class="org.springframework.extensions.config.ConfigBootstrap" init-method="register">
|
||||||
|
<property name="configService" ref="web.config" />
|
||||||
|
<property name="configs">
|
||||||
|
<list>
|
||||||
|
<value>classpath:alfresco/module/${moduleId}/module-config.xml</value>
|
||||||
|
</list>
|
||||||
|
</property>
|
||||||
|
</bean>
|
||||||
|
|
||||||
|
<bean id="${moduleId}.SessionIdMapper" class="${project.artifactId}.web.DefaultSessionIdMapper">
|
||||||
|
<property name="configService" ref="web.config" />
|
||||||
|
</bean>
|
||||||
|
|
||||||
|
<bean id="${moduleId}.KeycloakAuthenticationFilter" abstract="true" class="${project.artifactId}.web.KeycloakAuthenticationFilter">
|
||||||
|
<property name="configService" ref="web.config" />
|
||||||
|
<property name="connectorService" ref="connector.service" />
|
||||||
|
<property name="pageViewResolver" ref="pageViewResolver" />
|
||||||
|
<property name="sessionIdMapper" ref="${moduleId}.SessionIdMapper" />
|
||||||
|
<property name="primaryEndpoint" value="alfresco" />
|
||||||
|
<property name="secondaryEndpoints">
|
||||||
|
<list>
|
||||||
|
<value>alfresco-api</value>
|
||||||
|
<value>alfresco-feed</value>
|
||||||
|
</list>
|
||||||
|
</property>
|
||||||
|
</bean>
|
||||||
|
|
||||||
|
<bean id="${moduleId}.UserGroupsLoadFilter" class="${project.artifactId}.web.UserGroupsLoadFilter">
|
||||||
|
<property name="configService" ref="web.config" />
|
||||||
|
<property name="connectorService" ref="connector.service" />
|
||||||
|
</bean>
|
||||||
|
|
||||||
|
<bean class="${project.artifactId}.spring.KeycloakAuthenticationFilterActivation">
|
||||||
|
<property name="moduleId" value="${moduleId}" />
|
||||||
|
</bean>
|
||||||
|
|
||||||
|
</beans>
|
0
share/src/main/config/share-global.properties
Normal file
0
share/src/main/config/share-global.properties
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
<?xml version='1.0' encoding='UTF-8' ?>
|
||||||
|
<!--
|
||||||
|
Copyright 2019 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.
|
||||||
|
-->
|
||||||
|
<extension>
|
||||||
|
<modules>
|
||||||
|
<module>
|
||||||
|
<id>${moduleId} - Base Extensions</id>
|
||||||
|
<description>${project.name} - Base Extensions</description>
|
||||||
|
<version>${noSnapshotVersion}</version>
|
||||||
|
<auto-deploy>true</auto-deploy>
|
||||||
|
|
||||||
|
<customizations>
|
||||||
|
<customization>
|
||||||
|
<targetPackageRoot>org.alfresco</targetPackageRoot>
|
||||||
|
<sourcePackageRoot>de.acosix.keycloak.customisations</sourcePackageRoot>
|
||||||
|
</customization>
|
||||||
|
</customizations>
|
||||||
|
|
||||||
|
<customization>
|
||||||
|
<targetPackageRoot>org.alfresco.share.pages</targetPackageRoot>
|
||||||
|
<sourcePackageRoot>de.acosix.keycloak.customisations.share.header</sourcePackageRoot>
|
||||||
|
<alwaysApply>
|
||||||
|
<webscript>share-header</webscript>
|
||||||
|
</alwaysApply>
|
||||||
|
</customization>
|
||||||
|
</module>
|
||||||
|
</modules>
|
||||||
|
</extension>
|
@ -0,0 +1,457 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 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.share.config;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.alfresco.error.AlfrescoRuntimeException;
|
||||||
|
import org.alfresco.util.EqualsHelper;
|
||||||
|
import org.alfresco.util.ParameterCheck;
|
||||||
|
import org.keycloak.representations.adapters.config.AdapterConfig;
|
||||||
|
import org.springframework.extensions.config.ConfigElement;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
|
import de.acosix.alfresco.utility.share.config.BaseCustomConfigElement;
|
||||||
|
import de.acosix.alfresco.utility.share.config.ConfigValueHolder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Axel Faust
|
||||||
|
*/
|
||||||
|
public class KeycloakAdapterConfigElement extends BaseCustomConfigElement
|
||||||
|
{
|
||||||
|
|
||||||
|
public static final String NAME = "keycloak-adapter-config";
|
||||||
|
|
||||||
|
private static final long serialVersionUID = -7211927327179092723L;
|
||||||
|
|
||||||
|
private static final Map<String, Field> FIELD_BY_CONFIG_NAME;
|
||||||
|
|
||||||
|
private static final Map<String, Class<?>> VALUE_TYPE_BY_CONFIG_NAME;
|
||||||
|
|
||||||
|
private static final List<String> CONFIG_NAMES;
|
||||||
|
|
||||||
|
static
|
||||||
|
{
|
||||||
|
final Map<String, Field> fieldByConfigName = new HashMap<>();
|
||||||
|
final Map<String, Class<?>> valueTypeByConfigName = new HashMap<>();
|
||||||
|
final List<String> configNames = new ArrayList<>();
|
||||||
|
|
||||||
|
final Set<Class<?>> supportedValueTypes = new HashSet<>(Arrays.asList(String.class, Map.class));
|
||||||
|
final Map<Class<?>, Class<?>> primitiveWrapperTypeMap = new HashMap<>();
|
||||||
|
final Class<?>[] wrapperTypes = { Integer.class, Long.class, Boolean.class, Short.class, Byte.class, Character.class, Float.class,
|
||||||
|
Double.class };
|
||||||
|
final Class<?>[] primitiveTypes = { int.class, long.class, boolean.class, short.class, byte.class, char.class, float.class,
|
||||||
|
double.class };
|
||||||
|
for (int i = 0; i < primitiveTypes.length; i++)
|
||||||
|
{
|
||||||
|
supportedValueTypes.add(primitiveTypes[i]);
|
||||||
|
supportedValueTypes.add(wrapperTypes[i]);
|
||||||
|
primitiveWrapperTypeMap.put(primitiveTypes[i], wrapperTypes[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Class<?> cls = AdapterConfig.class;
|
||||||
|
while (cls != null && !Object.class.equals(cls))
|
||||||
|
{
|
||||||
|
final Field[] fields = cls.getDeclaredFields();
|
||||||
|
for (final Field field : fields)
|
||||||
|
{
|
||||||
|
final JsonProperty annotation = field.getAnnotation(JsonProperty.class);
|
||||||
|
if (annotation != null)
|
||||||
|
{
|
||||||
|
final String configName = annotation.value();
|
||||||
|
Class<?> valueType = field.getType();
|
||||||
|
if (valueType.isPrimitive())
|
||||||
|
{
|
||||||
|
valueType = primitiveWrapperTypeMap.get(valueType);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (supportedValueTypes.contains(valueType))
|
||||||
|
{
|
||||||
|
fieldByConfigName.put(configName, field);
|
||||||
|
valueTypeByConfigName.put(configName, valueType);
|
||||||
|
configNames.add(configName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cls = cls.getSuperclass();
|
||||||
|
}
|
||||||
|
|
||||||
|
FIELD_BY_CONFIG_NAME = Collections.unmodifiableMap(fieldByConfigName);
|
||||||
|
VALUE_TYPE_BY_CONFIG_NAME = Collections.unmodifiableMap(valueTypeByConfigName);
|
||||||
|
CONFIG_NAMES = Collections.unmodifiableList(configNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final Map<String, Object> configValueByField = new HashMap<>();
|
||||||
|
|
||||||
|
protected final Set<String> markedAsUnset = new HashSet<>();
|
||||||
|
|
||||||
|
protected final ConfigValueHolder<Long> connectionTimeout = new ConfigValueHolder<>();
|
||||||
|
|
||||||
|
protected final ConfigValueHolder<Long> socketTimeout = new ConfigValueHolder<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance of this class.
|
||||||
|
*/
|
||||||
|
public KeycloakAdapterConfigElement()
|
||||||
|
{
|
||||||
|
super(NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the connectionTimeout
|
||||||
|
*/
|
||||||
|
public Long getConnectionTimeout()
|
||||||
|
{
|
||||||
|
return this.connectionTimeout.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param connectionTimeout
|
||||||
|
* the connectionTimeout to set
|
||||||
|
*/
|
||||||
|
public void setConnectionTimeout(final Long connectionTimeout)
|
||||||
|
{
|
||||||
|
this.connectionTimeout.setValue(connectionTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the socketTimeout
|
||||||
|
*/
|
||||||
|
public Long getSocketTimeout()
|
||||||
|
{
|
||||||
|
return this.socketTimeout.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param socketTimeout
|
||||||
|
* the socketTimeout to set
|
||||||
|
*/
|
||||||
|
public void setSocketTimeout(final Long socketTimeout)
|
||||||
|
{
|
||||||
|
this.socketTimeout.setValue(socketTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a specific field is supported by this config element.
|
||||||
|
*
|
||||||
|
* @param fieldName
|
||||||
|
* the name of the field to check
|
||||||
|
* @return {@code true} if the field is supported, {@code false} otherwise
|
||||||
|
*/
|
||||||
|
public boolean isFieldSupported(final String fieldName)
|
||||||
|
{
|
||||||
|
ParameterCheck.mandatoryString("fieldName", fieldName);
|
||||||
|
final boolean supported = CONFIG_NAMES.contains(fieldName);
|
||||||
|
return supported;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the expected type of value for a specific field. This operation will never return a class object for primitive types,
|
||||||
|
* instead replacing those with the class object for the corresponding wrapper type.
|
||||||
|
*
|
||||||
|
* @param fieldName
|
||||||
|
* the name of the field for which to retrieve the type of value
|
||||||
|
* @return the type of value for the field
|
||||||
|
*/
|
||||||
|
public Class<?> getFieldValueType(final String fieldName)
|
||||||
|
{
|
||||||
|
if (!this.isFieldSupported(fieldName))
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException(fieldName + " is not a supported field");
|
||||||
|
}
|
||||||
|
final Class<?> valueType = VALUE_TYPE_BY_CONFIG_NAME.get(fieldName);
|
||||||
|
return valueType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the configured value for a specific field. Default values inherent in the {@link AdapterConfig Keycloak classes} are not
|
||||||
|
* considered by this operation.
|
||||||
|
*
|
||||||
|
* @param fieldName
|
||||||
|
* the name of the field for which to retrieve the value
|
||||||
|
* @return the currently configured value for the field, or {@code null} if no value has been configured
|
||||||
|
*/
|
||||||
|
public Object getFieldValue(final String fieldName)
|
||||||
|
{
|
||||||
|
final Object value = this.configValueByField.get(fieldName);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the configured value for a specific field.
|
||||||
|
*
|
||||||
|
* @param fieldName
|
||||||
|
* the name of the field for which to set the value
|
||||||
|
* @param value
|
||||||
|
* the value of the field to set
|
||||||
|
* @throws IllegalArgumentException
|
||||||
|
* if the field is {@link #isFieldSupported(String) not supported} or the type of value does not match the required type
|
||||||
|
*/
|
||||||
|
public void setFieldValue(final String fieldName, final Object value)
|
||||||
|
{
|
||||||
|
if (!this.isFieldSupported(fieldName))
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException(fieldName + " is not a supported field");
|
||||||
|
}
|
||||||
|
ParameterCheck.mandatory("value", value);
|
||||||
|
final Class<?> valueType = VALUE_TYPE_BY_CONFIG_NAME.get(fieldName);
|
||||||
|
if (!valueType.isInstance(value))
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException("Value is not an instance of " + valueType);
|
||||||
|
}
|
||||||
|
this.configValueByField.put(fieldName, value);
|
||||||
|
this.markedAsUnset.remove(fieldName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the configured value for a specific field.
|
||||||
|
*
|
||||||
|
* @param fieldName
|
||||||
|
* the name of the field for which to set the value
|
||||||
|
* @param markAsUnset
|
||||||
|
* {@code true} if the field should be marked as explicitly unset for the purpose of {@link #combine(ConfigElement) merging
|
||||||
|
* with other config elements}, {@code false} otherwise
|
||||||
|
* @throws IllegalArgumentException
|
||||||
|
* if the field is {@link #isFieldSupported(String) not supported}
|
||||||
|
*/
|
||||||
|
public void removeFieldValue(final String fieldName, final boolean markAsUnset)
|
||||||
|
{
|
||||||
|
if (!this.isFieldSupported(fieldName))
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException(fieldName + " is not a supported field");
|
||||||
|
}
|
||||||
|
this.configValueByField.remove(fieldName);
|
||||||
|
if (markAsUnset)
|
||||||
|
{
|
||||||
|
this.markedAsUnset.add(fieldName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the explicit {@code unset} flag of a field that may have been set via {@link #removeFieldValue(String, boolean) value
|
||||||
|
* removal}.
|
||||||
|
*
|
||||||
|
* @param fieldName
|
||||||
|
* the name of the field for which to retrieve the flag
|
||||||
|
* @return the value of the flag
|
||||||
|
*/
|
||||||
|
public boolean isFieldMarkedAsUnset(final String fieldName)
|
||||||
|
{
|
||||||
|
if (!this.isFieldSupported(fieldName))
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException(fieldName + " is not a supported field");
|
||||||
|
}
|
||||||
|
final boolean unset = this.markedAsUnset.contains(fieldName);
|
||||||
|
return unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds an instance of a Keycloak adapter configuration based on the configured values managed by this config element.
|
||||||
|
*
|
||||||
|
* @return the adapter configuration instance
|
||||||
|
*/
|
||||||
|
public AdapterConfig buildAdapterConfiguration()
|
||||||
|
{
|
||||||
|
final AdapterConfig config = new AdapterConfig();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
for (final String configName : CONFIG_NAMES)
|
||||||
|
{
|
||||||
|
final Field field = FIELD_BY_CONFIG_NAME.get(configName);
|
||||||
|
|
||||||
|
final Object value = this.configValueByField.get(configName);
|
||||||
|
if (value != null)
|
||||||
|
{
|
||||||
|
// TODO Refactor towards use of setter to avoid setAccessible
|
||||||
|
field.setAccessible(true);
|
||||||
|
field.set(config, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (final IllegalAccessException ex)
|
||||||
|
{
|
||||||
|
throw new AlfrescoRuntimeException("Error building adapter configuration", ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||||
|
@Override
|
||||||
|
public ConfigElement combine(final ConfigElement configElement)
|
||||||
|
{
|
||||||
|
if (!(configElement instanceof KeycloakAdapterConfigElement))
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException("Cannot combine with " + configElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
final KeycloakAdapterConfigElement combined = new KeycloakAdapterConfigElement();
|
||||||
|
final KeycloakAdapterConfigElement otherConfigElement = (KeycloakAdapterConfigElement) configElement;
|
||||||
|
|
||||||
|
for (final String configName : CONFIG_NAMES)
|
||||||
|
{
|
||||||
|
final Object thisValue = this.getFieldValue(configName);
|
||||||
|
final Object otherValue = otherConfigElement.getFieldValue(configName);
|
||||||
|
|
||||||
|
if (otherValue != null)
|
||||||
|
{
|
||||||
|
Object valueToSet = otherValue;
|
||||||
|
if (thisValue instanceof Map<?, ?> && otherValue instanceof Map<?, ?>)
|
||||||
|
{
|
||||||
|
valueToSet = new HashMap<>((Map<?, ?>) thisValue);
|
||||||
|
((Map) valueToSet).putAll((Map) otherValue);
|
||||||
|
}
|
||||||
|
else if (otherValue instanceof Map<?, ?>)
|
||||||
|
{
|
||||||
|
valueToSet = new HashMap<>((Map<?, ?>) otherValue);
|
||||||
|
}
|
||||||
|
combined.setFieldValue(configName, valueToSet);
|
||||||
|
}
|
||||||
|
else if (otherConfigElement.isFieldMarkedAsUnset(configName) || this.isFieldMarkedAsUnset(configName))
|
||||||
|
{
|
||||||
|
combined.removeFieldValue(configName, true);
|
||||||
|
}
|
||||||
|
else if (thisValue != null)
|
||||||
|
{
|
||||||
|
combined.setFieldValue(configName, thisValue instanceof Map<?, ?> ? new HashMap<>((Map<?, ?>) thisValue) : thisValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (otherConfigElement.connectionTimeout.isUnset())
|
||||||
|
{
|
||||||
|
combined.connectionTimeout.unset();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
combined.setConnectionTimeout(otherConfigElement.getConnectionTimeout() != null ? otherConfigElement.getConnectionTimeout()
|
||||||
|
: this.getConnectionTimeout());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (otherConfigElement.socketTimeout.isUnset())
|
||||||
|
{
|
||||||
|
combined.socketTimeout.unset();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
combined.setSocketTimeout(
|
||||||
|
otherConfigElement.getSocketTimeout() != null ? otherConfigElement.getSocketTimeout() : this.getSocketTimeout());
|
||||||
|
}
|
||||||
|
|
||||||
|
return combined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
final StringBuilder builder = new StringBuilder();
|
||||||
|
builder.append("KeycloakAdapterConfigElement [");
|
||||||
|
builder.append("configValueByField=");
|
||||||
|
builder.append(this.configValueByField);
|
||||||
|
builder.append(",markedAsUnset=");
|
||||||
|
builder.append(this.markedAsUnset);
|
||||||
|
if (this.connectionTimeout != null)
|
||||||
|
{
|
||||||
|
builder.append(",connectionTimeout=");
|
||||||
|
builder.append(this.connectionTimeout);
|
||||||
|
}
|
||||||
|
if (this.connectionTimeout != null)
|
||||||
|
{
|
||||||
|
builder.append(",socketTimeout=");
|
||||||
|
builder.append(this.socketTimeout);
|
||||||
|
}
|
||||||
|
builder.append("]");
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int hashCode()
|
||||||
|
{
|
||||||
|
final int prime = 31;
|
||||||
|
int result = super.hashCode();
|
||||||
|
|
||||||
|
// use class-consistent order for actual config values
|
||||||
|
for (final String configName : CONFIG_NAMES)
|
||||||
|
{
|
||||||
|
final Object value = this.configValueByField.get(configName);
|
||||||
|
final int valueHash = value == null ? (this.markedAsUnset.contains(configName) ? -1 : 0) : value.hashCode();
|
||||||
|
result = prime * result + valueHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = prime * result + (this.connectionTimeout != null ? this.connectionTimeout.hashCode() : 0);
|
||||||
|
result = prime * result + (this.socketTimeout != null ? this.socketTimeout.hashCode() : 0);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean equals(final Object obj)
|
||||||
|
{
|
||||||
|
if (this == obj)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!super.equals(obj))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!(obj instanceof KeycloakAdapterConfigElement))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final KeycloakAdapterConfigElement other = (KeycloakAdapterConfigElement) obj;
|
||||||
|
if (!EqualsHelper.nullSafeEquals(this.configValueByField, other.configValueByField))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!EqualsHelper.nullSafeEquals(this.markedAsUnset, other.markedAsUnset))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!EqualsHelper.nullSafeEquals(this.connectionTimeout, other.connectionTimeout))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!EqualsHelper.nullSafeEquals(this.socketTimeout, other.socketTimeout))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,142 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 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.share.config;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.alfresco.error.AlfrescoRuntimeException;
|
||||||
|
import org.dom4j.Element;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.extensions.config.ConfigElement;
|
||||||
|
import org.springframework.extensions.config.xml.elementreader.ConfigElementReader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Axel Faust
|
||||||
|
*/
|
||||||
|
public class KeycloakAdapterConfigElementReader implements ConfigElementReader
|
||||||
|
{
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(KeycloakAdapterConfigElementReader.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public ConfigElement parse(final Element element)
|
||||||
|
{
|
||||||
|
final KeycloakAdapterConfigElement configElement = new KeycloakAdapterConfigElement();
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
final Iterator<Element> subElementIterator = element.elementIterator();
|
||||||
|
while (subElementIterator.hasNext())
|
||||||
|
{
|
||||||
|
final Element subElement = subElementIterator.next();
|
||||||
|
final String subElementName = subElement.getName();
|
||||||
|
if (configElement.isFieldSupported(subElementName))
|
||||||
|
{
|
||||||
|
final Class<?> valueType = configElement.getFieldValueType(subElementName);
|
||||||
|
|
||||||
|
if (Map.class.equals(valueType))
|
||||||
|
{
|
||||||
|
final Map<String, Object> configMap = new HashMap<>();
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
final Iterator<Element> mapElementIterator = subElement.elementIterator();
|
||||||
|
while (mapElementIterator.hasNext())
|
||||||
|
{
|
||||||
|
final Element mapElement = mapElementIterator.next();
|
||||||
|
final String key = mapElement.getName();
|
||||||
|
final String value = mapElement.getTextTrim();
|
||||||
|
|
||||||
|
configMap.put(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
configElement.setFieldValue(subElementName, configMap);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
final String textTrim = subElement.getTextTrim();
|
||||||
|
if (textTrim.isEmpty())
|
||||||
|
{
|
||||||
|
configElement.removeFieldValue(subElementName, true);
|
||||||
|
}
|
||||||
|
else if (Number.class.isAssignableFrom(valueType))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
configElement.setFieldValue(subElementName,
|
||||||
|
valueType.getMethod("valueOf", String.class).invoke(null, textTrim));
|
||||||
|
}
|
||||||
|
catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ex)
|
||||||
|
{
|
||||||
|
LOGGER.error(
|
||||||
|
"Number-based value type {} does not provide a publicly accessible, static valueOf to handle conversion of value {}",
|
||||||
|
valueType, textTrim);
|
||||||
|
throw new AlfrescoRuntimeException("Failed to convert configuration value " + textTrim, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (Boolean.class.equals(valueType))
|
||||||
|
{
|
||||||
|
configElement.setFieldValue(subElementName, Boolean.valueOf(textTrim));
|
||||||
|
}
|
||||||
|
else if (Character.class.equals(valueType))
|
||||||
|
{
|
||||||
|
if (textTrim.length() > 1)
|
||||||
|
{
|
||||||
|
throw new IllegalStateException("Value " + textTrim + " has more than one character");
|
||||||
|
}
|
||||||
|
configElement.setFieldValue(subElementName, new Character(textTrim.charAt(0)));
|
||||||
|
}
|
||||||
|
else if (String.class.equals(valueType))
|
||||||
|
{
|
||||||
|
configElement.setFieldValue(subElementName, textTrim);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new UnsupportedOperationException("Unsupported value type " + valueType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
switch (subElementName)
|
||||||
|
{
|
||||||
|
// use -1 as dummy value for empty value to signify that empty value has explicitly been set (relevant for merge/combine
|
||||||
|
// of config)
|
||||||
|
case "connectionTimeout":
|
||||||
|
final String prospectiveConnectionTimeout = subElement.getTextTrim();
|
||||||
|
configElement.setConnectionTimeout(
|
||||||
|
prospectiveConnectionTimeout.isEmpty() ? null : Long.valueOf(prospectiveConnectionTimeout));
|
||||||
|
break;
|
||||||
|
case "socketTimeout":
|
||||||
|
final String prospectiveSocketTimeout = subElement.getTextTrim();
|
||||||
|
configElement.setSocketTimeout(prospectiveSocketTimeout.isEmpty() ? null : Long.valueOf(prospectiveSocketTimeout));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LOGGER.warn("Encountered unsupported Keycloak Adapter config element {}", subElementName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LOGGER.debug("Read configuration element {} from XML section", configElement);
|
||||||
|
|
||||||
|
return configElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,263 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 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.share.config;
|
||||||
|
|
||||||
|
import org.springframework.extensions.config.ConfigElement;
|
||||||
|
|
||||||
|
import de.acosix.alfresco.utility.share.config.BaseCustomConfigElement;
|
||||||
|
import de.acosix.alfresco.utility.share.config.ConfigValueHolder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author Axel Faust
|
||||||
|
*/
|
||||||
|
public class KeycloakAuthenticationConfigElement extends BaseCustomConfigElement
|
||||||
|
{
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 8587583775593697136L;
|
||||||
|
|
||||||
|
public static final String NAME = "keycloak-auth-config";
|
||||||
|
|
||||||
|
protected final ConfigValueHolder<Boolean> enhanceLoginForm = new ConfigValueHolder<>();
|
||||||
|
|
||||||
|
protected final ConfigValueHolder<Boolean> enableSsoFilter = new ConfigValueHolder<>();
|
||||||
|
|
||||||
|
protected final ConfigValueHolder<Boolean> forceKeycloakSso = new ConfigValueHolder<>();
|
||||||
|
|
||||||
|
protected final ConfigValueHolder<Integer> bodyBufferLimit = new ConfigValueHolder<>();
|
||||||
|
|
||||||
|
protected final ConfigValueHolder<Integer> sslRedirectPort = new ConfigValueHolder<>();
|
||||||
|
|
||||||
|
protected final ConfigValueHolder<Integer> sessionMapperLimit = new ConfigValueHolder<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance of this class.
|
||||||
|
*/
|
||||||
|
public KeycloakAuthenticationConfigElement()
|
||||||
|
{
|
||||||
|
super(NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param enhanceLoginForm
|
||||||
|
* the enhanceLoginForm to set
|
||||||
|
*/
|
||||||
|
public void setEnhanceLoginForm(final Boolean enhanceLoginForm)
|
||||||
|
{
|
||||||
|
this.enhanceLoginForm.setValue(enhanceLoginForm);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the enhanceLoginForm
|
||||||
|
*/
|
||||||
|
public Boolean getEnhanceLoginForm()
|
||||||
|
{
|
||||||
|
return this.enhanceLoginForm.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param enableSsoFilter
|
||||||
|
* the enableSsoFilter to set
|
||||||
|
*/
|
||||||
|
public void setEnableSsoFilter(final Boolean enableSsoFilter)
|
||||||
|
{
|
||||||
|
this.enableSsoFilter.setValue(enableSsoFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the enhanceSsoFilter
|
||||||
|
*/
|
||||||
|
public Boolean getEnableSsoFilter()
|
||||||
|
{
|
||||||
|
return this.enableSsoFilter.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param forceKeycloakSso
|
||||||
|
* the forceKeycloakSso to set
|
||||||
|
*/
|
||||||
|
public void setForceKeycloakSso(final Boolean forceKeycloakSso)
|
||||||
|
{
|
||||||
|
this.forceKeycloakSso.setValue(forceKeycloakSso);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the forceKeycloakSso
|
||||||
|
*/
|
||||||
|
public Boolean getForceKeycloakSso()
|
||||||
|
{
|
||||||
|
return this.forceKeycloakSso.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param bodyBufferLimit
|
||||||
|
* the bodyBufferLimit to set
|
||||||
|
*/
|
||||||
|
public void setBodyBufferLimit(final Integer bodyBufferLimit)
|
||||||
|
{
|
||||||
|
this.bodyBufferLimit.setValue(bodyBufferLimit);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the bodyBufferLimit
|
||||||
|
*/
|
||||||
|
public Integer getBodyBufferLimit()
|
||||||
|
{
|
||||||
|
return this.bodyBufferLimit.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param sslRedirectPort
|
||||||
|
* the sslRedirectPort to set
|
||||||
|
*/
|
||||||
|
public void setSslRedirectPort(final Integer sslRedirectPort)
|
||||||
|
{
|
||||||
|
this.sslRedirectPort.setValue(sslRedirectPort);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the sslRedirectPort
|
||||||
|
*/
|
||||||
|
public Integer getSslRedirectPort()
|
||||||
|
{
|
||||||
|
return this.sslRedirectPort.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param sessionMapperLimit
|
||||||
|
* the sessionMapperLimit to set
|
||||||
|
*/
|
||||||
|
public void setSessionMapperLimit(final Integer sessionMapperLimit)
|
||||||
|
{
|
||||||
|
this.sessionMapperLimit.setValue(sessionMapperLimit);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the sessionMapperLimit
|
||||||
|
*/
|
||||||
|
public Integer getSessionMapperLimit()
|
||||||
|
{
|
||||||
|
return this.sessionMapperLimit.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public ConfigElement combine(final ConfigElement configElement)
|
||||||
|
{
|
||||||
|
if (!(configElement instanceof KeycloakAuthenticationConfigElement))
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException("Cannot combine with " + configElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
final KeycloakAuthenticationConfigElement combined = new KeycloakAuthenticationConfigElement();
|
||||||
|
final KeycloakAuthenticationConfigElement otherConfigElement = (KeycloakAuthenticationConfigElement) configElement;
|
||||||
|
|
||||||
|
if (otherConfigElement.enhanceLoginForm.isUnset())
|
||||||
|
{
|
||||||
|
combined.enhanceLoginForm.unset();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
combined.setEnhanceLoginForm(otherConfigElement.getEnhanceLoginForm() != null ? otherConfigElement.getEnhanceLoginForm()
|
||||||
|
: this.getEnhanceLoginForm());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (otherConfigElement.enhanceLoginForm.isUnset())
|
||||||
|
{
|
||||||
|
combined.enhanceLoginForm.unset();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
combined.setEnableSsoFilter(
|
||||||
|
otherConfigElement.getEnableSsoFilter() != null ? otherConfigElement.getEnableSsoFilter() : this.getEnableSsoFilter());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (otherConfigElement.forceKeycloakSso.isUnset())
|
||||||
|
{
|
||||||
|
combined.forceKeycloakSso.unset();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
combined.setForceKeycloakSso(otherConfigElement.getForceKeycloakSso() != null ? otherConfigElement.getForceKeycloakSso()
|
||||||
|
: this.getForceKeycloakSso());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (otherConfigElement.bodyBufferLimit.isUnset())
|
||||||
|
{
|
||||||
|
combined.bodyBufferLimit.unset();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
combined.setBodyBufferLimit(
|
||||||
|
otherConfigElement.getBodyBufferLimit() != null ? otherConfigElement.getBodyBufferLimit() : this.getBodyBufferLimit());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (otherConfigElement.sslRedirectPort.isUnset())
|
||||||
|
{
|
||||||
|
combined.sslRedirectPort.unset();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
combined.setSslRedirectPort(
|
||||||
|
otherConfigElement.getSslRedirectPort() != null ? otherConfigElement.getSslRedirectPort() : this.getSslRedirectPort());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (otherConfigElement.sessionMapperLimit.isUnset())
|
||||||
|
{
|
||||||
|
combined.sessionMapperLimit.unset();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
combined.setSessionMapperLimit(otherConfigElement.getSessionMapperLimit() != null ? otherConfigElement.getSessionMapperLimit()
|
||||||
|
: this.getSessionMapperLimit());
|
||||||
|
}
|
||||||
|
|
||||||
|
return combined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
final StringBuilder builder = new StringBuilder();
|
||||||
|
builder.append("KeycloakAuthenticationConfigElement [");
|
||||||
|
builder.append("enhanceLoginForm=");
|
||||||
|
builder.append(this.enhanceLoginForm);
|
||||||
|
builder.append(", ");
|
||||||
|
builder.append("enableSsoFilter=");
|
||||||
|
builder.append(this.enableSsoFilter);
|
||||||
|
builder.append(", ");
|
||||||
|
builder.append("forceKeycloakSso=");
|
||||||
|
builder.append(this.forceKeycloakSso);
|
||||||
|
builder.append(", ");
|
||||||
|
builder.append("bodyBufferLimit=");
|
||||||
|
builder.append(this.bodyBufferLimit);
|
||||||
|
builder.append(", ");
|
||||||
|
builder.append("sslRedirectPort=");
|
||||||
|
builder.append(this.sslRedirectPort);
|
||||||
|
builder.append(", ");
|
||||||
|
builder.append("sessionMapperLimit=");
|
||||||
|
builder.append(this.sessionMapperLimit);
|
||||||
|
builder.append("]");
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,87 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 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.share.config;
|
||||||
|
|
||||||
|
import org.dom4j.Element;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.extensions.config.ConfigElement;
|
||||||
|
import org.springframework.extensions.config.xml.elementreader.ConfigElementReader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Axel Faust
|
||||||
|
*/
|
||||||
|
public class KeycloakAuthenticationConfigElementReader implements ConfigElementReader
|
||||||
|
{
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(KeycloakAuthenticationConfigElementReader.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public ConfigElement parse(final Element element)
|
||||||
|
{
|
||||||
|
final KeycloakAuthenticationConfigElement configElement = new KeycloakAuthenticationConfigElement();
|
||||||
|
|
||||||
|
final Element enhanceLoginForm = element.element("enhance-login-form");
|
||||||
|
if (enhanceLoginForm != null)
|
||||||
|
{
|
||||||
|
final String value = enhanceLoginForm.getTextTrim();
|
||||||
|
configElement.setEnhanceLoginForm(value.isEmpty() ? null : Boolean.valueOf(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
final Element enableSsoFilter = element.element("enable-sso-filter");
|
||||||
|
if (enableSsoFilter != null)
|
||||||
|
{
|
||||||
|
final String value = enableSsoFilter.getTextTrim();
|
||||||
|
configElement.setEnableSsoFilter(value.isEmpty() ? null : Boolean.valueOf(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
final Element forceKeycloakSso = element.element("force-keycloak-sso");
|
||||||
|
if (forceKeycloakSso != null)
|
||||||
|
{
|
||||||
|
final String value = forceKeycloakSso.getTextTrim();
|
||||||
|
configElement.setForceKeycloakSso(value.isEmpty() ? null : Boolean.valueOf(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
final Element bodyBufferLimit = element.element("body-buffer-limit");
|
||||||
|
if (bodyBufferLimit != null)
|
||||||
|
{
|
||||||
|
final String value = bodyBufferLimit.getTextTrim();
|
||||||
|
configElement.setBodyBufferLimit(value.isEmpty() ? null : Integer.valueOf(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
final Element sslRedirectPort = element.element("ssl-redirect-port");
|
||||||
|
if (sslRedirectPort != null)
|
||||||
|
{
|
||||||
|
final String value = sslRedirectPort.getTextTrim();
|
||||||
|
configElement.setSslRedirectPort(value.isEmpty() ? null : Integer.valueOf(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
final Element sessionMapperLimit = element.element("session-mapper-limit");
|
||||||
|
if (sessionMapperLimit != null)
|
||||||
|
{
|
||||||
|
final String value = sessionMapperLimit.getTextTrim();
|
||||||
|
configElement.setSessionMapperLimit(value.isEmpty() ? null : Integer.valueOf(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGGER.debug("Read configuration element {} from XML section", configElement);
|
||||||
|
|
||||||
|
return configElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 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.share.remote;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
import org.alfresco.web.site.servlet.SlingshotAlfrescoConnector;
|
||||||
|
import org.springframework.extensions.config.RemoteConfigElement.ConnectorDescriptor;
|
||||||
|
import org.springframework.extensions.webscripts.connector.ConnectorContext;
|
||||||
|
import org.springframework.extensions.webscripts.connector.ConnectorSession;
|
||||||
|
import org.springframework.extensions.webscripts.connector.RemoteClient;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Axel Faust
|
||||||
|
*/
|
||||||
|
public class BearerTokenAwareSlingshotAlfrescoConnector extends SlingshotAlfrescoConnector
|
||||||
|
{
|
||||||
|
|
||||||
|
public static final String CS_PARAM_BEARER_TOKEN = "bearerToken";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new instance of this class.
|
||||||
|
*
|
||||||
|
* @param descriptor
|
||||||
|
* the descriptor / configuration of this connector
|
||||||
|
* @param endpoint
|
||||||
|
* the endpoint with which this connector instance should connect
|
||||||
|
*/
|
||||||
|
public BearerTokenAwareSlingshotAlfrescoConnector(final ConnectorDescriptor descriptor, final String endpoint)
|
||||||
|
{
|
||||||
|
super(descriptor, endpoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void applyRequestHeaders(final RemoteClient remoteClient, final ConnectorContext context)
|
||||||
|
{
|
||||||
|
// apply default mapping of headers
|
||||||
|
super.applyRequestHeaders(remoteClient, context);
|
||||||
|
|
||||||
|
final ConnectorSession connectorSession = this.getConnectorSession();
|
||||||
|
if (connectorSession != null)
|
||||||
|
{
|
||||||
|
final String bearerToken = connectorSession.getParameter(CS_PARAM_BEARER_TOKEN);
|
||||||
|
if (bearerToken != null && !bearerToken.trim().isEmpty())
|
||||||
|
{
|
||||||
|
remoteClient.setRequestProperties(Collections.singletonMap("Authorization", "Bearer " + bearerToken));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,102 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 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.share.spring;
|
||||||
|
|
||||||
|
import org.alfresco.util.PropertyCheck;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.BeansException;
|
||||||
|
import org.springframework.beans.factory.InitializingBean;
|
||||||
|
import org.springframework.beans.factory.config.BeanDefinition;
|
||||||
|
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||||
|
import org.springframework.beans.factory.config.RuntimeBeanReference;
|
||||||
|
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||||
|
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
|
||||||
|
import org.springframework.beans.factory.support.GenericBeanDefinition;
|
||||||
|
|
||||||
|
import de.acosix.alfresco.keycloak.share.web.KeycloakAuthenticationFilter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Axel Faust
|
||||||
|
*/
|
||||||
|
public class KeycloakAuthenticationFilterActivation implements BeanDefinitionRegistryPostProcessor, InitializingBean
|
||||||
|
{
|
||||||
|
|
||||||
|
private static final String DEFAULT_SSO_AUTHENTICATION_FILTER_NAME = "SSOAuthenticationFilter";
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(KeycloakAuthenticationFilterActivation.class);
|
||||||
|
|
||||||
|
protected String moduleId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void afterPropertiesSet()
|
||||||
|
{
|
||||||
|
PropertyCheck.mandatory(this, "moduleId", this.moduleId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param moduleId
|
||||||
|
* the moduleId to set
|
||||||
|
*/
|
||||||
|
public void setModuleId(final String moduleId)
|
||||||
|
{
|
||||||
|
this.moduleId = moduleId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void postProcessBeanFactory(final ConfigurableListableBeanFactory beanFactory) throws BeansException
|
||||||
|
{
|
||||||
|
// NO-OP
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void postProcessBeanDefinitionRegistry(final BeanDefinitionRegistry registry) throws BeansException
|
||||||
|
{
|
||||||
|
final String keycloakFilterBeanName = this.moduleId + "." + KeycloakAuthenticationFilter.class.getSimpleName();
|
||||||
|
|
||||||
|
if (registry.containsBeanDefinition(keycloakFilterBeanName))
|
||||||
|
{
|
||||||
|
// re-register default filter under different name
|
||||||
|
final BeanDefinition defaultSsoAuthenticationFilter = registry.getBeanDefinition(DEFAULT_SSO_AUTHENTICATION_FILTER_NAME);
|
||||||
|
registry.removeBeanDefinition(DEFAULT_SSO_AUTHENTICATION_FILTER_NAME);
|
||||||
|
final String defaultSsoAuthenticationFilterReplacementName = this.moduleId + ".default"
|
||||||
|
+ DEFAULT_SSO_AUTHENTICATION_FILTER_NAME;
|
||||||
|
registry.registerBeanDefinition(defaultSsoAuthenticationFilterReplacementName, defaultSsoAuthenticationFilter);
|
||||||
|
|
||||||
|
// re-register our filter under default name
|
||||||
|
final BeanDefinition keycloakSsoAuthenticationFilter = registry.getBeanDefinition(keycloakFilterBeanName);
|
||||||
|
registry.removeBeanDefinition(keycloakFilterBeanName);
|
||||||
|
((GenericBeanDefinition) keycloakSsoAuthenticationFilter).setAbstract(false);
|
||||||
|
keycloakSsoAuthenticationFilter.getPropertyValues().add("defaultSsoFilter",
|
||||||
|
new RuntimeBeanReference(defaultSsoAuthenticationFilterReplacementName));
|
||||||
|
registry.registerBeanDefinition(DEFAULT_SSO_AUTHENTICATION_FILTER_NAME, keycloakSsoAuthenticationFilter);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LOGGER.error("Cannot activate KeycloakAuthenticationFilter bean as abstract bean {} was not found in Spring context",
|
||||||
|
keycloakFilterBeanName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,316 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 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.share.web;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||||
|
|
||||||
|
import org.keycloak.adapters.spi.InMemorySessionIdMapper;
|
||||||
|
import org.keycloak.adapters.spi.SessionIdMapper;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.InitializingBean;
|
||||||
|
import org.springframework.extensions.config.ConfigService;
|
||||||
|
|
||||||
|
import de.acosix.alfresco.keycloak.share.config.KeycloakAuthenticationConfigElement;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This implementation of a {@link SessionIdMapper Keycloak session ID mapper} is based on the {@link InMemorySessionIdMapper in-memory
|
||||||
|
* mapper}, but uses a better model for synchronization and respects configured size limits, ejecting least-recently active sessions first.
|
||||||
|
* Activity of session with regards to being determined the "least-recently active" session is based upon validation calls to
|
||||||
|
* {@link #hasSession(String) hasSession}.
|
||||||
|
*
|
||||||
|
* @author Axel Faust
|
||||||
|
*/
|
||||||
|
public class DefaultSessionIdMapper implements SessionIdMapper, InitializingBean
|
||||||
|
{
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(DefaultSessionIdMapper.class);
|
||||||
|
|
||||||
|
private static final int DEFAULT_SESSION_COUNT_LIMIT = 1000;
|
||||||
|
|
||||||
|
protected final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);
|
||||||
|
|
||||||
|
protected final Map<String, String> ssoToSession = new HashMap<>();
|
||||||
|
|
||||||
|
protected final Map<String, String> sessionToSso = new HashMap<>();
|
||||||
|
|
||||||
|
protected final Map<String, Set<String>> principalToSession = new HashMap<>();
|
||||||
|
|
||||||
|
protected final Map<String, String> sessionToPrincipal = new HashMap<>();
|
||||||
|
|
||||||
|
protected ConfigService configService;
|
||||||
|
|
||||||
|
protected int sessionCountLimit = DEFAULT_SESSION_COUNT_LIMIT;
|
||||||
|
|
||||||
|
protected Set<String> sessionUsedOrder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void afterPropertiesSet()
|
||||||
|
{
|
||||||
|
if (this.configService != null)
|
||||||
|
{
|
||||||
|
final KeycloakAuthenticationConfigElement keycloakAuthConfig = (KeycloakAuthenticationConfigElement) this.configService
|
||||||
|
.getConfig("Keycloak").getConfigElement(KeycloakAuthenticationConfigElement.NAME);
|
||||||
|
final Integer sessionMapperLimit = keycloakAuthConfig.getSessionMapperLimit();
|
||||||
|
if (sessionMapperLimit != null)
|
||||||
|
{
|
||||||
|
this.sessionCountLimit = sessionMapperLimit.intValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.sessionCountLimit <= 0)
|
||||||
|
{
|
||||||
|
LOGGER.warn(
|
||||||
|
"Session count limit is set to {} - session ID mapper will not restrict size of internal data structures (this can cause OOMEs)",
|
||||||
|
this.sessionCountLimit);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.sessionUsedOrder = new LinkedHashSet<>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param configService
|
||||||
|
* the configService to set
|
||||||
|
*/
|
||||||
|
public void setConfigService(final ConfigService configService)
|
||||||
|
{
|
||||||
|
this.configService = configService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param sessionCountLimit
|
||||||
|
* the sessionCountLimit to set
|
||||||
|
*/
|
||||||
|
public void setSessionCountLimit(final int sessionCountLimit)
|
||||||
|
{
|
||||||
|
this.sessionCountLimit = sessionCountLimit;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasSession(final String id)
|
||||||
|
{
|
||||||
|
this.lock.readLock().lock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
LOGGER.debug("Checking hasSession for {}", id);
|
||||||
|
final boolean hasSession = this.sessionToSso.containsKey(id) || this.sessionToPrincipal.containsKey(id);
|
||||||
|
LOGGER.debug("Session {}", hasSession ? "is mapped" : "is not mapped");
|
||||||
|
|
||||||
|
if (hasSession && this.sessionCountLimit > 0)
|
||||||
|
{
|
||||||
|
synchronized (this.sessionUsedOrder)
|
||||||
|
{
|
||||||
|
this.sessionUsedOrder.remove(id);
|
||||||
|
this.sessionUsedOrder.add(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return hasSession;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
this.lock.readLock().unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void clear()
|
||||||
|
{
|
||||||
|
this.lock.writeLock().lock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
LOGGER.info("Clearing all mappings");
|
||||||
|
this.ssoToSession.clear();
|
||||||
|
this.sessionToSso.clear();
|
||||||
|
this.principalToSession.clear();
|
||||||
|
this.sessionToPrincipal.clear();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
this.lock.writeLock().unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Set<String> getUserSessions(final String principal)
|
||||||
|
{
|
||||||
|
Set<String> userSessions;
|
||||||
|
this.lock.readLock().lock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
LOGGER.debug("Retrieving user sessions for {}", principal);
|
||||||
|
final Set<String> lookup = this.principalToSession.get(principal);
|
||||||
|
if (lookup != null)
|
||||||
|
{
|
||||||
|
userSessions = new HashSet<>();
|
||||||
|
userSessions.addAll(lookup);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
userSessions = Collections.emptySet();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
this.lock.readLock().unlock();
|
||||||
|
}
|
||||||
|
LOGGER.debug("Principal {} is mapped to sessions {}", principal, userSessions);
|
||||||
|
return userSessions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String getSessionFromSSO(final String sso)
|
||||||
|
{
|
||||||
|
this.lock.readLock().lock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return this.ssoToSession.get(sso);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
this.lock.readLock().unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void map(final String sso, final String principal, final String session)
|
||||||
|
{
|
||||||
|
this.lock.writeLock().lock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
LOGGER.debug("Adding mapping ({}, {}, {})", sso, principal, session);
|
||||||
|
|
||||||
|
if (sso != null)
|
||||||
|
{
|
||||||
|
this.ssoToSession.put(sso, session);
|
||||||
|
this.sessionToSso.put(session, sso);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (principal != null)
|
||||||
|
{
|
||||||
|
this.principalToSession.compute(principal, (key, value) -> {
|
||||||
|
if (value == null)
|
||||||
|
{
|
||||||
|
value = new HashSet<>();
|
||||||
|
}
|
||||||
|
value.add(session);
|
||||||
|
return value;
|
||||||
|
});
|
||||||
|
this.sessionToPrincipal.put(session, principal);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.sessionCountLimit > 0 && sso != null && principal != null)
|
||||||
|
{
|
||||||
|
synchronized (this.sessionUsedOrder)
|
||||||
|
{
|
||||||
|
this.sessionUsedOrder.add(session);
|
||||||
|
|
||||||
|
final int sessionsToRemove = this.sessionUsedOrder.size() - this.sessionCountLimit;
|
||||||
|
if (sessionsToRemove == 1)
|
||||||
|
{
|
||||||
|
final String sessionToRemove = this.sessionUsedOrder.iterator().next();
|
||||||
|
this.removeSession(sessionToRemove);
|
||||||
|
}
|
||||||
|
// should really not happen, but in place should we ever switch to a more bulk-handling
|
||||||
|
else if (sessionsToRemove > 0)
|
||||||
|
{
|
||||||
|
final List<String> sessionsForRemoval = new ArrayList<>(this.sessionUsedOrder).subList(0, sessionsToRemove);
|
||||||
|
sessionsForRemoval.forEach(this::removeSession);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
this.lock.writeLock().unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void removeSession(final String session)
|
||||||
|
{
|
||||||
|
this.lock.writeLock().lock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
LOGGER.debug("Removing session {}", session);
|
||||||
|
|
||||||
|
final String sso = this.sessionToSso.remove(session);
|
||||||
|
if (sso != null)
|
||||||
|
{
|
||||||
|
this.ssoToSession.remove(sso);
|
||||||
|
}
|
||||||
|
|
||||||
|
final String principal = this.sessionToPrincipal.remove(session);
|
||||||
|
if (principal != null)
|
||||||
|
{
|
||||||
|
this.principalToSession.computeIfPresent(principal, (key, value) -> {
|
||||||
|
value.remove(session);
|
||||||
|
if (value.isEmpty())
|
||||||
|
{
|
||||||
|
value = null;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.sessionCountLimit > 0)
|
||||||
|
{
|
||||||
|
synchronized (this.sessionUsedOrder)
|
||||||
|
{
|
||||||
|
this.sessionUsedOrder.remove(session);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
this.lock.writeLock().unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,192 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 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.share.web;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
import org.alfresco.util.Pair;
|
||||||
|
import org.keycloak.adapters.servlet.ServletHttpFacade;
|
||||||
|
import org.keycloak.adapters.spi.HttpFacade;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This {@link HttpFacade} wraps servlet requests and responses in such a way that any response headers / cookies being set by Keycloak
|
||||||
|
* authenticators are captured, and otherwise no output is written to the servlet response. This is required for some scenarios in which a
|
||||||
|
* redirect action should be included in the login form.
|
||||||
|
*
|
||||||
|
* @author Axel Faust
|
||||||
|
*/
|
||||||
|
public class RedirectCaptureServletHttpFacade extends ServletHttpFacade
|
||||||
|
{
|
||||||
|
|
||||||
|
protected final Map<Pair<String, String>, javax.servlet.http.Cookie> cookies = new HashMap<>();
|
||||||
|
|
||||||
|
protected final Map<String, List<String>> headers = new HashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance of this class for the provided servlet request.
|
||||||
|
*
|
||||||
|
* @param request
|
||||||
|
* the servlet request to facade
|
||||||
|
*/
|
||||||
|
public RedirectCaptureServletHttpFacade(final HttpServletRequest request)
|
||||||
|
{
|
||||||
|
super(request, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Response getResponse()
|
||||||
|
{
|
||||||
|
return new ResponseCaptureFacade();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the cookies
|
||||||
|
*/
|
||||||
|
public List<javax.servlet.http.Cookie> getCookies()
|
||||||
|
{
|
||||||
|
return new ArrayList<>(this.cookies.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the headers
|
||||||
|
*/
|
||||||
|
public Map<String, List<String>> getHeaders()
|
||||||
|
{
|
||||||
|
final Map<String, List<String>> headers = new HashMap<>();
|
||||||
|
this.headers.forEach((headerName, values) -> headers.put(headerName, new ArrayList<>(values)));
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author Axel Faust
|
||||||
|
*/
|
||||||
|
private class ResponseCaptureFacade implements Response
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setStatus(final int status)
|
||||||
|
{
|
||||||
|
// NO-OP
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void addHeader(final String name, final String value)
|
||||||
|
{
|
||||||
|
RedirectCaptureServletHttpFacade.this.headers.computeIfAbsent(name, key -> new ArrayList<>()).add(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setHeader(final String name, final String value)
|
||||||
|
{
|
||||||
|
RedirectCaptureServletHttpFacade.this.headers.put(name, new ArrayList<>(Collections.singleton(value)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void resetCookie(final String name, final String path)
|
||||||
|
{
|
||||||
|
RedirectCaptureServletHttpFacade.this.cookies.remove(new Pair<>(name, path));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setCookie(final String name, final String value, final String path, final String domain, final int maxAge,
|
||||||
|
final boolean secure, final boolean httpOnly)
|
||||||
|
{
|
||||||
|
final javax.servlet.http.Cookie cookie = new javax.servlet.http.Cookie(name, value);
|
||||||
|
cookie.setPath(path);
|
||||||
|
if (domain != null)
|
||||||
|
{
|
||||||
|
cookie.setDomain(domain);
|
||||||
|
}
|
||||||
|
cookie.setMaxAge(maxAge);
|
||||||
|
cookie.setSecure(secure);
|
||||||
|
cookie.setHttpOnly(httpOnly);
|
||||||
|
RedirectCaptureServletHttpFacade.this.cookies.put(new Pair<>(name, path), cookie);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public OutputStream getOutputStream()
|
||||||
|
{
|
||||||
|
return new ByteArrayOutputStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void sendError(final int code)
|
||||||
|
{
|
||||||
|
// NO-OP
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void sendError(final int code, final String message)
|
||||||
|
{
|
||||||
|
// NO-OP
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void end()
|
||||||
|
{
|
||||||
|
// NO-OP
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,261 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 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.share.web;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
import javax.servlet.FilterChain;
|
||||||
|
import javax.servlet.ServletContext;
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.ServletRequest;
|
||||||
|
import javax.servlet.ServletResponse;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpSession;
|
||||||
|
|
||||||
|
import org.alfresco.error.AlfrescoRuntimeException;
|
||||||
|
import org.alfresco.util.PropertyCheck;
|
||||||
|
import org.alfresco.web.site.servlet.SlingshotLoginController;
|
||||||
|
import org.json.simple.JSONArray;
|
||||||
|
import org.json.simple.JSONObject;
|
||||||
|
import org.json.simple.parser.JSONParser;
|
||||||
|
import org.json.simple.parser.ParseException;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.InitializingBean;
|
||||||
|
import org.springframework.context.ApplicationContext;
|
||||||
|
import org.springframework.context.ApplicationContextAware;
|
||||||
|
import org.springframework.extensions.config.ConfigElement;
|
||||||
|
import org.springframework.extensions.config.ConfigService;
|
||||||
|
import org.springframework.extensions.surf.RequestContext;
|
||||||
|
import org.springframework.extensions.surf.RequestContextUtil;
|
||||||
|
import org.springframework.extensions.surf.exception.ConnectorServiceException;
|
||||||
|
import org.springframework.extensions.surf.exception.RequestContextException;
|
||||||
|
import org.springframework.extensions.surf.site.AuthenticationUtil;
|
||||||
|
import org.springframework.extensions.surf.support.AlfrescoUserFactory;
|
||||||
|
import org.springframework.extensions.surf.support.ThreadLocalRequestContext;
|
||||||
|
import org.springframework.extensions.surf.util.URLEncoder;
|
||||||
|
import org.springframework.extensions.webscripts.Status;
|
||||||
|
import org.springframework.extensions.webscripts.connector.Connector;
|
||||||
|
import org.springframework.extensions.webscripts.connector.ConnectorContext;
|
||||||
|
import org.springframework.extensions.webscripts.connector.ConnectorService;
|
||||||
|
import org.springframework.extensions.webscripts.connector.HttpMethod;
|
||||||
|
import org.springframework.extensions.webscripts.connector.Response;
|
||||||
|
import org.springframework.extensions.webscripts.servlet.DependencyInjectedFilter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This filter performs the initial load of user groups for any user authenticated by a filter preceeding it in the filter chain, and
|
||||||
|
* transparently refreshes the user groups after a configurable amount of time has past, in order to avoid Share user groups to become stale
|
||||||
|
* / inconsistent with actual group memberships in the Alfresco Repository. This filter is necessary since the default logic for the simple
|
||||||
|
* initialisation inside {@link SlingshotLoginController} is inaccessible to custom authentication filters, and there is actually no refresh
|
||||||
|
* functionality in default Alfresco at all, which can be problematic for SSO-authenticated sessions that may be active for a long time.
|
||||||
|
*
|
||||||
|
* @author Axel Faust
|
||||||
|
*/
|
||||||
|
public class UserGroupsLoadFilter implements DependencyInjectedFilter, InitializingBean, ApplicationContextAware
|
||||||
|
{
|
||||||
|
|
||||||
|
public static final String SESSION_ATTRIBUTE_KEY_USER_GROUPS_LAST_LOADED = SlingshotLoginController.SESSION_ATTRIBUTE_KEY_USER_GROUPS
|
||||||
|
+ "_lastLoaded";
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(UserGroupsLoadFilter.class);
|
||||||
|
|
||||||
|
private static final long DEFAULT_CACHED_USER_GROUPS_TIMEOUT = 60000;
|
||||||
|
|
||||||
|
protected ApplicationContext applicationContext;
|
||||||
|
|
||||||
|
protected ConfigService configService;
|
||||||
|
|
||||||
|
protected ConnectorService connectorService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setApplicationContext(final ApplicationContext applicationContext)
|
||||||
|
{
|
||||||
|
this.applicationContext = applicationContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void afterPropertiesSet()
|
||||||
|
{
|
||||||
|
PropertyCheck.mandatory(this, "applicationContext", this.applicationContext);
|
||||||
|
PropertyCheck.mandatory(this, "configService", this.configService);
|
||||||
|
PropertyCheck.mandatory(this, "connectorService", this.connectorService);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param configService
|
||||||
|
* the configService to set
|
||||||
|
*/
|
||||||
|
public void setConfigService(final ConfigService configService)
|
||||||
|
{
|
||||||
|
this.configService = configService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param connectorService
|
||||||
|
* the connectorService to set
|
||||||
|
*/
|
||||||
|
public void setConnectorService(final ConnectorService connectorService)
|
||||||
|
{
|
||||||
|
this.connectorService = connectorService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void doFilter(final ServletContext context, final ServletRequest request, final ServletResponse response,
|
||||||
|
final FilterChain chain) throws IOException, ServletException
|
||||||
|
{
|
||||||
|
if (request instanceof HttpServletRequest)
|
||||||
|
{
|
||||||
|
final HttpSession session = ((HttpServletRequest) request).getSession(false);
|
||||||
|
if (session != null)
|
||||||
|
{
|
||||||
|
final String userId = AuthenticationUtil.getUserId((HttpServletRequest) request);
|
||||||
|
final String userGroupsCSVList = (String) session.getAttribute(SlingshotLoginController.SESSION_ATTRIBUTE_KEY_USER_GROUPS);
|
||||||
|
|
||||||
|
final Date lastLoaded = (Date) session.getAttribute(SESSION_ATTRIBUTE_KEY_USER_GROUPS_LAST_LOADED);
|
||||||
|
long cachedUserGroupsTimeout = DEFAULT_CACHED_USER_GROUPS_TIMEOUT;
|
||||||
|
|
||||||
|
final ConfigElement userConfig = this.configService.getGlobalConfig().getConfigElement("user");
|
||||||
|
if (userConfig != null)
|
||||||
|
{
|
||||||
|
final String timeoutConfig = userConfig.getChildValue("cached-user-groups-timeout");
|
||||||
|
if (timeoutConfig != null)
|
||||||
|
{
|
||||||
|
cachedUserGroupsTimeout = Long.parseLong(timeoutConfig, 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userId != null)
|
||||||
|
{
|
||||||
|
if (userGroupsCSVList == null
|
||||||
|
|| (lastLoaded != null && lastLoaded.getTime() + cachedUserGroupsTimeout < System.currentTimeMillis()))
|
||||||
|
{
|
||||||
|
session.setAttribute(SlingshotLoginController.SESSION_ATTRIBUTE_KEY_USER_GROUPS,
|
||||||
|
this.loadUserGroupsCSVList((HttpServletRequest) request, session, userId));
|
||||||
|
session.setAttribute(SESSION_ATTRIBUTE_KEY_USER_GROUPS_LAST_LOADED, new Date());
|
||||||
|
}
|
||||||
|
else if (lastLoaded == null)
|
||||||
|
{
|
||||||
|
// might have just been loaded by an authentication filter on initial login
|
||||||
|
session.setAttribute(SESSION_ATTRIBUTE_KEY_USER_GROUPS_LAST_LOADED, new Date());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
chain.doFilter(request, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the groups a user is a member of as a comma-separated list from the default Alfresco backend.
|
||||||
|
*
|
||||||
|
* @param request
|
||||||
|
* the HTTP servlet request
|
||||||
|
* @param session
|
||||||
|
* the current session
|
||||||
|
* @param userId
|
||||||
|
* the ID of the user for which to load the group memberships
|
||||||
|
* @return the list of groups the user is a member of as a comma-separated list of names
|
||||||
|
*/
|
||||||
|
protected String loadUserGroupsCSVList(final HttpServletRequest request, final HttpSession session, final String userId)
|
||||||
|
{
|
||||||
|
String userGroupsCSVList;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// logic nearly identical to SlingshotLoginController
|
||||||
|
final Connector connector = this.connectorService.getConnector(AlfrescoUserFactory.ALFRESCO_ENDPOINT_ID, userId, session);
|
||||||
|
|
||||||
|
// bug in default Alfresco RequestCachingConnector: with ConnectorContext having HttpMethod.GET, null check of
|
||||||
|
// ThreadLocalRequestContext.getRequestContext() is short-circuited, causing NPE on access
|
||||||
|
final RequestContext requestContext = ThreadLocalRequestContext.getRequestContext();
|
||||||
|
if (requestContext == null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
RequestContextUtil.initRequestContext(this.applicationContext, request, true);
|
||||||
|
}
|
||||||
|
catch (final RequestContextException e)
|
||||||
|
{
|
||||||
|
LOGGER.error("Failed to initialise request context", e);
|
||||||
|
throw new AlfrescoRuntimeException("Failed to initialise request context", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final ConnectorContext c = new ConnectorContext(HttpMethod.GET);
|
||||||
|
c.setContentType("application/json");
|
||||||
|
final Response res = connector.call("/api/people/" + URLEncoder.encode(userId) + "?groups=true", c);
|
||||||
|
|
||||||
|
if (res.getStatus().getCode() == Status.STATUS_OK)
|
||||||
|
{
|
||||||
|
final String responseText = res.getResponse();
|
||||||
|
final JSONParser jsonParser = new JSONParser();
|
||||||
|
final Object userData = jsonParser.parse(responseText.toString());
|
||||||
|
|
||||||
|
final StringBuilder groups = new StringBuilder(512);
|
||||||
|
if (userData instanceof JSONObject)
|
||||||
|
{
|
||||||
|
final Object groupsArray = ((JSONObject) userData).get("groups");
|
||||||
|
if (groupsArray instanceof JSONArray)
|
||||||
|
{
|
||||||
|
for (final Object groupData : (JSONArray) groupsArray)
|
||||||
|
{
|
||||||
|
if (groupData instanceof JSONObject)
|
||||||
|
{
|
||||||
|
final Object groupName = ((JSONObject) groupData).get("itemName");
|
||||||
|
if (groupName != null)
|
||||||
|
{
|
||||||
|
if (groups.length() > 0)
|
||||||
|
{
|
||||||
|
groups.append(',');
|
||||||
|
}
|
||||||
|
groups.append(groupName.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
userGroupsCSVList = groups.toString();
|
||||||
|
|
||||||
|
LOGGER.debug("Retrieved group memberships for user {}: {}", userId, userGroupsCSVList);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LOGGER.warn("Failed to load user groups for {} with backend call resulting in HTTP {} response and message {}", userId,
|
||||||
|
res.getStatus().getCode(), res.getStatus().getMessage());
|
||||||
|
userGroupsCSVList = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (final ConnectorServiceException | ParseException ex)
|
||||||
|
{
|
||||||
|
LOGGER.error("Failed to load user groups for {}", userId, ex);
|
||||||
|
userGroupsCSVList = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return userGroupsCSVList;
|
||||||
|
}
|
||||||
|
}
|
0
share/src/main/messages/.gitkeep
Normal file
0
share/src/main/messages/.gitkeep
Normal file
44
share/src/main/resources/META-INF/web-fragment.xml
Normal file
44
share/src/main/resources/META-INF/web-fragment.xml
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<!--
|
||||||
|
Copyright 2019 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.
|
||||||
|
-->
|
||||||
|
<web-fragment xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee"
|
||||||
|
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
|
||||||
|
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee/web-fragment_3_0.xsd" id="WebAppFragment_ID" version="3.0">
|
||||||
|
|
||||||
|
<!-- Servlet 3.0 Spec does not support ordering filter/filter-mapping before those from web.xml -->
|
||||||
|
<!-- So this only includes filters that do not rely on ordering -->
|
||||||
|
<name>${moduleId}.AddonFilters</name>
|
||||||
|
|
||||||
|
<ordering>
|
||||||
|
<after>
|
||||||
|
<others />
|
||||||
|
</after>
|
||||||
|
</ordering>
|
||||||
|
|
||||||
|
<filter>
|
||||||
|
<filter-name>>${moduleId}.UserGroupsLoadFilter</filter-name>
|
||||||
|
<filter-class>org.springframework.extensions.webscripts.servlet.BeanProxyFilter</filter-class>
|
||||||
|
<init-param>
|
||||||
|
<param-name>beanName</param-name>
|
||||||
|
<param-value>${moduleId}.UserGroupsLoadFilter</param-value>
|
||||||
|
</init-param>
|
||||||
|
</filter>
|
||||||
|
|
||||||
|
<filter-mapping>
|
||||||
|
<filter-name>>${moduleId}.UserGroupsLoadFilter</filter-name>
|
||||||
|
<url-pattern>/*</url-pattern>
|
||||||
|
</filter-mapping>
|
||||||
|
</web-fragment>
|
@ -0,0 +1,43 @@
|
|||||||
|
<#--
|
||||||
|
Copyright 2019 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.
|
||||||
|
-->
|
||||||
|
<#function showRedirectForm>
|
||||||
|
<#local showRedirect = false />
|
||||||
|
<#local authConfig = config.scoped['Keycloak']['keycloak-auth-config'] />
|
||||||
|
<#if authConfig?? && authConfig.enhanceLoginForm??>
|
||||||
|
<#local showRedirect = authConfig.enhanceLoginForm />
|
||||||
|
</#if>
|
||||||
|
<#return showRedirect />
|
||||||
|
</#function>
|
||||||
|
|
||||||
|
<#if keycloakRedirectUriModel?? && showRedirectForm()>
|
||||||
|
<@markup id="oidc-redirect-button" target="form" action="after">
|
||||||
|
<#assign el = args.htmlid?html>
|
||||||
|
<#-- reuse CSS for consistent look&feel -->
|
||||||
|
<form action="${keycloakRedirectUriModel.baseUrl}" method="get" class="form-fields login">
|
||||||
|
<#list keycloakRedirectUriModel.parameters as parameter>
|
||||||
|
<input type="hidden" name="${parameter.name?html}" value="${parameter.value?html}" />
|
||||||
|
</#list>
|
||||||
|
<div class="form-field">
|
||||||
|
<#-- YUI style button without extra client-side JS code -->
|
||||||
|
<span class="yui-button yui-submit-button">
|
||||||
|
<span class="first-child">
|
||||||
|
<button type="submit" tabindex="0" id="${el}-oidcRedirect">${msg('button.oidc-sso')?html}</button>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</@markup>
|
||||||
|
</#if>
|
@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function main()
|
||||||
|
{
|
||||||
|
var keycloakRedirectUrl, keycloakRedirectUriModel, parameters, idx, parameter, parameterModel;
|
||||||
|
|
||||||
|
// redirect URL is already pre-constructed for a simple <a> redirect
|
||||||
|
// we want to support a form-based redirect for UI consistency, so need to deconstruct and even decode (parts of) URL
|
||||||
|
keycloakRedirectUrl = Packages.de.acosix.alfresco.keycloak.share.web.KeycloakAuthenticationFilter.getLoginRedirectUrl();
|
||||||
|
if (keycloakRedirectUrl !== null)
|
||||||
|
{
|
||||||
|
// make sure it is a JS string, not Java string
|
||||||
|
keycloakRedirectUrl = String(keycloakRedirectUrl);
|
||||||
|
keycloakRedirectUriModel = {
|
||||||
|
baseUrl : keycloakRedirectUrl.substring(0, keycloakRedirectUrl.indexOf('?')),
|
||||||
|
parameters : []
|
||||||
|
};
|
||||||
|
parameters = keycloakRedirectUrl.substring(keycloakRedirectUrl.indexOf('?') + 1).split(/&/);
|
||||||
|
for (idx = 0; idx < parameters.length; idx++)
|
||||||
|
{
|
||||||
|
parameter = parameters[idx];
|
||||||
|
if (parameter !== '' && parameter.indexOf('=') !== -1)
|
||||||
|
{
|
||||||
|
parameterModel = {
|
||||||
|
name : parameter.substring(0, parameter.indexOf('=')),
|
||||||
|
value : null
|
||||||
|
};
|
||||||
|
if (parameterModel.name === 'redirect_uri')
|
||||||
|
{
|
||||||
|
parameterModel.value = decodeURIComponent(parameter.substring(parameter.indexOf('=') + 1));
|
||||||
|
if (parameterModel.value.indexOf('?') !== -1)
|
||||||
|
{
|
||||||
|
parameterModel.value = parameterModel.value.substring(0, parameterModel.value.indexOf('?'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
parameterModel.value = parameter.substring(parameter.indexOf('=') + 1);
|
||||||
|
}
|
||||||
|
keycloakRedirectUriModel.parameters.push(parameterModel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
model.keycloakRedirectUriModel = keycloakRedirectUriModel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
@ -0,0 +1 @@
|
|||||||
|
button.oidc-sso=Login via SSO
|
@ -0,0 +1 @@
|
|||||||
|
button.oidc-sso=\u00dcber SSO anmelden
|
@ -0,0 +1,42 @@
|
|||||||
|
var userMenu, otherMenuGroup, logoutItem;
|
||||||
|
|
||||||
|
if (model.jsonModel && model.jsonModel.widgets
|
||||||
|
&& Packages.de.acosix.alfresco.keycloak.share.web.KeycloakAuthenticationFilter.isAuthenticatedByKeycloak())
|
||||||
|
{
|
||||||
|
// default Share does not show logout action for externally authenticated users
|
||||||
|
// but with Keycloak we can actually support it, so add it (if missing and user menu has not been removed)
|
||||||
|
|
||||||
|
userMenu = widgetUtils.findObject(model.jsonModel.widgets, 'id', 'HEADER_USER_MENU');
|
||||||
|
if (userMenu)
|
||||||
|
{
|
||||||
|
otherMenuGroup = widgetUtils.findObject(model.jsonModel.widgets, 'id', 'HEADER_USER_MENU_OTHER_GROUP');
|
||||||
|
if (!otherMenuGroup)
|
||||||
|
{
|
||||||
|
otherMenuGroup = {
|
||||||
|
id : 'HEADER_USER_MENU_OTHER_GROUP',
|
||||||
|
name : 'alfresco/menus/AlfMenuGroup',
|
||||||
|
config : {
|
||||||
|
label : 'group.other.label',
|
||||||
|
widgets : [],
|
||||||
|
additionalCssClasses : 'alf-menu-group-no-label'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
userMenu.config.widgets.push(otherMenuGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
logoutItem = widgetUtils.findObject(model.jsonModel.widgets, 'id', 'HEADER_USER_MENU_LOGOUT');
|
||||||
|
if (!logoutItem)
|
||||||
|
{
|
||||||
|
otherMenuGroup.config.widgets.push({
|
||||||
|
id : 'HEADER_USER_MENU_LOGOUT',
|
||||||
|
name : 'alfresco/header/AlfMenuItem',
|
||||||
|
config : {
|
||||||
|
id : 'HEADER_USER_MENU_LOGOUT',
|
||||||
|
label : 'logout.label',
|
||||||
|
iconClass : 'alf-user-logout-icon',
|
||||||
|
publishTopic : 'ALF_DOLOGOUT'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
0
share/src/main/templates/.gitkeep
Normal file
0
share/src/main/templates/.gitkeep
Normal file
0
share/src/main/webapp/.gitkeep
Normal file
0
share/src/main/webapp/.gitkeep
Normal file
47
share/src/test/docker/repository-it.xml
Normal file
47
share/src/test/docker/repository-it.xml
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
|
<!--
|
||||||
|
Copyright 2019 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.
|
||||||
|
-->
|
||||||
|
<assembly
|
||||||
|
xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 http://maven.apache.org/xsd/assembly-1.1.3.xsd">
|
||||||
|
<id>repository-it-docker</id>
|
||||||
|
<formats>
|
||||||
|
<format>dir</format>
|
||||||
|
</formats>
|
||||||
|
<includeBaseDirectory>false</includeBaseDirectory>
|
||||||
|
<dependencySets>
|
||||||
|
<!--
|
||||||
|
<dependencySet>
|
||||||
|
<outputDirectory>WEB-INF/lib</outputDirectory>
|
||||||
|
<includes>
|
||||||
|
</includes>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependencySet>
|
||||||
|
-->
|
||||||
|
<dependencySet>
|
||||||
|
<outputDirectory>WEB-INF/lib</outputDirectory>
|
||||||
|
<includes>
|
||||||
|
<include>de.acosix.alfresco.utility:de.acosix.alfresco.utility.common:*</include>
|
||||||
|
<include>de.acosix.alfresco.utility:de.acosix.alfresco.utility.core.repo.quartz1:*</include>
|
||||||
|
<include>de.acosix.alfresco.utility:de.acosix.alfresco.utility.core.repo.quartz2:*</include>
|
||||||
|
<include>de.acosix.alfresco.utility:de.acosix.alfresco.utility.core.repo:jar:installable:*</include>
|
||||||
|
<include>${project.groupId}:de.acosix.alfresco.keycloak.repo:jar:installable:*</include>
|
||||||
|
</includes>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependencySet>
|
||||||
|
</dependencySets>
|
||||||
|
</assembly>
|
65
share/src/test/docker/share-it.xml
Normal file
65
share/src/test/docker/share-it.xml
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
|
<!--
|
||||||
|
Copyright 2019 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.
|
||||||
|
-->
|
||||||
|
<assembly
|
||||||
|
xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 http://maven.apache.org/xsd/assembly-1.1.3.xsd">
|
||||||
|
<id>share-it-docker</id>
|
||||||
|
<formats>
|
||||||
|
<format>dir</format>
|
||||||
|
</formats>
|
||||||
|
<includeBaseDirectory>false</includeBaseDirectory>
|
||||||
|
<fileSets>
|
||||||
|
<fileSet>
|
||||||
|
<directory>${project.build.directory}</directory>
|
||||||
|
<outputDirectory>WEB-INF/lib</outputDirectory>
|
||||||
|
<includes>
|
||||||
|
<include>${project.artifactId}-${project.version}-installable.jar</include>
|
||||||
|
</includes>
|
||||||
|
</fileSet>
|
||||||
|
<fileSet>
|
||||||
|
<directory>${project.basedir}/src/test/resources</directory>
|
||||||
|
<outputDirectory>WEB-INF/classes</outputDirectory>
|
||||||
|
<includes>
|
||||||
|
<include>**/*.properties</include>
|
||||||
|
<include>**/*.xml</include>
|
||||||
|
</includes>
|
||||||
|
<filtered>true</filtered>
|
||||||
|
<lineEnding>lf</lineEnding>
|
||||||
|
</fileSet>
|
||||||
|
</fileSets>
|
||||||
|
<dependencySets>
|
||||||
|
<dependencySet>
|
||||||
|
<outputDirectory>WEB-INF/lib</outputDirectory>
|
||||||
|
<includes>
|
||||||
|
<include>org.keycloak:*</include>
|
||||||
|
<include>org.jboss.logging:*</include>
|
||||||
|
<include>org.bouncycastle:*</include>
|
||||||
|
<include>com.fasterxml.jackson.core:*</include>
|
||||||
|
</includes>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependencySet>
|
||||||
|
<dependencySet>
|
||||||
|
<outputDirectory>WEB-INF/lib</outputDirectory>
|
||||||
|
<includes>
|
||||||
|
<include>de.acosix.alfresco.utility:de.acosix.alfresco.utility.common:*</include>
|
||||||
|
<include>de.acosix.alfresco.utility:de.acosix.alfresco.utility.core.share:jar:installable:*</include>
|
||||||
|
</includes>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependencySet>
|
||||||
|
</dependencySets>
|
||||||
|
</assembly>
|
@ -0,0 +1,29 @@
|
|||||||
|
<?xml version='1.0' encoding='UTF-8' ?>
|
||||||
|
<!--
|
||||||
|
Copyright 2019 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.
|
||||||
|
-->
|
||||||
|
<alfresco-config>
|
||||||
|
|
||||||
|
<config evaluator="string-compare" condition="Test">
|
||||||
|
<keycloak-adapter-config>
|
||||||
|
<credentials>
|
||||||
|
<secret>secret</secret>
|
||||||
|
<provider>test</provider>
|
||||||
|
</credentials>
|
||||||
|
<allow-any-hostname>true</allow-any-hostname>
|
||||||
|
</keycloak-adapter-config>
|
||||||
|
</config>
|
||||||
|
|
||||||
|
</alfresco-config>
|
Loading…
x
Reference in New Issue
Block a user