├── .gitignore ├── LICENSE.txt ├── README.md ├── build.gradle ├── demo-realm.json ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── legacy-user-app ├── build.gradle └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── acme │ │ │ └── legacy │ │ │ └── app │ │ │ ├── Application.java │ │ │ ├── JerseyConfig.java │ │ │ ├── entity │ │ │ └── User.java │ │ │ ├── manager │ │ │ ├── AccessDeniedException.java │ │ │ ├── FederatedUserConverter.java │ │ │ ├── PasswordEncoder.java │ │ │ ├── SimplePasswordEncoder.java │ │ │ ├── SimpleUserManager.java │ │ │ └── UserManager.java │ │ │ ├── repository │ │ │ ├── JsonUserRepository.java │ │ │ └── UserRepository.java │ │ │ └── service │ │ │ └── LegacyUserService.java │ └── resources │ │ ├── application.yml │ │ ├── logback.xml │ │ └── users.json │ └── test │ └── java │ └── com │ └── acme │ └── legacy │ └── app │ └── manager │ └── SimplePasswordEncoderTest.java ├── portal-demo ├── build.gradle └── src │ └── main │ ├── java │ └── com │ │ └── acme │ │ └── portal │ │ ├── Application.java │ │ ├── PortalController.java │ │ └── SecurityConfig.java │ └── resources │ ├── application.yml │ ├── keycloak.json │ ├── logback.xml │ └── templates │ ├── home.ftl │ └── info.ftl ├── settings.gradle ├── user-migration-federation-provider ├── build.gradle └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── smartling │ │ │ └── keycloak │ │ │ └── provider │ │ │ ├── RemoteUserFederationProvider.java │ │ │ └── RemoteUserFederationProviderFactory.java │ └── resources │ │ └── META-INF │ │ └── services │ │ └── org.keycloak.models.UserFederationProviderFactory │ └── test │ └── java │ └── com │ └── smartling │ └── keycloak │ └── provider │ ├── RemoteUserFederationProviderFactoryTest.java │ └── RemoteUserFederationProviderTest.java └── user-model ├── build.gradle └── src └── main └── java └── com └── smartling └── keycloak └── federation ├── FederatedUserModel.java ├── FederatedUserService.java └── UserCredentialsDto.java /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .gradle 3 | build/ 4 | classes/ 5 | *.iml 6 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Keycloak Migration User Federation Provider 2 | 3 | Provides a [Keycloak][0] [user federation provider][1] for migrating users 4 | from a legacy system to Keycloak with zero downtime. For more information, 5 | read our post, [Migrate to Keycloak with Zero Downtime][3] on the Smartling Engineering Blog. 6 | 7 | ## Key features of the migration provider 8 | 9 | 1. Replace your legacy login system with Keycloak without downtime 10 | 2. Migrate users to Keycloak without having to reset their passwords 11 | 3. If something totally unexpected goes wrong with your new system launch, you can 12 | roll back with little to no user facing impact 13 | 14 | ## What's Provided 15 | 16 | 1. The user-migration-federation-provider 17 | 2. A sample legacy-user-app 18 | 3. A portal-demo service set up as a Keycloak client 19 | 20 | ## How it Works 21 | 22 | The Smartling user federation provider will import your users on demand into Keycloak's 23 | local storage, including username and password validation with the legacy system. 24 | For an in depth look at how on demand user migration works, read our post, 25 | [Migrate to Keycloak with Zero Downtime][3] on the Smartling Engineering Blog. 26 | 27 | ## Installing the Federation Provider 28 | 29 | ### Install Keycloak 30 | 31 | Download [Keycloak 1.7.0.Final][2] and unzip / un-tar it. 32 | 33 | ### Clone this Repository 34 | 35 | Clone this repository to your local workstation. You will have to edit `gradle.properties` 36 | to point to the location where you installed Keycloak 1.7.0.Final. This location should 37 | not contain spaces in the path. 38 | 39 | ### Install the Federation Provider 40 | 41 | To install the Federation and configure Keycloak: 42 | 43 | From this repository, run: 44 | 45 | ``` bash 46 | ./gradlew deployModules 47 | cd 48 | ``` 49 | 50 | Open `standalone/configuration/keycloak-server.json` with your preferred text editor. 51 | Update the `"providers"` configuration to include `module:net.smartling.provider.federation`: 52 | 53 | ``` json 54 | "providers": [ 55 | "classpath:${jboss.server.config.dir}/providers/*", 56 | "module:net.smartling.provider.federation" 57 | ], 58 | ``` 59 | 60 | The federation provider can now be used from Keycloak. 61 | 62 | ## Running the Provided Examples 63 | 64 | This project contains two sample applications as noted above. The first behaves as a legacy 65 | user service maintaining user information for the sample company. The second is a portal 66 | service that simply displays information about the logged in user. 67 | 68 | The steps below outline how use the examples. 69 | 70 | ### Start Keycloak & Import the Example Realm 71 | 72 | Start Keycloak, if it isn't already running, and log into the master realm as `admin`. 73 | Import the included `demo-realm.json` file. 74 | 75 | ### Start the Legacy User Application 76 | 77 | From the location you cloned this repository into, start the legacy user service. 78 | 79 | ``` bash 80 | $ ./gradlew :legacy-user-app:bootRun 81 | ``` 82 | 83 | ### Start the Portal Application 84 | 85 | From the location you cloned this repository into, start the demo portal. 86 | 87 | ``` bash 88 | $ ./gradlew :portal-demo:bootRun 89 | ``` 90 | 91 | ### Access the Portal 92 | 93 | Open http://localhost:9082 in your web browser. You can log in as any of the sample users 94 | in the legacy user service. 95 | 96 | The included sample users are: 97 | 98 | * craig@007.com 99 | * green@007.com 100 | * mendes@007.com 101 | 102 | For simplicity, all of the user's passwords are `Martini4`. 103 | 104 | [0]: http://keycloak.jboss.org/ 105 | [1]: http://keycloak.github.io/docs/userguide/keycloak-server/html/user_federation.html 106 | [2]: http://downloads.jboss.org/keycloak/1.7.0.Final/keycloak-1.7.0.Final.tar.gz 107 | [3]: http://tech.smartling.com/migrate-to-keycloak-with-zero-downtime/ 108 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Smartling, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | plugins { 18 | id "com.github.zhurlik.jbossmodules" version "0.14" 19 | } 20 | 21 | group = 'com.smartling.keycloak.provider' 22 | description = 'User Migration Federation Provider' 23 | version = '1.0.0' + (project.hasProperty('release') && project.ext.release ? '-RELEASE' : '-SNAPSHOT') 24 | 25 | apply plugin: "com.github.zhurlik.jbossmodules" 26 | 27 | ext { 28 | keycloakVersion = '1.7.0.Final' 29 | } 30 | 31 | repositories { 32 | jcenter() 33 | } 34 | 35 | jbossrepos { 36 | keycloak { 37 | home = keycloakHome 38 | } 39 | } 40 | 41 | dependencies { 42 | jbossmodules project('user-model') 43 | jbossmodules project('user-migration-federation-provider') 44 | } 45 | 46 | modules { 47 | 48 | userMigration { 49 | moduleName = 'net.smartling.provider.federation' 50 | resources = ["user-migration-federation-provider-${version}.jar", "user-model-${version}.jar"] 51 | dependencies = [ 52 | [name: 'org.keycloak.keycloak-core'], 53 | [name: 'org.keycloak.keycloak-model-api'], 54 | [name: 'org.jboss.resteasy.resteasy-jaxrs'], 55 | [name: 'org.jboss.logging'], 56 | [name: 'org.apache.httpcomponents'], 57 | [name: 'javax.ws.rs.api'] 58 | ] 59 | } 60 | } 61 | 62 | subprojects { 63 | apply plugin: 'java' 64 | version = rootProject.version 65 | repositories { 66 | jcenter() 67 | } 68 | } 69 | 70 | makeModules.dependsOn subprojects.jar 71 | 72 | task moduleZip(type: Zip) { 73 | description 'Packages Wildfly modules for distribution' 74 | dependsOn makeModules 75 | from 'build/install/keycloak/' 76 | } 77 | 78 | task wrapper(type: Wrapper) { 79 | gradleVersion = '2.7' 80 | } 81 | -------------------------------------------------------------------------------- /demo-realm.json: -------------------------------------------------------------------------------- 1 | { 2 | "id" : "Eon", 3 | "realm" : "Eon", 4 | "notBefore" : 0, 5 | "revokeRefreshToken" : false, 6 | "accessTokenLifespan" : 300, 7 | "accessTokenLifespanForImplicitFlow" : 900, 8 | "ssoSessionIdleTimeout" : 1800, 9 | "ssoSessionMaxLifespan" : 36000, 10 | "offlineSessionIdleTimeout" : 2592000, 11 | "accessCodeLifespan" : 60, 12 | "accessCodeLifespanUserAction" : 300, 13 | "accessCodeLifespanLogin" : 1800, 14 | "enabled" : true, 15 | "sslRequired" : "external", 16 | "registrationAllowed" : false, 17 | "registrationEmailAsUsername" : false, 18 | "rememberMe" : false, 19 | "verifyEmail" : false, 20 | "resetPasswordAllowed" : false, 21 | "editUsernameAllowed" : false, 22 | "bruteForceProtected" : false, 23 | "maxFailureWaitSeconds" : 900, 24 | "minimumQuickLoginWaitSeconds" : 60, 25 | "waitIncrementSeconds" : 60, 26 | "quickLoginCheckMilliSeconds" : 1000, 27 | "maxDeltaTimeSeconds" : 43200, 28 | "failureFactor" : 30, 29 | "privateKey" : "MIIEpgIBAAKCAQEApDc8DuKDzX3iw8ipVhOfyCc1K6tYbm7Mi5BmbcXhrLCpnxdQ9AzNHldV+dMiJVPQd4hl3dlrLitdymrkWPX3E3sLomO2W8QUbz0QYNbuYxtBisNMEH6/41l9REr0xEb0JyHKqVT4dsv6a10xLxZ+/zyGFOk30Y/XvNf9/uikAEc4IKIJrxWxIHR+tpM/GzW1rbcaVp+dnqHyjwHnEU+2aoBTqC0Wq6UTJUPxnJ9IlV2evrBhFe9+oFOUFr10wj5xMEqEiNhbnXJ5jVytgEIf9M9uC8URa1haDLl4TnxICQyAprX4C/Pq3ybuckLGo8bGCS/GlgxkV0h/SfO+uUYULQIDAQABAoIBAQCad1JI9gzgqH87hm6yhvcPQMIk96HRDuvcg9G1hCNJW0vSWA5P4hK3bZNz0YQW9IXtSiH30mGdJjGtC2sLuyHT8zP4Zi7Mlt5Z3Gihjsrh8mtuaVINWgyJgE3yhUdDsTJL9N869LBannhJsXloP5Lp5zaASPe2acWW3t0OgCp0DwWQR4q607UaOH5ISxR04iJsXmvQOPtmPxGfL6+E8gOmqSMUTnco9L6yP3+kfv7y5a6sTbb1HsuFJ9/IkcMNVV+A6jJmddgLWkSb0GY6cTfnTbC6zUDatOqh+b/78AKJnoXgyPOzwz8vNR1qkglYLLVnmToDpu9R/2Tf0r951yiBAoGBAOYlj2GjfI9EeynoLs0bZLHXTnv4iciIYwvHqpoYJtlxUWxtGLKa+LNboYC4In63n0OLShXF1L/1XnLMa95lWOcMBuSWappSb+D4WlkynfVG0PT/0pFfiR0CvIc7zsg/KksVb2yjjLYbBEXma6L0jJTdt2H4vAHqq7i7ew8Tie1dAoGBALapqo0gLWbvsV7WAtpMuyqH3DSPrq4HhsT3Sp2CSx7uyA63pbBz/aDQwoRpEtZ3TzSp025szyunEJWGFByZkyMtZsSlbXQ2j4xwaTNtOrXk4N0XrthZqGV7p8Tjmi27YLqTDKt/TCSVzl8pCERGOqivOdRT9LzZvDdwjF9/PIURAoGBAJJKabuMqg5/XzKiLa2ergEFdRQERcC8QQkp392XYIpzJ2ieaEaPj2qi4iPp57NYkNnkXjE80SE7nM+n9SEmlr42vOLsYdK6d/cupm9wZ0uTuhshyf0yFvvj02a6s5RB4mZbt7n8s+LFhY/RCbZJcFroHDsgWpF1U7ZXJb/NkazZAoGBAIqb1wq+RxWcf4jKd2G15jVQ4R2VhmUS2wat7JX3YA+5/F+Gphlu+yBEKccWgK/z20vILuPVd6PVY3VDSBGnzApekmRYb+VG3ckhKANZOLr8UlITfZ848dsIaXezirR3QuBxY6TjtSDx+KcnWNmOPUbxqL1hdA62Xufm5O42e/MxAoGBAJmjI6nlcsoqXhZRlUDdZ0uqxG0nalo0x8dw9AWG0PWdbzeiOqdv28oyHI4H5/+fTS2zY3XHHT3OlJtyflM/zVLakCuMV+0PoFlRsrRZi0BWuXY/80vOG7r5isHyScnnQCjSKEuuF/CY5JDUO10qpF/VnfssQo++Gd6RJyHePXyw", 30 | "publicKey" : "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApDc8DuKDzX3iw8ipVhOfyCc1K6tYbm7Mi5BmbcXhrLCpnxdQ9AzNHldV+dMiJVPQd4hl3dlrLitdymrkWPX3E3sLomO2W8QUbz0QYNbuYxtBisNMEH6/41l9REr0xEb0JyHKqVT4dsv6a10xLxZ+/zyGFOk30Y/XvNf9/uikAEc4IKIJrxWxIHR+tpM/GzW1rbcaVp+dnqHyjwHnEU+2aoBTqC0Wq6UTJUPxnJ9IlV2evrBhFe9+oFOUFr10wj5xMEqEiNhbnXJ5jVytgEIf9M9uC8URa1haDLl4TnxICQyAprX4C/Pq3ybuckLGo8bGCS/GlgxkV0h/SfO+uUYULQIDAQAB", 31 | "certificate" : "MIIClTCCAX0CBgFSMV4TuTANBgkqhkiG9w0BAQsFADAOMQwwCgYDVQQDDANFb24wHhcNMTYwMTExMTU0NDU1WhcNMjYwMTExMTU0NjM1WjAOMQwwCgYDVQQDDANFb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCkNzwO4oPNfeLDyKlWE5/IJzUrq1hubsyLkGZtxeGssKmfF1D0DM0eV1X50yIlU9B3iGXd2WsuK13KauRY9fcTewuiY7ZbxBRvPRBg1u5jG0GKw0wQfr/jWX1ESvTERvQnIcqpVPh2y/prXTEvFn7/PIYU6TfRj9e81/3+6KQARzggogmvFbEgdH62kz8bNbWttxpWn52eofKPAecRT7ZqgFOoLRarpRMlQ/Gcn0iVXZ6+sGEV736gU5QWvXTCPnEwSoSI2FudcnmNXK2AQh/0z24LxRFrWFoMuXhOfEgJDICmtfgL8+rfJu5yQsajxsYJL8aWDGRXSH9J8765RhQtAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAARIKmVSzLpPtpAUAg4eZmDIwByKbHBNU2tCbiCv6l1nMRUVOQ1Ab0GsFOwqx0Zv358uAwa+doMEYRLKAVMp3GABuHhNNVeFhEI0RQ01Zse3zyE7qttxxHdIRv5zPgU6LU1ZPWPNVgVOB1ctOSxMSCH8m8Ujcs2iHNyUzqFV96vvVYXV5Qohi2ml/mR18nKaCF7LCvFmYYD+z24MhAGhwZW7M2NvoNch8LUeZLJUlLdRqVvuolh4BJQ8MPAP6ZCtHfm8suD8+bIRmpFFF7mVj0uA9HMWvs8hYPx28dEzlStna9wi8scTvq9U8iz6VbjUCdhXTk0RTzNwSvnSDnXumYQ=", 32 | "codeSecret" : "3da651ac-7f27-4347-abc3-48d816177d98", 33 | "roles" : { 34 | "realm" : [ { 35 | "id" : "c7928a01-0c9e-4cdd-b60f-5e94782a0b66", 36 | "name" : "offline_access", 37 | "description" : "${role_offline-access}", 38 | "scopeParamRequired" : true, 39 | "composite" : false 40 | } ], 41 | "client" : { 42 | "demo-portal" : [ ], 43 | "security-admin-console" : [ ], 44 | "account" : [ { 45 | "id" : "4aa9b96d-4433-498d-bc6c-06074ac59320", 46 | "name" : "view-profile", 47 | "description" : "${role_view-profile}", 48 | "scopeParamRequired" : false, 49 | "composite" : false 50 | }, { 51 | "id" : "33a7f368-dffb-418d-8a02-d3c4d4f35fa0", 52 | "name" : "manage-account", 53 | "description" : "${role_manage-account}", 54 | "scopeParamRequired" : false, 55 | "composite" : false 56 | } ], 57 | "realm-management" : [ { 58 | "id" : "263db2a0-f127-450b-940a-73374ecd0270", 59 | "name" : "view-events", 60 | "description" : "${role_view-events}", 61 | "scopeParamRequired" : false, 62 | "composite" : false 63 | }, { 64 | "id" : "f18f23db-420f-47de-8b14-2ddd84817d90", 65 | "name" : "view-users", 66 | "description" : "${role_view-users}", 67 | "scopeParamRequired" : false, 68 | "composite" : false 69 | }, { 70 | "id" : "d9297044-d2e5-4187-86a4-a8cf2daa9f5a", 71 | "name" : "impersonation", 72 | "description" : "${role_impersonation}", 73 | "scopeParamRequired" : false, 74 | "composite" : false 75 | }, { 76 | "id" : "71f1950d-a8cb-414f-89ac-7a8ced0d728e", 77 | "name" : "manage-realm", 78 | "description" : "${role_manage-realm}", 79 | "scopeParamRequired" : false, 80 | "composite" : false 81 | }, { 82 | "id" : "72dd759e-6965-4b35-9348-fe9d7209920b", 83 | "name" : "create-client", 84 | "description" : "${role_create-client}", 85 | "scopeParamRequired" : false, 86 | "composite" : false 87 | }, { 88 | "id" : "d7f36b47-a0c2-4eea-8c03-a537828ff3f2", 89 | "name" : "manage-users", 90 | "description" : "${role_manage-users}", 91 | "scopeParamRequired" : false, 92 | "composite" : false 93 | }, { 94 | "id" : "af94630c-2ba6-4f97-bfcd-2158c8ea3b35", 95 | "name" : "view-realm", 96 | "description" : "${role_view-realm}", 97 | "scopeParamRequired" : false, 98 | "composite" : false 99 | }, { 100 | "id" : "67b438e2-0fca-4174-b4de-b34bb70c5e3a", 101 | "name" : "view-identity-providers", 102 | "description" : "${role_view-identity-providers}", 103 | "scopeParamRequired" : false, 104 | "composite" : false 105 | }, { 106 | "id" : "cafa24c4-7790-4435-bc48-b0a3af8f7ac8", 107 | "name" : "manage-clients", 108 | "description" : "${role_manage-clients}", 109 | "scopeParamRequired" : false, 110 | "composite" : false 111 | }, { 112 | "id" : "34aadd1e-915f-4f77-bf43-6691fa3cab7f", 113 | "name" : "manage-events", 114 | "description" : "${role_manage-events}", 115 | "scopeParamRequired" : false, 116 | "composite" : false 117 | }, { 118 | "id" : "4feef576-1e02-4d9c-98bf-a2e2436f3e4f", 119 | "name" : "realm-admin", 120 | "description" : "${role_realm-admin}", 121 | "scopeParamRequired" : false, 122 | "composite" : true, 123 | "composites" : { 124 | "client" : { 125 | "realm-management" : [ "view-events", "view-users", "impersonation", "manage-realm", "create-client", "manage-users", "view-realm", "manage-clients", "view-identity-providers", "manage-events", "manage-identity-providers", "view-clients" ] 126 | } 127 | } 128 | }, { 129 | "id" : "91be1609-33c1-4d8e-b72b-0961db1c73b7", 130 | "name" : "view-clients", 131 | "description" : "${role_view-clients}", 132 | "scopeParamRequired" : false, 133 | "composite" : false 134 | }, { 135 | "id" : "f22297dc-bf20-4358-8aae-4100aeb41c17", 136 | "name" : "manage-identity-providers", 137 | "description" : "${role_manage-identity-providers}", 138 | "scopeParamRequired" : false, 139 | "composite" : false 140 | } ], 141 | "broker" : [ { 142 | "id" : "3f12615c-3aae-45bb-8904-214cd11f1fa7", 143 | "name" : "read-token", 144 | "description" : "${role_read-token}", 145 | "scopeParamRequired" : false, 146 | "composite" : false 147 | } ], 148 | "admin-cli" : [ ] 149 | } 150 | }, 151 | "groups" : [ ], 152 | "defaultRoles" : [ "offline_access" ], 153 | "requiredCredentials" : [ "password" ], 154 | "otpPolicyType" : "totp", 155 | "otpPolicyAlgorithm" : "HmacSHA1", 156 | "otpPolicyInitialCounter" : 0, 157 | "otpPolicyDigits" : 6, 158 | "otpPolicyLookAheadWindow" : 1, 159 | "otpPolicyPeriod" : 30, 160 | "clientScopeMappings" : { 161 | "realm-management" : [ { 162 | "client" : "security-admin-console", 163 | "roles" : [ "realm-admin" ] 164 | }, { 165 | "client" : "admin-cli", 166 | "roles" : [ "realm-admin" ] 167 | } ] 168 | }, 169 | "clients" : [ { 170 | "id" : "5b0d3e4d-2d62-4c19-aae9-d0166101fb35", 171 | "clientId" : "demo-portal", 172 | "name" : "demo-portal", 173 | "description" : "Demo User Portal", 174 | "rootUrl" : "http://localhost:9082/", 175 | "surrogateAuthRequired" : false, 176 | "enabled" : true, 177 | "clientAuthenticatorType" : "client-secret", 178 | "secret" : "06694847-9253-4d4e-bd8f-9920d15d8e03", 179 | "redirectUris" : [ "http://localhost:9082/*" ], 180 | "webOrigins" : [ "http://localhost:9082" ], 181 | "notBefore" : 0, 182 | "bearerOnly" : false, 183 | "consentRequired" : false, 184 | "standardFlowEnabled" : true, 185 | "implicitFlowEnabled" : false, 186 | "directAccessGrantsEnabled" : false, 187 | "serviceAccountsEnabled" : false, 188 | "publicClient" : false, 189 | "frontchannelLogout" : false, 190 | "protocol" : "openid-connect", 191 | "attributes" : { 192 | "saml.signature.algorithm" : "RSA_SHA256", 193 | "saml_name_id_format" : "username", 194 | "saml.multivalued.roles" : "false", 195 | "saml_signature_canonicalization_method" : "http://www.w3.org/2001/10/xml-exc-c14n#", 196 | "saml.encrypt" : "false", 197 | "saml.server.signature" : "false", 198 | "saml.assertion.signature" : "false", 199 | "saml.force.post.binding" : "false", 200 | "saml.client.signature" : "false", 201 | "saml_force_name_id_format" : "false", 202 | "saml.authnstatement" : "true" 203 | }, 204 | "fullScopeAllowed" : true, 205 | "nodeReRegistrationTimeout" : -1, 206 | "protocolMappers" : [ { 207 | "id" : "965ad595-13dc-48ce-86e5-3f96f28054a7", 208 | "name" : "role list", 209 | "protocol" : "saml", 210 | "protocolMapper" : "saml-role-list-mapper", 211 | "consentRequired" : false, 212 | "config" : { 213 | "single" : "false", 214 | "attribute.nameformat" : "Basic", 215 | "attribute.name" : "Role" 216 | } 217 | }, { 218 | "id" : "35ec5cab-1973-4b32-bb6d-cbbcd0c2be1d", 219 | "name" : "full name", 220 | "protocol" : "openid-connect", 221 | "protocolMapper" : "oidc-full-name-mapper", 222 | "consentRequired" : true, 223 | "consentText" : "${fullName}", 224 | "config" : { 225 | "access.token.claim" : "true", 226 | "id.token.claim" : "true" 227 | } 228 | }, { 229 | "id" : "7dbcaaf5-6c4a-4c89-aeae-6b32b20e305e", 230 | "name" : "email", 231 | "protocol" : "openid-connect", 232 | "protocolMapper" : "oidc-usermodel-property-mapper", 233 | "consentRequired" : true, 234 | "consentText" : "${email}", 235 | "config" : { 236 | "claim.name" : "email", 237 | "jsonType.label" : "String", 238 | "user.attribute" : "email", 239 | "access.token.claim" : "true", 240 | "id.token.claim" : "true" 241 | } 242 | }, { 243 | "id" : "c0d2ae80-7d29-4e6e-860e-d93949e95047", 244 | "name" : "title", 245 | "protocol" : "openid-connect", 246 | "protocolMapper" : "oidc-usermodel-attribute-mapper", 247 | "consentRequired" : false, 248 | "config" : { 249 | "claim.name" : "title", 250 | "jsonType.label" : "String", 251 | "user.attribute" : "title", 252 | "access.token.claim" : "true", 253 | "id.token.claim" : "true" 254 | } 255 | }, { 256 | "id" : "84b65c0a-a58a-48ed-8a1a-01e57a66d565", 257 | "name" : "username", 258 | "protocol" : "openid-connect", 259 | "protocolMapper" : "oidc-usermodel-property-mapper", 260 | "consentRequired" : true, 261 | "consentText" : "${username}", 262 | "config" : { 263 | "claim.name" : "preferred_username", 264 | "jsonType.label" : "String", 265 | "user.attribute" : "username", 266 | "access.token.claim" : "true", 267 | "id.token.claim" : "true" 268 | } 269 | }, { 270 | "id" : "736cb0f6-8e2b-43bf-a799-fb8de6dda782", 271 | "name" : "given name", 272 | "protocol" : "openid-connect", 273 | "protocolMapper" : "oidc-usermodel-property-mapper", 274 | "consentRequired" : true, 275 | "consentText" : "${givenName}", 276 | "config" : { 277 | "claim.name" : "given_name", 278 | "jsonType.label" : "String", 279 | "user.attribute" : "firstName", 280 | "access.token.claim" : "true", 281 | "id.token.claim" : "true" 282 | } 283 | }, { 284 | "id" : "49f4ad2f-8831-4020-9058-be18362152ec", 285 | "name" : "family name", 286 | "protocol" : "openid-connect", 287 | "protocolMapper" : "oidc-usermodel-property-mapper", 288 | "consentRequired" : true, 289 | "consentText" : "${familyName}", 290 | "config" : { 291 | "claim.name" : "family_name", 292 | "jsonType.label" : "String", 293 | "user.attribute" : "lastName", 294 | "access.token.claim" : "true", 295 | "id.token.claim" : "true" 296 | } 297 | } ] 298 | }, { 299 | "id" : "b976f3c2-ccda-43bd-bfe4-da412db9c977", 300 | "clientId" : "security-admin-console", 301 | "name" : "${client_security-admin-console}", 302 | "baseUrl" : "/auth/admin/Eon/console/index.html", 303 | "surrogateAuthRequired" : false, 304 | "enabled" : true, 305 | "clientAuthenticatorType" : "client-secret", 306 | "secret" : "9804d05c-69b0-49d6-94e7-75248716c5bd", 307 | "redirectUris" : [ "/auth/admin/Eon/console/*" ], 308 | "webOrigins" : [ ], 309 | "notBefore" : 0, 310 | "bearerOnly" : false, 311 | "consentRequired" : false, 312 | "standardFlowEnabled" : true, 313 | "implicitFlowEnabled" : false, 314 | "directAccessGrantsEnabled" : false, 315 | "serviceAccountsEnabled" : false, 316 | "publicClient" : true, 317 | "frontchannelLogout" : false, 318 | "attributes" : { }, 319 | "fullScopeAllowed" : false, 320 | "nodeReRegistrationTimeout" : 0, 321 | "protocolMappers" : [ { 322 | "id" : "9230b0cc-044b-4bac-b1c5-2eab745cb51b", 323 | "name" : "locale", 324 | "protocol" : "openid-connect", 325 | "protocolMapper" : "oidc-usermodel-attribute-mapper", 326 | "consentRequired" : false, 327 | "consentText" : "${locale}", 328 | "config" : { 329 | "claim.name" : "locale", 330 | "jsonType.label" : "String", 331 | "user.attribute" : "locale", 332 | "access.token.claim" : "true", 333 | "id.token.claim" : "true" 334 | } 335 | }, { 336 | "id" : "5dab6f3c-0f86-4e39-88a5-7132e6ae2c37", 337 | "name" : "username", 338 | "protocol" : "openid-connect", 339 | "protocolMapper" : "oidc-usermodel-property-mapper", 340 | "consentRequired" : true, 341 | "consentText" : "${username}", 342 | "config" : { 343 | "claim.name" : "preferred_username", 344 | "jsonType.label" : "String", 345 | "user.attribute" : "username", 346 | "access.token.claim" : "true", 347 | "id.token.claim" : "true" 348 | } 349 | }, { 350 | "id" : "0bcdd980-3367-43f2-ba35-2f8cf9bc4109", 351 | "name" : "email", 352 | "protocol" : "openid-connect", 353 | "protocolMapper" : "oidc-usermodel-property-mapper", 354 | "consentRequired" : true, 355 | "consentText" : "${email}", 356 | "config" : { 357 | "claim.name" : "email", 358 | "jsonType.label" : "String", 359 | "user.attribute" : "email", 360 | "access.token.claim" : "true", 361 | "id.token.claim" : "true" 362 | } 363 | }, { 364 | "id" : "b4acb7de-a0ca-4aff-9c24-52bc10f8c354", 365 | "name" : "family name", 366 | "protocol" : "openid-connect", 367 | "protocolMapper" : "oidc-usermodel-property-mapper", 368 | "consentRequired" : true, 369 | "consentText" : "${familyName}", 370 | "config" : { 371 | "claim.name" : "family_name", 372 | "jsonType.label" : "String", 373 | "user.attribute" : "lastName", 374 | "access.token.claim" : "true", 375 | "id.token.claim" : "true" 376 | } 377 | }, { 378 | "id" : "095507b7-35c1-4c0d-86bc-7f759a856980", 379 | "name" : "role list", 380 | "protocol" : "saml", 381 | "protocolMapper" : "saml-role-list-mapper", 382 | "consentRequired" : false, 383 | "config" : { 384 | "single" : "false", 385 | "attribute.nameformat" : "Basic", 386 | "attribute.name" : "Role" 387 | } 388 | }, { 389 | "id" : "60bb9131-875d-4fef-ae8f-c76c5c16ffd7", 390 | "name" : "given name", 391 | "protocol" : "openid-connect", 392 | "protocolMapper" : "oidc-usermodel-property-mapper", 393 | "consentRequired" : true, 394 | "consentText" : "${givenName}", 395 | "config" : { 396 | "claim.name" : "given_name", 397 | "jsonType.label" : "String", 398 | "user.attribute" : "firstName", 399 | "access.token.claim" : "true", 400 | "id.token.claim" : "true" 401 | } 402 | }, { 403 | "id" : "49cc549e-0608-4f00-9592-4014630e1754", 404 | "name" : "full name", 405 | "protocol" : "openid-connect", 406 | "protocolMapper" : "oidc-full-name-mapper", 407 | "consentRequired" : true, 408 | "consentText" : "${fullName}", 409 | "config" : { 410 | "access.token.claim" : "true", 411 | "id.token.claim" : "true" 412 | } 413 | } ] 414 | }, { 415 | "id" : "6ce9c851-9dba-4d1e-a264-5883d726e145", 416 | "clientId" : "account", 417 | "name" : "${client_account}", 418 | "baseUrl" : "/auth/realms/Eon/account", 419 | "surrogateAuthRequired" : false, 420 | "enabled" : true, 421 | "clientAuthenticatorType" : "client-secret", 422 | "secret" : "4f42f72e-46b3-46c7-8f69-af71d4c07e86", 423 | "defaultRoles" : [ "view-profile", "manage-account" ], 424 | "redirectUris" : [ "/auth/realms/Eon/account/*" ], 425 | "webOrigins" : [ ], 426 | "notBefore" : 0, 427 | "bearerOnly" : false, 428 | "consentRequired" : false, 429 | "standardFlowEnabled" : true, 430 | "implicitFlowEnabled" : false, 431 | "directAccessGrantsEnabled" : false, 432 | "serviceAccountsEnabled" : false, 433 | "publicClient" : false, 434 | "frontchannelLogout" : false, 435 | "attributes" : { }, 436 | "fullScopeAllowed" : false, 437 | "nodeReRegistrationTimeout" : 0, 438 | "protocolMappers" : [ { 439 | "id" : "9804a0ab-9e72-4ed2-a474-9f3b5f81b99e", 440 | "name" : "role list", 441 | "protocol" : "saml", 442 | "protocolMapper" : "saml-role-list-mapper", 443 | "consentRequired" : false, 444 | "config" : { 445 | "single" : "false", 446 | "attribute.nameformat" : "Basic", 447 | "attribute.name" : "Role" 448 | } 449 | }, { 450 | "id" : "4c55c06a-1569-4722-8a76-cb75e2c9eacf", 451 | "name" : "given name", 452 | "protocol" : "openid-connect", 453 | "protocolMapper" : "oidc-usermodel-property-mapper", 454 | "consentRequired" : true, 455 | "consentText" : "${givenName}", 456 | "config" : { 457 | "claim.name" : "given_name", 458 | "jsonType.label" : "String", 459 | "user.attribute" : "firstName", 460 | "access.token.claim" : "true", 461 | "id.token.claim" : "true" 462 | } 463 | }, { 464 | "id" : "9aeaf949-4146-4a6f-bf0d-00a1ceb6ab5b", 465 | "name" : "family name", 466 | "protocol" : "openid-connect", 467 | "protocolMapper" : "oidc-usermodel-property-mapper", 468 | "consentRequired" : true, 469 | "consentText" : "${familyName}", 470 | "config" : { 471 | "claim.name" : "family_name", 472 | "jsonType.label" : "String", 473 | "user.attribute" : "lastName", 474 | "access.token.claim" : "true", 475 | "id.token.claim" : "true" 476 | } 477 | }, { 478 | "id" : "cd2ad883-2297-462d-86f4-32be5616d4c5", 479 | "name" : "username", 480 | "protocol" : "openid-connect", 481 | "protocolMapper" : "oidc-usermodel-property-mapper", 482 | "consentRequired" : true, 483 | "consentText" : "${username}", 484 | "config" : { 485 | "claim.name" : "preferred_username", 486 | "jsonType.label" : "String", 487 | "user.attribute" : "username", 488 | "access.token.claim" : "true", 489 | "id.token.claim" : "true" 490 | } 491 | }, { 492 | "id" : "72cee00b-64c6-4684-bdf9-c35d72a86dd8", 493 | "name" : "email", 494 | "protocol" : "openid-connect", 495 | "protocolMapper" : "oidc-usermodel-property-mapper", 496 | "consentRequired" : true, 497 | "consentText" : "${email}", 498 | "config" : { 499 | "claim.name" : "email", 500 | "jsonType.label" : "String", 501 | "user.attribute" : "email", 502 | "access.token.claim" : "true", 503 | "id.token.claim" : "true" 504 | } 505 | }, { 506 | "id" : "066f8c82-9cb2-4cf9-9b99-427264fcebe3", 507 | "name" : "full name", 508 | "protocol" : "openid-connect", 509 | "protocolMapper" : "oidc-full-name-mapper", 510 | "consentRequired" : true, 511 | "consentText" : "${fullName}", 512 | "config" : { 513 | "access.token.claim" : "true", 514 | "id.token.claim" : "true" 515 | } 516 | } ] 517 | }, { 518 | "id" : "50f42860-3ccf-4243-89c1-98b8c17dcbd3", 519 | "clientId" : "broker", 520 | "name" : "${client_broker}", 521 | "surrogateAuthRequired" : false, 522 | "enabled" : true, 523 | "clientAuthenticatorType" : "client-secret", 524 | "secret" : "ae4a1913-2c08-48d0-b649-1f9674ca4136", 525 | "redirectUris" : [ ], 526 | "webOrigins" : [ ], 527 | "notBefore" : 0, 528 | "bearerOnly" : false, 529 | "consentRequired" : false, 530 | "standardFlowEnabled" : true, 531 | "implicitFlowEnabled" : false, 532 | "directAccessGrantsEnabled" : false, 533 | "serviceAccountsEnabled" : false, 534 | "publicClient" : false, 535 | "frontchannelLogout" : false, 536 | "attributes" : { }, 537 | "fullScopeAllowed" : false, 538 | "nodeReRegistrationTimeout" : 0, 539 | "protocolMappers" : [ { 540 | "id" : "dae01503-560d-4f16-9ae6-0803bc357dcc", 541 | "name" : "role list", 542 | "protocol" : "saml", 543 | "protocolMapper" : "saml-role-list-mapper", 544 | "consentRequired" : false, 545 | "config" : { 546 | "single" : "false", 547 | "attribute.nameformat" : "Basic", 548 | "attribute.name" : "Role" 549 | } 550 | }, { 551 | "id" : "e14967db-141b-44f9-b116-3ad56bb44e89", 552 | "name" : "email", 553 | "protocol" : "openid-connect", 554 | "protocolMapper" : "oidc-usermodel-property-mapper", 555 | "consentRequired" : true, 556 | "consentText" : "${email}", 557 | "config" : { 558 | "claim.name" : "email", 559 | "jsonType.label" : "String", 560 | "user.attribute" : "email", 561 | "access.token.claim" : "true", 562 | "id.token.claim" : "true" 563 | } 564 | }, { 565 | "id" : "6013a1dc-b64e-44df-a2a3-dd55026b6c93", 566 | "name" : "full name", 567 | "protocol" : "openid-connect", 568 | "protocolMapper" : "oidc-full-name-mapper", 569 | "consentRequired" : true, 570 | "consentText" : "${fullName}", 571 | "config" : { 572 | "access.token.claim" : "true", 573 | "id.token.claim" : "true" 574 | } 575 | }, { 576 | "id" : "dde474bd-d165-4478-afee-948a2a9efe0f", 577 | "name" : "family name", 578 | "protocol" : "openid-connect", 579 | "protocolMapper" : "oidc-usermodel-property-mapper", 580 | "consentRequired" : true, 581 | "consentText" : "${familyName}", 582 | "config" : { 583 | "claim.name" : "family_name", 584 | "jsonType.label" : "String", 585 | "user.attribute" : "lastName", 586 | "access.token.claim" : "true", 587 | "id.token.claim" : "true" 588 | } 589 | }, { 590 | "id" : "d5110aca-db88-4062-a1b6-d9131c7cd85b", 591 | "name" : "username", 592 | "protocol" : "openid-connect", 593 | "protocolMapper" : "oidc-usermodel-property-mapper", 594 | "consentRequired" : true, 595 | "consentText" : "${username}", 596 | "config" : { 597 | "claim.name" : "preferred_username", 598 | "jsonType.label" : "String", 599 | "user.attribute" : "username", 600 | "access.token.claim" : "true", 601 | "id.token.claim" : "true" 602 | } 603 | }, { 604 | "id" : "19de6e7e-0b5e-4c20-b031-c4f40d208251", 605 | "name" : "given name", 606 | "protocol" : "openid-connect", 607 | "protocolMapper" : "oidc-usermodel-property-mapper", 608 | "consentRequired" : true, 609 | "consentText" : "${givenName}", 610 | "config" : { 611 | "claim.name" : "given_name", 612 | "jsonType.label" : "String", 613 | "user.attribute" : "firstName", 614 | "access.token.claim" : "true", 615 | "id.token.claim" : "true" 616 | } 617 | } ] 618 | }, { 619 | "id" : "8bf4daa2-2f59-433e-8049-6393523c70c7", 620 | "clientId" : "realm-management", 621 | "name" : "${client_realm-management}", 622 | "surrogateAuthRequired" : false, 623 | "enabled" : true, 624 | "clientAuthenticatorType" : "client-secret", 625 | "secret" : "537126dd-eb5c-44e8-aa2f-49d2b5e8c866", 626 | "redirectUris" : [ ], 627 | "webOrigins" : [ ], 628 | "notBefore" : 0, 629 | "bearerOnly" : true, 630 | "consentRequired" : false, 631 | "standardFlowEnabled" : true, 632 | "implicitFlowEnabled" : false, 633 | "directAccessGrantsEnabled" : false, 634 | "serviceAccountsEnabled" : false, 635 | "publicClient" : false, 636 | "frontchannelLogout" : false, 637 | "attributes" : { }, 638 | "fullScopeAllowed" : false, 639 | "nodeReRegistrationTimeout" : 0, 640 | "protocolMappers" : [ { 641 | "id" : "380ab72e-dba3-4584-a15b-032a88f255ab", 642 | "name" : "full name", 643 | "protocol" : "openid-connect", 644 | "protocolMapper" : "oidc-full-name-mapper", 645 | "consentRequired" : true, 646 | "consentText" : "${fullName}", 647 | "config" : { 648 | "access.token.claim" : "true", 649 | "id.token.claim" : "true" 650 | } 651 | }, { 652 | "id" : "c19b7f1c-b06f-4764-b33c-5bee625ada27", 653 | "name" : "family name", 654 | "protocol" : "openid-connect", 655 | "protocolMapper" : "oidc-usermodel-property-mapper", 656 | "consentRequired" : true, 657 | "consentText" : "${familyName}", 658 | "config" : { 659 | "claim.name" : "family_name", 660 | "jsonType.label" : "String", 661 | "user.attribute" : "lastName", 662 | "access.token.claim" : "true", 663 | "id.token.claim" : "true" 664 | } 665 | }, { 666 | "id" : "6cbca48f-2b77-4734-ada2-dcb577ef405e", 667 | "name" : "email", 668 | "protocol" : "openid-connect", 669 | "protocolMapper" : "oidc-usermodel-property-mapper", 670 | "consentRequired" : true, 671 | "consentText" : "${email}", 672 | "config" : { 673 | "claim.name" : "email", 674 | "jsonType.label" : "String", 675 | "user.attribute" : "email", 676 | "access.token.claim" : "true", 677 | "id.token.claim" : "true" 678 | } 679 | }, { 680 | "id" : "db8e2945-a872-4276-8f37-3c968af66d48", 681 | "name" : "given name", 682 | "protocol" : "openid-connect", 683 | "protocolMapper" : "oidc-usermodel-property-mapper", 684 | "consentRequired" : true, 685 | "consentText" : "${givenName}", 686 | "config" : { 687 | "claim.name" : "given_name", 688 | "jsonType.label" : "String", 689 | "user.attribute" : "firstName", 690 | "access.token.claim" : "true", 691 | "id.token.claim" : "true" 692 | } 693 | }, { 694 | "id" : "aa67d8b9-b64e-4f0e-b8c1-e5ac681f8d5b", 695 | "name" : "username", 696 | "protocol" : "openid-connect", 697 | "protocolMapper" : "oidc-usermodel-property-mapper", 698 | "consentRequired" : true, 699 | "consentText" : "${username}", 700 | "config" : { 701 | "claim.name" : "preferred_username", 702 | "jsonType.label" : "String", 703 | "user.attribute" : "username", 704 | "access.token.claim" : "true", 705 | "id.token.claim" : "true" 706 | } 707 | }, { 708 | "id" : "627ce8be-9ca2-4154-9848-ef013d41b768", 709 | "name" : "role list", 710 | "protocol" : "saml", 711 | "protocolMapper" : "saml-role-list-mapper", 712 | "consentRequired" : false, 713 | "config" : { 714 | "single" : "false", 715 | "attribute.nameformat" : "Basic", 716 | "attribute.name" : "Role" 717 | } 718 | } ] 719 | }, { 720 | "id" : "af737a75-ae49-40d5-a514-afa01e04e635", 721 | "clientId" : "admin-cli", 722 | "name" : "${client_admin-cli}", 723 | "surrogateAuthRequired" : false, 724 | "enabled" : true, 725 | "clientAuthenticatorType" : "client-secret", 726 | "secret" : "269aa9b9-e7bc-4f8c-bdd4-03730aadc987", 727 | "redirectUris" : [ ], 728 | "webOrigins" : [ ], 729 | "notBefore" : 0, 730 | "bearerOnly" : false, 731 | "consentRequired" : false, 732 | "standardFlowEnabled" : false, 733 | "implicitFlowEnabled" : false, 734 | "directAccessGrantsEnabled" : true, 735 | "serviceAccountsEnabled" : false, 736 | "publicClient" : true, 737 | "frontchannelLogout" : false, 738 | "attributes" : { }, 739 | "fullScopeAllowed" : false, 740 | "nodeReRegistrationTimeout" : 0, 741 | "protocolMappers" : [ { 742 | "id" : "eb0ee9f0-26da-4138-bbb4-66ca1304d73f", 743 | "name" : "role list", 744 | "protocol" : "saml", 745 | "protocolMapper" : "saml-role-list-mapper", 746 | "consentRequired" : false, 747 | "config" : { 748 | "single" : "false", 749 | "attribute.nameformat" : "Basic", 750 | "attribute.name" : "Role" 751 | } 752 | }, { 753 | "id" : "2c767fb2-7007-47db-9762-9022b2e7e471", 754 | "name" : "given name", 755 | "protocol" : "openid-connect", 756 | "protocolMapper" : "oidc-usermodel-property-mapper", 757 | "consentRequired" : true, 758 | "consentText" : "${givenName}", 759 | "config" : { 760 | "claim.name" : "given_name", 761 | "jsonType.label" : "String", 762 | "user.attribute" : "firstName", 763 | "access.token.claim" : "true", 764 | "id.token.claim" : "true" 765 | } 766 | }, { 767 | "id" : "f63469cc-53cd-4905-b43b-64fc5aab2f04", 768 | "name" : "full name", 769 | "protocol" : "openid-connect", 770 | "protocolMapper" : "oidc-full-name-mapper", 771 | "consentRequired" : true, 772 | "consentText" : "${fullName}", 773 | "config" : { 774 | "access.token.claim" : "true", 775 | "id.token.claim" : "true" 776 | } 777 | }, { 778 | "id" : "d20c3c09-3c05-4b98-996b-dc6a2e4b68be", 779 | "name" : "email", 780 | "protocol" : "openid-connect", 781 | "protocolMapper" : "oidc-usermodel-property-mapper", 782 | "consentRequired" : true, 783 | "consentText" : "${email}", 784 | "config" : { 785 | "claim.name" : "email", 786 | "jsonType.label" : "String", 787 | "user.attribute" : "email", 788 | "access.token.claim" : "true", 789 | "id.token.claim" : "true" 790 | } 791 | }, { 792 | "id" : "c5fa7ed6-041a-4597-8335-48c69a8040a6", 793 | "name" : "family name", 794 | "protocol" : "openid-connect", 795 | "protocolMapper" : "oidc-usermodel-property-mapper", 796 | "consentRequired" : true, 797 | "consentText" : "${familyName}", 798 | "config" : { 799 | "claim.name" : "family_name", 800 | "jsonType.label" : "String", 801 | "user.attribute" : "lastName", 802 | "access.token.claim" : "true", 803 | "id.token.claim" : "true" 804 | } 805 | }, { 806 | "id" : "21d8f7f6-ce21-4864-b165-fbf552749160", 807 | "name" : "username", 808 | "protocol" : "openid-connect", 809 | "protocolMapper" : "oidc-usermodel-property-mapper", 810 | "consentRequired" : true, 811 | "consentText" : "${username}", 812 | "config" : { 813 | "claim.name" : "preferred_username", 814 | "jsonType.label" : "String", 815 | "user.attribute" : "username", 816 | "access.token.claim" : "true", 817 | "id.token.claim" : "true" 818 | } 819 | } ] 820 | } ], 821 | "browserSecurityHeaders" : { 822 | "contentSecurityPolicy" : "frame-src 'self'", 823 | "xFrameOptions" : "SAMEORIGIN" 824 | }, 825 | "smtpServer" : { }, 826 | "userFederationProviders" : [ { 827 | "id" : "1cf0bf7f-58e2-4d9b-b09f-128a6d464108", 828 | "displayName" : "Legacy User Store", 829 | "providerName" : "User Migration API Provider", 830 | "config" : { 831 | "User Validation Host Base URI" : "http://localhost:9081/migration" 832 | }, 833 | "priority" : 0, 834 | "fullSyncPeriod" : 0, 835 | "changedSyncPeriod" : 0, 836 | "lastSync" : 0 837 | } ], 838 | "eventsEnabled" : false, 839 | "eventsListeners" : [ "jboss-logging" ], 840 | "enabledEventTypes" : [ ], 841 | "adminEventsEnabled" : false, 842 | "adminEventsDetailsEnabled" : false, 843 | "identityFederationEnabled" : false, 844 | "internationalizationEnabled" : false, 845 | "supportedLocales" : [ ], 846 | "authenticationFlows" : [ { 847 | "alias" : "Handle Existing Account", 848 | "description" : "Handle what to do if there is existing account with same email/username like authenticated identity provider", 849 | "providerId" : "basic-flow", 850 | "topLevel" : false, 851 | "builtIn" : true, 852 | "authenticationExecutions" : [ { 853 | "authenticator" : "idp-confirm-link", 854 | "autheticatorFlow" : false, 855 | "requirement" : "REQUIRED", 856 | "userSetupAllowed" : false, 857 | "priority" : 10 858 | }, { 859 | "authenticator" : "idp-email-verification", 860 | "autheticatorFlow" : false, 861 | "requirement" : "ALTERNATIVE", 862 | "userSetupAllowed" : false, 863 | "priority" : 20 864 | }, { 865 | "flowAlias" : "Verify Existing Account by Re-authentication", 866 | "autheticatorFlow" : true, 867 | "requirement" : "ALTERNATIVE", 868 | "userSetupAllowed" : false, 869 | "priority" : 30 870 | } ] 871 | }, { 872 | "alias" : "Verify Existing Account by Re-authentication", 873 | "description" : "Reauthentication of existing account", 874 | "providerId" : "basic-flow", 875 | "topLevel" : false, 876 | "builtIn" : true, 877 | "authenticationExecutions" : [ { 878 | "authenticator" : "idp-username-password-form", 879 | "autheticatorFlow" : false, 880 | "requirement" : "REQUIRED", 881 | "userSetupAllowed" : false, 882 | "priority" : 10 883 | }, { 884 | "authenticator" : "auth-otp-form", 885 | "autheticatorFlow" : false, 886 | "requirement" : "OPTIONAL", 887 | "userSetupAllowed" : false, 888 | "priority" : 20 889 | } ] 890 | }, { 891 | "alias" : "browser", 892 | "description" : "browser based authentication", 893 | "providerId" : "basic-flow", 894 | "topLevel" : true, 895 | "builtIn" : true, 896 | "authenticationExecutions" : [ { 897 | "authenticator" : "auth-cookie", 898 | "autheticatorFlow" : false, 899 | "requirement" : "ALTERNATIVE", 900 | "userSetupAllowed" : false, 901 | "priority" : 10 902 | }, { 903 | "authenticator" : "auth-spnego", 904 | "autheticatorFlow" : false, 905 | "requirement" : "DISABLED", 906 | "userSetupAllowed" : false, 907 | "priority" : 20 908 | }, { 909 | "flowAlias" : "forms", 910 | "autheticatorFlow" : true, 911 | "requirement" : "ALTERNATIVE", 912 | "userSetupAllowed" : false, 913 | "priority" : 30 914 | } ] 915 | }, { 916 | "alias" : "clients", 917 | "description" : "Base authentication for clients", 918 | "providerId" : "client-flow", 919 | "topLevel" : true, 920 | "builtIn" : true, 921 | "authenticationExecutions" : [ { 922 | "authenticator" : "client-secret", 923 | "autheticatorFlow" : false, 924 | "requirement" : "ALTERNATIVE", 925 | "userSetupAllowed" : false, 926 | "priority" : 10 927 | }, { 928 | "authenticator" : "client-jwt", 929 | "autheticatorFlow" : false, 930 | "requirement" : "ALTERNATIVE", 931 | "userSetupAllowed" : false, 932 | "priority" : 20 933 | } ] 934 | }, { 935 | "alias" : "direct grant", 936 | "description" : "OpenID Connect Resource Owner Grant", 937 | "providerId" : "basic-flow", 938 | "topLevel" : true, 939 | "builtIn" : true, 940 | "authenticationExecutions" : [ { 941 | "authenticator" : "direct-grant-validate-username", 942 | "autheticatorFlow" : false, 943 | "requirement" : "REQUIRED", 944 | "userSetupAllowed" : false, 945 | "priority" : 10 946 | }, { 947 | "authenticator" : "direct-grant-validate-password", 948 | "autheticatorFlow" : false, 949 | "requirement" : "REQUIRED", 950 | "userSetupAllowed" : false, 951 | "priority" : 20 952 | }, { 953 | "authenticator" : "direct-grant-validate-otp", 954 | "autheticatorFlow" : false, 955 | "requirement" : "OPTIONAL", 956 | "userSetupAllowed" : false, 957 | "priority" : 30 958 | } ] 959 | }, { 960 | "alias" : "first broker login", 961 | "description" : "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account", 962 | "providerId" : "basic-flow", 963 | "topLevel" : true, 964 | "builtIn" : true, 965 | "authenticationExecutions" : [ { 966 | "authenticatorConfig" : "review profile config", 967 | "authenticator" : "idp-review-profile", 968 | "autheticatorFlow" : false, 969 | "requirement" : "REQUIRED", 970 | "userSetupAllowed" : false, 971 | "priority" : 10 972 | }, { 973 | "authenticatorConfig" : "create unique user config", 974 | "authenticator" : "idp-create-user-if-unique", 975 | "autheticatorFlow" : false, 976 | "requirement" : "ALTERNATIVE", 977 | "userSetupAllowed" : false, 978 | "priority" : 20 979 | }, { 980 | "flowAlias" : "Handle Existing Account", 981 | "autheticatorFlow" : true, 982 | "requirement" : "ALTERNATIVE", 983 | "userSetupAllowed" : false, 984 | "priority" : 30 985 | } ] 986 | }, { 987 | "alias" : "forms", 988 | "description" : "Username, password, otp and other auth forms.", 989 | "providerId" : "basic-flow", 990 | "topLevel" : false, 991 | "builtIn" : true, 992 | "authenticationExecutions" : [ { 993 | "authenticator" : "auth-username-password-form", 994 | "autheticatorFlow" : false, 995 | "requirement" : "REQUIRED", 996 | "userSetupAllowed" : false, 997 | "priority" : 10 998 | }, { 999 | "authenticator" : "auth-otp-form", 1000 | "autheticatorFlow" : false, 1001 | "requirement" : "OPTIONAL", 1002 | "userSetupAllowed" : false, 1003 | "priority" : 20 1004 | } ] 1005 | }, { 1006 | "alias" : "registration", 1007 | "description" : "registration flow", 1008 | "providerId" : "basic-flow", 1009 | "topLevel" : true, 1010 | "builtIn" : true, 1011 | "authenticationExecutions" : [ { 1012 | "authenticator" : "registration-page-form", 1013 | "flowAlias" : "registration form", 1014 | "autheticatorFlow" : true, 1015 | "requirement" : "REQUIRED", 1016 | "userSetupAllowed" : false, 1017 | "priority" : 10 1018 | } ] 1019 | }, { 1020 | "alias" : "registration form", 1021 | "description" : "registration form", 1022 | "providerId" : "form-flow", 1023 | "topLevel" : false, 1024 | "builtIn" : true, 1025 | "authenticationExecutions" : [ { 1026 | "authenticator" : "registration-user-creation", 1027 | "autheticatorFlow" : false, 1028 | "requirement" : "REQUIRED", 1029 | "userSetupAllowed" : false, 1030 | "priority" : 20 1031 | }, { 1032 | "authenticator" : "registration-profile-action", 1033 | "autheticatorFlow" : false, 1034 | "requirement" : "REQUIRED", 1035 | "userSetupAllowed" : false, 1036 | "priority" : 40 1037 | }, { 1038 | "authenticator" : "registration-password-action", 1039 | "autheticatorFlow" : false, 1040 | "requirement" : "REQUIRED", 1041 | "userSetupAllowed" : false, 1042 | "priority" : 50 1043 | }, { 1044 | "authenticator" : "registration-recaptcha-action", 1045 | "autheticatorFlow" : false, 1046 | "requirement" : "DISABLED", 1047 | "userSetupAllowed" : false, 1048 | "priority" : 60 1049 | } ] 1050 | }, { 1051 | "alias" : "reset credentials", 1052 | "description" : "Reset credentials for a user if they forgot their password or something", 1053 | "providerId" : "basic-flow", 1054 | "topLevel" : true, 1055 | "builtIn" : true, 1056 | "authenticationExecutions" : [ { 1057 | "authenticator" : "reset-credentials-choose-user", 1058 | "autheticatorFlow" : false, 1059 | "requirement" : "REQUIRED", 1060 | "userSetupAllowed" : false, 1061 | "priority" : 10 1062 | }, { 1063 | "authenticator" : "reset-credential-email", 1064 | "autheticatorFlow" : false, 1065 | "requirement" : "REQUIRED", 1066 | "userSetupAllowed" : false, 1067 | "priority" : 20 1068 | }, { 1069 | "authenticator" : "reset-password", 1070 | "autheticatorFlow" : false, 1071 | "requirement" : "REQUIRED", 1072 | "userSetupAllowed" : false, 1073 | "priority" : 30 1074 | }, { 1075 | "authenticator" : "reset-otp", 1076 | "autheticatorFlow" : false, 1077 | "requirement" : "OPTIONAL", 1078 | "userSetupAllowed" : false, 1079 | "priority" : 40 1080 | } ] 1081 | } ], 1082 | "authenticatorConfig" : [ { 1083 | "alias" : "create unique user config", 1084 | "config" : { 1085 | "require.password.update.after.registration" : "false" 1086 | } 1087 | }, { 1088 | "alias" : "review profile config", 1089 | "config" : { 1090 | "update.profile.on.first.login" : "missing" 1091 | } 1092 | } ], 1093 | "requiredActions" : [ { 1094 | "alias" : "CONFIGURE_TOTP", 1095 | "name" : "Configure Totp", 1096 | "providerId" : "CONFIGURE_TOTP", 1097 | "enabled" : true, 1098 | "defaultAction" : false, 1099 | "config" : { } 1100 | }, { 1101 | "alias" : "UPDATE_PASSWORD", 1102 | "name" : "Update Password", 1103 | "providerId" : "UPDATE_PASSWORD", 1104 | "enabled" : true, 1105 | "defaultAction" : false, 1106 | "config" : { } 1107 | }, { 1108 | "alias" : "UPDATE_PROFILE", 1109 | "name" : "Update Profile", 1110 | "providerId" : "UPDATE_PROFILE", 1111 | "enabled" : true, 1112 | "defaultAction" : false, 1113 | "config" : { } 1114 | }, { 1115 | "alias" : "VERIFY_EMAIL", 1116 | "name" : "Verify Email", 1117 | "providerId" : "VERIFY_EMAIL", 1118 | "enabled" : true, 1119 | "defaultAction" : false, 1120 | "config" : { } 1121 | }, { 1122 | "alias" : "terms_and_conditions", 1123 | "name" : "Terms and Conditions", 1124 | "providerId" : "terms_and_conditions", 1125 | "enabled" : false, 1126 | "defaultAction" : false, 1127 | "config" : { } 1128 | } ], 1129 | "browserFlow" : "browser", 1130 | "registrationFlow" : "registration", 1131 | "directGrantFlow" : "direct grant", 1132 | "resetCredentialsFlow" : "reset credentials", 1133 | "clientAuthenticationFlow" : "clients" 1134 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | 2 | org.gradle.daemon=false 3 | keycloakHome=/Users/scott/Applications/keycloak-1.7.0.Final 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Smartling/keycloak-user-migration-provider/e2745c4bcaf8e62ba54ac5bfb19de06e2bb796f9/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat Oct 17 13:51:42 EDT 2015 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.7-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >&- 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >&- 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /legacy-user-app/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Smartling, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | buildscript { 18 | repositories { 19 | jcenter() 20 | maven { url "http://repo.spring.io/snapshot" } 21 | maven { url "http://repo.spring.io/milestone" } 22 | } 23 | dependencies { 24 | classpath("org.springframework.boot:spring-boot-gradle-plugin:1.3.1.RELEASE") 25 | } 26 | } 27 | 28 | apply plugin: 'spring-boot' 29 | apply plugin: 'war' 30 | 31 | dependencies { 32 | compile project(':user-model') 33 | compile 'org.springframework.boot:spring-boot-starter-web:1.3.1.RELEASE' 34 | compile 'org.springframework.boot:spring-boot-starter-jersey:1.3.1.RELEASE' 35 | compile 'commons-codec:commons-codec:1.10' 36 | // providedRuntime "org.springframework.boot:spring-boot-starter-tomcat:1.3.1.RELEASE" 37 | testCompile 'org.springframework.boot:spring-boot-starter-test:1.3.1.RELEASE' 38 | } 39 | -------------------------------------------------------------------------------- /legacy-user-app/src/main/java/com/acme/legacy/app/Application.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Smartling, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.acme.legacy.app; 17 | 18 | import org.springframework.boot.SpringApplication; 19 | import org.springframework.boot.autoconfigure.SpringBootApplication; 20 | 21 | /** 22 | * Entry point for legacy user service. 23 | * 24 | * @author Scott Rossillo 25 | */ 26 | @SpringBootApplication 27 | public class Application 28 | { 29 | public static void main(String args[]) 30 | { 31 | SpringApplication.run(Application.class, args); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /legacy-user-app/src/main/java/com/acme/legacy/app/JerseyConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Smartling, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.acme.legacy.app; 17 | 18 | import com.acme.legacy.app.service.LegacyUserService; 19 | import org.glassfish.jersey.server.ResourceConfig; 20 | import org.springframework.context.annotation.Configuration; 21 | 22 | /** 23 | * Simple JAX-RS servlet Spring configuration. 24 | */ 25 | @Configuration 26 | public class JerseyConfig extends ResourceConfig 27 | { 28 | public JerseyConfig() 29 | { 30 | register(LegacyUserService.class); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /legacy-user-app/src/main/java/com/acme/legacy/app/entity/User.java: -------------------------------------------------------------------------------- 1 | package com.acme.legacy.app.entity; 2 | 3 | import java.io.Serializable; 4 | import java.util.List; 5 | import java.util.Map; 6 | import java.util.Set; 7 | 8 | /** 9 | * Represents an overly simplified user entity in the legacy system. 10 | * 11 | * @author Scott Rossillo 12 | */ 13 | public class User implements Serializable 14 | { 15 | private String email; 16 | private String firstName; 17 | private String lastName; 18 | private String title; 19 | private String password; 20 | private String salt; 21 | private boolean emailVerified; 22 | private boolean enabled; 23 | private Set roles; 24 | 25 | public User() 26 | { 27 | } 28 | 29 | public String getEmail() 30 | { 31 | return email; 32 | } 33 | 34 | public void setEmail(String email) 35 | { 36 | this.email = email; 37 | } 38 | 39 | public String getFirstName() 40 | { 41 | return firstName; 42 | } 43 | 44 | public void setFirstName(String firstName) 45 | { 46 | this.firstName = firstName; 47 | } 48 | 49 | public String getLastName() 50 | { 51 | return lastName; 52 | } 53 | 54 | public void setLastName(String lastName) 55 | { 56 | this.lastName = lastName; 57 | } 58 | 59 | public String getTitle() 60 | { 61 | return title; 62 | } 63 | 64 | public void setTitle(String title) 65 | { 66 | this.title = title; 67 | } 68 | 69 | public String getPassword() 70 | { 71 | return password; 72 | } 73 | 74 | public void setPassword(String password) 75 | { 76 | this.password = password; 77 | } 78 | 79 | public String getSalt() 80 | { 81 | return salt; 82 | } 83 | 84 | public void setSalt(String salt) 85 | { 86 | this.salt = salt; 87 | } 88 | 89 | public boolean isEmailVerified() 90 | { 91 | return emailVerified; 92 | } 93 | 94 | public void setEmailVerified(boolean emailVerified) 95 | { 96 | this.emailVerified = emailVerified; 97 | } 98 | 99 | public boolean isEnabled() 100 | { 101 | return enabled; 102 | } 103 | 104 | public void setEnabled(boolean enabled) 105 | { 106 | this.enabled = enabled; 107 | } 108 | 109 | public Set getRoles() 110 | { 111 | return roles; 112 | } 113 | 114 | public void setRoles(Set roles) 115 | { 116 | this.roles = roles; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /legacy-user-app/src/main/java/com/acme/legacy/app/manager/AccessDeniedException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Smartling, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.acme.legacy.app.manager; 17 | 18 | /** 19 | * Thrown to indicate a login attempt failed. 20 | * 21 | * @author Scott Rossillo 22 | */ 23 | public class AccessDeniedException extends Exception 24 | { 25 | public AccessDeniedException() 26 | { 27 | super("Invalid username or password"); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /legacy-user-app/src/main/java/com/acme/legacy/app/manager/FederatedUserConverter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Smartling, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.acme.legacy.app.manager; 17 | 18 | import com.acme.legacy.app.entity.User; 19 | import com.smartling.keycloak.federation.FederatedUserModel; 20 | import org.springframework.core.convert.converter.Converter; 21 | import org.springframework.stereotype.Component; 22 | 23 | import java.util.Collections; 24 | import java.util.HashMap; 25 | import java.util.List; 26 | import java.util.Map; 27 | 28 | /** 29 | * Converts legacy a {@link User} into a {@link FederatedUserModel}. 30 | * 31 | * @author Scott Rossillo 32 | */ 33 | @Component 34 | public class FederatedUserConverter implements Converter 35 | { 36 | @Override 37 | public FederatedUserModel convert(User source) 38 | { 39 | if (source == null) 40 | return null; 41 | 42 | FederatedUserModel model = new FederatedUserModel(); 43 | Map> attributes = new HashMap<>(); 44 | 45 | model.setAttributes(attributes); 46 | 47 | model.setUsername(source.getEmail()); 48 | model.setEmail(source.getEmail()); 49 | model.setFirstName(source.getFirstName()); 50 | model.setLastName(source.getLastName()); 51 | 52 | model.setEnabled(source.isEnabled()); 53 | model.setEmailVerified(source.isEmailVerified()); 54 | 55 | // map other data to attributes 56 | attributes.put("title", Collections.singletonList(source.getTitle())); 57 | 58 | // map roles 59 | model.setRoles(source.getRoles()); 60 | 61 | return model; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /legacy-user-app/src/main/java/com/acme/legacy/app/manager/PasswordEncoder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Smartling, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.acme.legacy.app.manager; 17 | 18 | /** 19 | * Legacy user manager password encoder. 20 | */ 21 | public interface PasswordEncoder 22 | { 23 | String encode(String rawPassword, String salt); 24 | } 25 | -------------------------------------------------------------------------------- /legacy-user-app/src/main/java/com/acme/legacy/app/manager/SimplePasswordEncoder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Smartling, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.acme.legacy.app.manager; 17 | 18 | import org.apache.commons.codec.digest.DigestUtils; 19 | import org.springframework.stereotype.Component; 20 | 21 | /** 22 | * Simple, single pass SHA-256 password encoder. In the real world, this would be your 23 | * current password encoding strategy. 24 | * 25 | * @author Scott Rossillo 26 | */ 27 | @Component 28 | public class SimplePasswordEncoder implements PasswordEncoder 29 | { 30 | @Override 31 | public String encode(String rawPassword, String salt) 32 | { 33 | return DigestUtils.sha256Hex(rawPassword + salt); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /legacy-user-app/src/main/java/com/acme/legacy/app/manager/SimpleUserManager.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Smartling, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.acme.legacy.app.manager; 17 | 18 | import com.acme.legacy.app.entity.User; 19 | import com.acme.legacy.app.repository.UserRepository; 20 | import org.slf4j.Logger; 21 | import org.slf4j.LoggerFactory; 22 | import org.springframework.beans.factory.annotation.Autowired; 23 | import org.springframework.stereotype.Service; 24 | import org.springframework.util.Assert; 25 | 26 | /** 27 | * Simple user manager implementation. 28 | * 29 | * @author Scott Rossillo 30 | */ 31 | @Service 32 | public class SimpleUserManager implements UserManager 33 | { 34 | private static final Logger LOGGER = LoggerFactory.getLogger(SimpleUserManager.class); 35 | @Autowired 36 | private PasswordEncoder passwordEncoder; 37 | 38 | @Autowired 39 | private UserRepository userRepository; 40 | 41 | @Override 42 | public User findByEmail(String email) 43 | { 44 | Assert.notNull(email, "email address required"); 45 | return userRepository.findByEmail(email); 46 | } 47 | 48 | @Override 49 | public User login(String email, String password) throws AccessDeniedException 50 | { 51 | Assert.notNull(email, "email address required"); 52 | Assert.notNull(password, "password required"); 53 | User user = userRepository.findByEmail(email); 54 | String encodedPassword; 55 | 56 | if (user == null) 57 | throw new AccessDeniedException(); 58 | 59 | encodedPassword = passwordEncoder.encode(password, user.getSalt()); 60 | 61 | if (!encodedPassword.equals(user.getPassword())) 62 | throw new AccessDeniedException(); 63 | 64 | return user; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /legacy-user-app/src/main/java/com/acme/legacy/app/manager/UserManager.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Smartling, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.acme.legacy.app.manager; 17 | 18 | import com.acme.legacy.app.entity.User; 19 | 20 | /** 21 | * Provides a user manager. 22 | * 23 | * @author Scott Rossillo 24 | */ 25 | public interface UserManager 26 | { 27 | /** 28 | * Returns the user identified by the given email address. 29 | * 30 | * @param email the email address for the user to find 31 | * @return the user identified by the given email if found; 32 | * null otherwise 33 | */ 34 | User findByEmail(String email); 35 | 36 | /** 37 | * Logs in the user with the given email and password. 38 | * 39 | * @param email the user's email (required) 40 | * @param password the user's password (required) 41 | * @return the logged in user 42 | * @throws AccessDeniedException if the given email and password are invalid 43 | */ 44 | User login(String email, String password) throws AccessDeniedException; 45 | } 46 | -------------------------------------------------------------------------------- /legacy-user-app/src/main/java/com/acme/legacy/app/repository/JsonUserRepository.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Smartling, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.acme.legacy.app.repository; 17 | 18 | import com.acme.legacy.app.entity.User; 19 | import com.fasterxml.jackson.core.type.TypeReference; 20 | import com.fasterxml.jackson.databind.ObjectMapper; 21 | import org.springframework.core.io.ClassPathResource; 22 | import org.springframework.core.io.Resource; 23 | import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; 24 | import org.springframework.stereotype.Repository; 25 | 26 | import javax.annotation.PostConstruct; 27 | import java.util.Collections; 28 | import java.util.List; 29 | import java.util.Map; 30 | import java.util.TreeMap; 31 | 32 | /** 33 | * Trivial, read-only file based user repository. 34 | */ 35 | @Repository 36 | public class JsonUserRepository implements UserRepository 37 | { 38 | private Map users; 39 | 40 | @PostConstruct 41 | @SuppressWarnings("unchecked") 42 | public void init() throws Exception { 43 | Resource resource = new ClassPathResource("users.json"); 44 | ObjectMapper mapper = Jackson2ObjectMapperBuilder.json().build(); 45 | List userList = mapper.readValue(resource.getInputStream(), new TypeReference>() { }); 46 | Map userMap = new TreeMap<>(); 47 | 48 | for (User user : userList) 49 | userMap.put(user.getEmail(), user); 50 | 51 | this.users = Collections.unmodifiableMap(userMap); 52 | } 53 | 54 | @Override 55 | public User findByEmail(String email) 56 | { 57 | return users.get(email.toLowerCase()); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /legacy-user-app/src/main/java/com/acme/legacy/app/repository/UserRepository.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Smartling, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.acme.legacy.app.repository; 17 | 18 | import com.acme.legacy.app.entity.User; 19 | 20 | /** 21 | * Represents a user repository. 22 | * 23 | * @author Scott Rossillo 24 | */ 25 | public interface UserRepository 26 | { 27 | /** 28 | * Returns the user identified by the given email address. 29 | * 30 | * @param email the email address for the user to find 31 | * @return the user identified by the given email if found; 32 | * null otherwise 33 | */ 34 | User findByEmail(String email); 35 | } 36 | -------------------------------------------------------------------------------- /legacy-user-app/src/main/java/com/acme/legacy/app/service/LegacyUserService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Smartling, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.acme.legacy.app.service; 17 | 18 | import com.acme.legacy.app.entity.User; 19 | import com.acme.legacy.app.manager.AccessDeniedException; 20 | import com.acme.legacy.app.manager.UserManager; 21 | import com.smartling.keycloak.federation.FederatedUserModel; 22 | import com.smartling.keycloak.federation.FederatedUserService; 23 | import com.smartling.keycloak.federation.UserCredentialsDto; 24 | import org.slf4j.Logger; 25 | import org.slf4j.LoggerFactory; 26 | import org.springframework.beans.factory.annotation.Autowired; 27 | import org.springframework.core.convert.ConversionService; 28 | import org.springframework.stereotype.Component; 29 | 30 | import javax.ws.rs.Consumes; 31 | import javax.ws.rs.Path; 32 | import javax.ws.rs.Produces; 33 | import javax.ws.rs.core.MediaType; 34 | import javax.ws.rs.core.Response; 35 | import javax.ws.rs.core.Response.Status; 36 | 37 | /** 38 | * Simple JAX-RS based legacy user service. 39 | */ 40 | @Component 41 | @Path("/migration") 42 | @Consumes(MediaType.APPLICATION_JSON) 43 | @Produces(MediaType.APPLICATION_JSON) 44 | public class LegacyUserService implements FederatedUserService 45 | { 46 | private static final Logger LOG = LoggerFactory.getLogger(LegacyUserService.class); 47 | 48 | @Autowired 49 | private ConversionService conversionService; 50 | 51 | @Autowired 52 | private UserManager userManager; 53 | 54 | public LegacyUserService() 55 | { 56 | LOG.warn("creating me"); 57 | } 58 | 59 | @Override 60 | public FederatedUserModel getUserDetails(String username) 61 | { 62 | User user = userManager.findByEmail(username); 63 | return conversionService.convert(user, FederatedUserModel.class); 64 | } 65 | 66 | @Override 67 | public Response validateUserExists(String username) 68 | { 69 | User user = userManager.findByEmail(username); 70 | Status status = user != null ? Status.OK : Status.NOT_FOUND; 71 | return Response.status(status).entity("").build(); 72 | } 73 | 74 | @Override 75 | public Response validateLogin(String username, UserCredentialsDto credentials) 76 | { 77 | Status status = Status.OK; 78 | 79 | try 80 | { 81 | userManager.login(username, credentials.getPassword()); 82 | LOG.info("User {} login valid", username); 83 | } 84 | catch (AccessDeniedException ex) 85 | { 86 | status = Status.UNAUTHORIZED; 87 | LOG.info("User {} login failed", username); 88 | } 89 | 90 | return Response.status(status).entity("").build(); 91 | } 92 | } 93 | 94 | -------------------------------------------------------------------------------- /legacy-user-app/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 9081 3 | -------------------------------------------------------------------------------- /legacy-user-app/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{yyyy-MM-dd HH:mm:ss:SSS} - [%level] \(%file:%method:%line\) %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /legacy-user-app/src/main/resources/users.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "email" : "craig@007.com", 4 | "emailVerified" : true, 5 | "enabled" : true, 6 | "firstName" : "Daniel", 7 | "lastName" : "Craig", 8 | "title" : "James Bond", 9 | "password" : "20202291b1f44baf5a2b0e1051b6fdabc5f8c06df4d9a43af32daa7c6843eb3a", 10 | "salt" : "aef9823", 11 | "roles" : [ 12 | "user" 13 | ] 14 | }, 15 | { 16 | "email" : "green@007.com", 17 | "emailVerified" : true, 18 | "enabled" : true, 19 | "firstName" : "Eva", 20 | "lastName" : "Green", 21 | "title" : "Vesper Lynd", 22 | "password" : "bba3c22363ecebf8b5f50e11c20b6d2928a4f5b197f5877dc3a88c1bd200f68d", 23 | "salt" : "jipn5769", 24 | "roles" : [ 25 | "user" 26 | ] 27 | }, 28 | { 29 | "email" : "mendes@007.com", 30 | "emailVerified" : true, 31 | "enabled" : true, 32 | "firstName" : "Sam", 33 | "lastName" : "Mendes", 34 | "title" : "Director", 35 | "password" : "68252a68dc4fccd7b824a80ba4d8337c74b2fe238d060f7e5d34adb0a366186a", 36 | "salt" : "dhfq178", 37 | "roles" : [ 38 | "user" 39 | ] 40 | } 41 | ] 42 | -------------------------------------------------------------------------------- /legacy-user-app/src/test/java/com/acme/legacy/app/manager/SimplePasswordEncoderTest.java: -------------------------------------------------------------------------------- 1 | package com.acme.legacy.app.manager; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Created by scott on 12/23/15. 9 | */ 10 | public class SimplePasswordEncoderTest 11 | { 12 | 13 | private PasswordEncoder passwordEncoder = new SimplePasswordEncoder(); 14 | 15 | @Test 16 | public void testEncode() throws Exception 17 | { 18 | System.err.println(passwordEncoder.encode("Martini4", "dhfq178")); 19 | } 20 | } -------------------------------------------------------------------------------- /portal-demo/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Smartling, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | buildscript { 18 | repositories { 19 | jcenter() 20 | maven { url "http://repo.spring.io/snapshot" } 21 | maven { url "http://repo.spring.io/milestone" } 22 | } 23 | dependencies { 24 | classpath("org.springframework.boot:spring-boot-gradle-plugin:1.3.1.RELEASE") 25 | } 26 | } 27 | 28 | apply plugin: 'spring-boot' 29 | 30 | dependencies { 31 | compile 'org.springframework.boot:spring-boot-starter-web:1.3.1.RELEASE' 32 | compile 'org.springframework.boot:spring-boot-starter-security:1.3.1.RELEASE' 33 | compile 'org.springframework.boot:spring-boot-starter-freemarker:1.3.1.RELEASE' 34 | compile 'org.keycloak:keycloak-spring-security-adapter:1.7.0.Final' 35 | testCompile 'org.springframework.boot:spring-boot-starter-test:1.3.1.RELEASE' 36 | } 37 | -------------------------------------------------------------------------------- /portal-demo/src/main/java/com/acme/portal/Application.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Smartling, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.acme.portal; 17 | 18 | import org.springframework.boot.SpringApplication; 19 | import org.springframework.boot.autoconfigure.SpringBootApplication; 20 | 21 | /** 22 | * Demo application entry point. 23 | * 24 | * @author Scott Rossillo 25 | */ 26 | @SpringBootApplication 27 | public class Application 28 | { 29 | public static void main(String args[]) 30 | { 31 | SpringApplication.run(Application.class, args); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /portal-demo/src/main/java/com/acme/portal/PortalController.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Smartling, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.acme.portal; 17 | 18 | import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken; 19 | import org.keycloak.representations.IDToken; 20 | import org.springframework.security.core.context.SecurityContextHolder; 21 | import org.springframework.stereotype.Controller; 22 | import org.springframework.ui.Model; 23 | import org.springframework.web.bind.annotation.ModelAttribute; 24 | import org.springframework.web.bind.annotation.RequestMapping; 25 | import org.springframework.web.bind.annotation.RequestMethod; 26 | 27 | /** 28 | * Simple controller demonstrating the migration of user data from the legacy 29 | * user store to Keycloak, on demand. 30 | * 31 | * @author Scott Rossillo 32 | */ 33 | @Controller 34 | public class PortalController 35 | { 36 | @RequestMapping(value = "/", method = RequestMethod.GET) 37 | public String handleWelcomeRequest() 38 | { 39 | return "home"; 40 | } 41 | 42 | @RequestMapping(value = "/info/user", method = RequestMethod.GET) 43 | public String handlerUserInfoRequest(Model model) 44 | { 45 | KeycloakAuthenticationToken authentication = (KeycloakAuthenticationToken) SecurityContextHolder.getContext().getAuthentication(); 46 | IDToken token = authentication.getAccount().getKeycloakSecurityContext().getIdToken(); 47 | 48 | model.addAttribute("token", token); 49 | model.addAttribute("claims", token.getOtherClaims()); 50 | 51 | return "info"; 52 | } 53 | 54 | @ModelAttribute("serviceName") 55 | public String populateServiceName() 56 | { 57 | return "Demo Portal"; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /portal-demo/src/main/java/com/acme/portal/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Smartling, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.acme.portal; 17 | 18 | import org.keycloak.adapters.springsecurity.KeycloakSecurityComponents; 19 | import org.keycloak.adapters.springsecurity.config.KeycloakWebSecurityConfigurerAdapter; 20 | import org.springframework.beans.factory.annotation.Autowired; 21 | import org.springframework.context.annotation.Bean; 22 | import org.springframework.context.annotation.ComponentScan; 23 | import org.springframework.context.annotation.Configuration; 24 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 25 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 26 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 27 | import org.springframework.security.core.session.SessionRegistryImpl; 28 | import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy; 29 | import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy; 30 | 31 | /** 32 | * Simple security configuration. 33 | */ 34 | @Configuration 35 | @EnableWebSecurity 36 | @ComponentScan(basePackageClasses = KeycloakSecurityComponents.class) 37 | public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter 38 | { 39 | @Autowired 40 | public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { 41 | auth.authenticationProvider(keycloakAuthenticationProvider()); 42 | } 43 | 44 | @Bean 45 | @Override 46 | protected SessionAuthenticationStrategy sessionAuthenticationStrategy() { 47 | return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl()); 48 | } 49 | 50 | @Override 51 | protected void configure(HttpSecurity http) throws Exception 52 | { 53 | super.configure(http); 54 | http 55 | .authorizeRequests() 56 | .antMatchers("/info/**").authenticated(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /portal-demo/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 9082 3 | 4 | keycloak: 5 | configurationFile: "classpath:keycloak.json" 6 | 7 | spring: 8 | freemarker: 9 | cache: false 10 | -------------------------------------------------------------------------------- /portal-demo/src/main/resources/keycloak.json: -------------------------------------------------------------------------------- 1 | { 2 | "realm": "Eon", 3 | "realm-public-key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApDc8DuKDzX3iw8ipVhOfyCc1K6tYbm7Mi5BmbcXhrLCpnxdQ9AzNHldV+dMiJVPQd4hl3dlrLitdymrkWPX3E3sLomO2W8QUbz0QYNbuYxtBisNMEH6/41l9REr0xEb0JyHKqVT4dsv6a10xLxZ+/zyGFOk30Y/XvNf9/uikAEc4IKIJrxWxIHR+tpM/GzW1rbcaVp+dnqHyjwHnEU+2aoBTqC0Wq6UTJUPxnJ9IlV2evrBhFe9+oFOUFr10wj5xMEqEiNhbnXJ5jVytgEIf9M9uC8URa1haDLl4TnxICQyAprX4C/Pq3ybuckLGo8bGCS/GlgxkV0h/SfO+uUYULQIDAQAB", 4 | "auth-server-url": "http://localhost:8080/auth", 5 | "ssl-required": "external", 6 | "resource": "demo-portal", 7 | "credentials": { 8 | "secret": "06694847-9253-4d4e-bd8f-9920d15d8e03" 9 | } 10 | } -------------------------------------------------------------------------------- /portal-demo/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{yyyy-MM-dd HH:mm:ss:SSS} - [%level] \(%file:%method:%line\) %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /portal-demo/src/main/resources/templates/home.ftl: -------------------------------------------------------------------------------- 1 | <#import "/spring.ftl" as spring /> 2 | <#assign xhtmlCompliant = true in spring> 3 | 4 | 5 | 6 | ${serviceName}: Info 7 | 9 | 11 | 12 | 13 |
14 | 21 | 22 | View user info 23 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /portal-demo/src/main/resources/templates/info.ftl: -------------------------------------------------------------------------------- 1 | <#import "/spring.ftl" as spring /> 2 | <#assign xhtmlCompliant = true in spring> 3 | 4 | 5 | 6 | ${serviceName}: Info 7 | 9 | 11 | 12 | 13 |
14 | 27 |

Welcome, ${token.name}

28 | 29 |
30 |
User information
31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 |
Email${token.email}
Given Name${token.givenName}
Family Name${token.familyName}
Full Name${token.name}
51 |
52 | 53 |
54 |
Custom Claims
55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | <#list claims?keys as claimName> 65 | 66 | 67 | 68 | 69 | 70 | 71 |
ClaimValue
${claimName}${claims[claimName]}
72 |
73 |
74 | 75 | 76 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Smartling, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | rootProject.name = 'keycloak-user-federation-example' 18 | include 'user-model' 19 | include 'user-migration-federation-provider' 20 | include 'legacy-user-app' 21 | include 'portal-demo' 22 | -------------------------------------------------------------------------------- /user-migration-federation-provider/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Smartling, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | plugins { 18 | id "nebula.provided-base" version "3.0.3" 19 | } 20 | 21 | sourceCompatibility = 1.7 22 | targetCompatibility = 1.7 23 | 24 | dependencies { 25 | compile project(':user-model') 26 | provided "org.keycloak:keycloak-core:${keycloakVersion}" 27 | provided "org.keycloak:keycloak-model-api:${keycloakVersion}" 28 | provided "org.apache.httpcomponents:httpclient:4.3.6" 29 | provided "org.jboss.logging:jboss-logging:3.2.1.Final" 30 | provided "javax.ws.rs:javax.ws.rs-api:2.0.1" 31 | provided 'org.jboss.resteasy:resteasy-client:3.0.11.Final' 32 | 33 | testCompile 'junit:junit:4.12' 34 | testCompile 'org.mockito:mockito-all:1.10.19' 35 | testCompile 'org.powermock:powermock-mockito-release-full:1.6.2' 36 | } 37 | -------------------------------------------------------------------------------- /user-migration-federation-provider/src/main/java/com/smartling/keycloak/provider/RemoteUserFederationProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Smartling, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.smartling.keycloak.provider; 17 | 18 | import com.smartling.keycloak.federation.FederatedUserModel; 19 | import com.smartling.keycloak.federation.FederatedUserService; 20 | import com.smartling.keycloak.federation.UserCredentialsDto; 21 | import org.apache.http.HttpStatus; 22 | import org.jboss.logging.Logger; 23 | import org.jboss.resteasy.client.jaxrs.ResteasyClient; 24 | import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder; 25 | import org.jboss.resteasy.client.jaxrs.ResteasyWebTarget; 26 | import org.keycloak.models.CredentialValidationOutput; 27 | import org.keycloak.models.GroupModel; 28 | import org.keycloak.models.KeycloakSession; 29 | import org.keycloak.models.RealmModel; 30 | import org.keycloak.models.RoleModel; 31 | import org.keycloak.models.UserCredentialModel; 32 | import org.keycloak.models.UserFederationProvider; 33 | import org.keycloak.models.UserFederationProviderModel; 34 | import org.keycloak.models.UserModel; 35 | 36 | import javax.ws.rs.NotFoundException; 37 | import javax.ws.rs.core.Response; 38 | import java.util.Arrays; 39 | import java.util.Collections; 40 | import java.util.List; 41 | import java.util.Map; 42 | import java.util.Set; 43 | 44 | /** 45 | * Remote API based user federation provider. 46 | * 47 | * @author Scott Rossillo 48 | */ 49 | public class RemoteUserFederationProvider implements UserFederationProvider { 50 | 51 | private static final Logger LOG = Logger.getLogger(RemoteUserFederationProvider.class); 52 | private static final Set supportedCredentialTypes = Collections.singleton(UserCredentialModel.PASSWORD); 53 | 54 | private KeycloakSession session; 55 | private UserFederationProviderModel model; 56 | private final FederatedUserService federatedUserService; 57 | 58 | private static FederatedUserService buildClient(String uri) { 59 | 60 | ResteasyClient client = new ResteasyClientBuilder().disableTrustManager().build(); 61 | ResteasyWebTarget target = client.target(uri); 62 | 63 | return target 64 | .proxyBuilder(FederatedUserService.class) 65 | .classloader(FederatedUserService.class.getClassLoader()) 66 | .build(); 67 | } 68 | 69 | public RemoteUserFederationProvider(KeycloakSession session, UserFederationProviderModel model, String uri) { 70 | this(session, model, buildClient(uri)); 71 | LOG.debugf("Using validation base URI: " + uri); 72 | } 73 | 74 | protected RemoteUserFederationProvider(KeycloakSession session, UserFederationProviderModel model, FederatedUserService federatedUserService) { 75 | this.session = session; 76 | this.model = model; 77 | this.federatedUserService = federatedUserService; 78 | } 79 | 80 | @Override 81 | public boolean synchronizeRegistrations() { 82 | return false; 83 | } 84 | 85 | @Override 86 | public UserModel register(RealmModel realm, UserModel user) { 87 | LOG.warn("User registration not supported."); 88 | return null; 89 | } 90 | 91 | @Override 92 | public boolean removeUser(RealmModel realm, UserModel user) { 93 | return true; 94 | } 95 | 96 | private UserModel createUserModel(RealmModel realm, String rawUsername) throws NotFoundException { 97 | 98 | String username = rawUsername.toLowerCase().trim(); 99 | FederatedUserModel remoteUser = federatedUserService.getUserDetails(username); 100 | LOG.infof("Creating user model for: %s", username); 101 | UserModel userModel = session.userStorage().addUser(realm, username); 102 | 103 | if (!username.equals(remoteUser.getEmail())) { 104 | throw new IllegalStateException(String.format("Local and remote users differ: [%s != %s]", username, remoteUser.getUsername())); 105 | } 106 | 107 | userModel.setFederationLink(model.getId()); 108 | userModel.setEnabled(remoteUser.isEnabled()); 109 | userModel.setEmail(username); 110 | userModel.setEmailVerified(remoteUser.isEmailVerified()); 111 | userModel.setFirstName(remoteUser.getFirstName()); 112 | userModel.setLastName(remoteUser.getLastName()); 113 | 114 | if (remoteUser.getAttributes() != null) { 115 | Map> attributes = remoteUser.getAttributes(); 116 | for (String attributeName : attributes.keySet()) 117 | userModel.setAttribute(attributeName, attributes.get(attributeName)); 118 | } 119 | 120 | if (remoteUser.getRoles() != null) { 121 | for (String role : remoteUser.getRoles()) { 122 | RoleModel roleModel = realm.getRole(role); 123 | if (roleModel != null) { 124 | userModel.grantRole(roleModel); 125 | LOG.infof("Granted user %s, role %s", username, role); 126 | } 127 | } 128 | } 129 | 130 | return userModel; 131 | } 132 | 133 | @Override 134 | public UserModel getUserByUsername(RealmModel realm, String username) { 135 | LOG.infof("Get by username: %s", username); 136 | 137 | try { 138 | return this.createUserModel(realm, username); 139 | } catch (NotFoundException ex) { 140 | LOG.errorf("Federated user not found: %s", username); 141 | return null; 142 | } 143 | } 144 | 145 | @Override 146 | public UserModel getUserByEmail(RealmModel realm, String email) { 147 | LOG.infof("Get by email: %s", email); 148 | 149 | try { 150 | return this.createUserModel(realm, email); 151 | } catch (NotFoundException ex) { 152 | LOG.error("Federated user (by email) not found: " + email); 153 | return null; 154 | } 155 | } 156 | 157 | @Override 158 | public List searchByAttributes(Map attributes, RealmModel realm, int maxResults) { 159 | LOG.debug("In searchByAttributes(): " + attributes); 160 | return Collections.emptyList(); 161 | } 162 | 163 | @Override 164 | public void preRemove(RealmModel realm) { 165 | // no-op 166 | } 167 | 168 | @Override 169 | public void preRemove(RealmModel realm, RoleModel role) { 170 | // no-op 171 | } 172 | 173 | @Override 174 | public void preRemove(RealmModel realm, GroupModel group) 175 | { 176 | // no-op 177 | } 178 | 179 | @Override 180 | public Set getSupportedCredentialTypes(UserModel user) { 181 | return supportedCredentialTypes; 182 | } 183 | 184 | @Override 185 | public Set getSupportedCredentialTypes() { 186 | return supportedCredentialTypes; 187 | } 188 | 189 | @Override 190 | public boolean validCredentials(RealmModel realm, UserModel user, List input) { 191 | 192 | LOG.infof("Validating credentials for %s", user.getUsername()); 193 | 194 | if (input == null || input.isEmpty()) { 195 | throw new IllegalArgumentException("UserCredentialModel list is empty or null!"); 196 | } 197 | 198 | UserCredentialModel credentials = input.get(0); 199 | Response response = federatedUserService.validateLogin(user.getUsername(), new UserCredentialsDto(credentials.getValue())); 200 | boolean valid = HttpStatus.SC_OK == response.getStatus(); 201 | 202 | if (valid) { 203 | user.updateCredential(credentials); 204 | user.setFederationLink(null); 205 | } 206 | 207 | return valid; 208 | } 209 | 210 | @Override 211 | public boolean validCredentials(RealmModel realm, UserModel user, UserCredentialModel... input) { 212 | return validCredentials(realm, user, Arrays.asList(input)); 213 | } 214 | 215 | @Override 216 | public CredentialValidationOutput validCredentials(RealmModel realm, UserCredentialModel credential) { 217 | return CredentialValidationOutput.failed(); 218 | } 219 | 220 | @Override 221 | public UserModel validateAndProxy(RealmModel realm, UserModel local) 222 | { 223 | return local; 224 | } 225 | 226 | @Override 227 | public boolean isValid(RealmModel realm, UserModel local) 228 | { 229 | LOG.debugf("Checking if user is valid: %s", local.getUsername()); 230 | Response response = federatedUserService.validateUserExists(local.getUsername()); 231 | LOG.infof("Checked if %s is valid: %d", local.getUsername(), response.getStatus()); 232 | return HttpStatus.SC_OK == response.getStatus(); 233 | } 234 | 235 | @Override 236 | public void close() { 237 | // no-op 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /user-migration-federation-provider/src/main/java/com/smartling/keycloak/provider/RemoteUserFederationProviderFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Smartling, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.smartling.keycloak.provider; 17 | 18 | import org.jboss.logging.Logger; 19 | import org.keycloak.Config.Scope; 20 | import org.keycloak.models.KeycloakSession; 21 | import org.keycloak.models.KeycloakSessionFactory; 22 | import org.keycloak.models.UserFederationProvider; 23 | import org.keycloak.models.UserFederationProviderFactory; 24 | import org.keycloak.models.UserFederationProviderModel; 25 | import org.keycloak.models.UserFederationSyncResult; 26 | 27 | import java.util.Collections; 28 | import java.util.Date; 29 | import java.util.Set; 30 | 31 | /** 32 | * Remote user federation provider factory. 33 | * 34 | * @author Scott Rossillo 35 | */ 36 | public class RemoteUserFederationProviderFactory implements UserFederationProviderFactory { 37 | 38 | public static final String PROVIDER_NAME = "User Migration API Provider"; 39 | public static final String PROP_USER_VALIDATION_URL = "User Validation Host Base URI"; 40 | 41 | private static final Logger LOG = Logger.getLogger(RemoteUserFederationProviderFactory.class); 42 | 43 | @Override 44 | public UserFederationProvider getInstance(KeycloakSession session, UserFederationProviderModel model) { 45 | return new RemoteUserFederationProvider(session, model, model.getConfig().get(PROP_USER_VALIDATION_URL)); 46 | } 47 | 48 | @Override 49 | public Set getConfigurationOptions() { 50 | LOG.warn("Returning configuration options"); 51 | return Collections.singleton(PROP_USER_VALIDATION_URL); 52 | } 53 | 54 | @Override 55 | public String getId() { 56 | return PROVIDER_NAME; 57 | } 58 | 59 | @Override 60 | public UserFederationProvider create(KeycloakSession session) { 61 | return null; 62 | } 63 | 64 | @Override 65 | public void init(Scope config) { 66 | // no-op 67 | } 68 | 69 | @Override 70 | public void postInit(KeycloakSessionFactory factory) { 71 | // no-op 72 | } 73 | 74 | @Override 75 | public void close() { 76 | // no-op 77 | } 78 | 79 | @Override 80 | public UserFederationSyncResult syncAllUsers(KeycloakSessionFactory sessionFactory, String realmId, UserFederationProviderModel model) 81 | { 82 | throw new UnsupportedOperationException("This federation provider doesn't support syncAllUsers()"); 83 | } 84 | 85 | @Override 86 | public UserFederationSyncResult syncChangedUsers(KeycloakSessionFactory sessionFactory, String realmId, UserFederationProviderModel model, Date lastSync) 87 | { 88 | throw new UnsupportedOperationException("This federation provider doesn't support syncChangedUsers()"); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /user-migration-federation-provider/src/main/resources/META-INF/services/org.keycloak.models.UserFederationProviderFactory: -------------------------------------------------------------------------------- 1 | com.smartling.keycloak.provider.RemoteUserFederationProviderFactory -------------------------------------------------------------------------------- /user-migration-federation-provider/src/test/java/com/smartling/keycloak/provider/RemoteUserFederationProviderFactoryTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Smartling, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.smartling.keycloak.provider; 17 | 18 | import org.junit.Before; 19 | import org.junit.Test; 20 | import org.keycloak.Config.Scope; 21 | import org.keycloak.models.KeycloakSession; 22 | import org.keycloak.models.KeycloakSessionFactory; 23 | import org.keycloak.models.UserFederationProvider; 24 | import org.keycloak.models.UserFederationProviderModel; 25 | import org.mockito.Mock; 26 | import org.mockito.MockitoAnnotations; 27 | import org.testng.collections.Lists; 28 | 29 | import java.util.Collections; 30 | import java.util.Date; 31 | import java.util.Set; 32 | import java.util.UUID; 33 | 34 | import static org.junit.Assert.*; 35 | import static org.mockito.Mockito.*; 36 | 37 | /** 38 | * Remote user federation provider factory tests. 39 | */ 40 | public class RemoteUserFederationProviderFactoryTest { 41 | 42 | private RemoteUserFederationProviderFactory factory; 43 | 44 | @Mock 45 | private KeycloakSessionFactory keycloakSessionFactory; 46 | 47 | @Mock 48 | private KeycloakSession keycloakSession; 49 | 50 | @Mock 51 | private Scope config; 52 | 53 | @Mock 54 | private UserFederationProviderModel userFederationProviderModel; 55 | 56 | @Before 57 | public void setUp() throws Exception { 58 | MockitoAnnotations.initMocks(this); 59 | factory = new RemoteUserFederationProviderFactory(); 60 | when(userFederationProviderModel.getConfig()) 61 | .thenReturn(Collections.singletonMap(RemoteUserFederationProviderFactory.PROP_USER_VALIDATION_URL, "https://fake.com")); 62 | } 63 | 64 | @Test 65 | public void testGetInstance() throws Exception { 66 | UserFederationProvider provider = factory.getInstance(keycloakSession, userFederationProviderModel); 67 | assertNotNull(provider); 68 | assertTrue(provider instanceof RemoteUserFederationProvider); 69 | } 70 | 71 | @Test 72 | public void testGetConfigurationOptions() throws Exception { 73 | Set options = factory.getConfigurationOptions(); 74 | assertNotNull(options); 75 | assertEquals(1, options.size()); 76 | assertEquals(RemoteUserFederationProviderFactory.PROP_USER_VALIDATION_URL, Lists.newArrayList(options).get(0)); 77 | } 78 | 79 | @Test 80 | public void testGetId() throws Exception { 81 | assertEquals(RemoteUserFederationProviderFactory.PROVIDER_NAME, factory.getId()); 82 | } 83 | 84 | @Test(expected = UnsupportedOperationException.class) 85 | public void testSyncAllUsers() throws Exception { 86 | String realmId = UUID.randomUUID().toString(); 87 | factory.syncAllUsers(keycloakSessionFactory, realmId, userFederationProviderModel); 88 | verifyZeroInteractions(keycloakSession, keycloakSessionFactory, realmId, userFederationProviderModel); 89 | } 90 | 91 | @Test(expected = UnsupportedOperationException.class) 92 | public void testSyncChangedUsers() throws Exception { 93 | String realmId = UUID.randomUUID().toString(); 94 | Date lastSync = new Date(); 95 | factory.syncChangedUsers(keycloakSessionFactory, realmId, userFederationProviderModel, lastSync); 96 | verifyZeroInteractions(keycloakSession, keycloakSessionFactory, realmId, userFederationProviderModel, lastSync); 97 | } 98 | 99 | @Test 100 | public void testCreate() throws Exception { 101 | assertNull(factory.create(keycloakSession)); 102 | } 103 | 104 | @Test 105 | public void testInit() throws Exception { 106 | factory.init(config); 107 | verifyZeroInteractions(config); 108 | } 109 | 110 | @Test 111 | public void testPostInit() throws Exception { 112 | factory.postInit(keycloakSessionFactory); 113 | verifyZeroInteractions(keycloakSession, keycloakSessionFactory); 114 | } 115 | 116 | @Test 117 | public void testClose() throws Exception { 118 | factory.close(); 119 | verifyZeroInteractions(keycloakSession, keycloakSessionFactory); 120 | } 121 | } -------------------------------------------------------------------------------- /user-migration-federation-provider/src/test/java/com/smartling/keycloak/provider/RemoteUserFederationProviderTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Smartling, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.smartling.keycloak.provider; 17 | 18 | import com.smartling.keycloak.federation.FederatedUserModel; 19 | import com.smartling.keycloak.federation.FederatedUserService; 20 | import com.smartling.keycloak.federation.UserCredentialsDto; 21 | import org.junit.Before; 22 | import org.junit.Test; 23 | import org.keycloak.models.CredentialValidationOutput; 24 | import org.keycloak.models.KeycloakSession; 25 | import org.keycloak.models.RealmModel; 26 | import org.keycloak.models.RoleModel; 27 | import org.keycloak.models.UserCredentialModel; 28 | import org.keycloak.models.UserFederationManager; 29 | import org.keycloak.models.UserFederationProviderModel; 30 | import org.keycloak.models.UserModel; 31 | import org.keycloak.models.UserProvider; 32 | import org.mockito.Mock; 33 | import org.mockito.MockitoAnnotations; 34 | import org.mockito.internal.util.collections.Sets; 35 | 36 | import javax.ws.rs.core.Response; 37 | import javax.ws.rs.core.Response.Status; 38 | import java.util.Arrays; 39 | import java.util.Collections; 40 | import java.util.UUID; 41 | 42 | import static org.junit.Assert.*; 43 | import static org.mockito.Mockito.*; 44 | 45 | /** 46 | * Remote user federation provider test cases. 47 | */ 48 | public class RemoteUserFederationProviderTest { 49 | 50 | private static final String FEDERATED_USER_KNOWN_EMAIL = "wa+user@smartling.com"; 51 | private static final String FEDERATED_USER_KNOWN_USERNAME = FEDERATED_USER_KNOWN_EMAIL; 52 | private static final String FEDERATED_USER_KNOWN_PASSWORD = UUID.randomUUID().toString(); 53 | private static final UserCredentialsDto FEDERATED_USER_CREDENTIALS_DTO = new UserCredentialsDto(FEDERATED_USER_KNOWN_PASSWORD); 54 | private static final String FEDERATED_USER_ROLE = "ROLE_FOO"; 55 | 56 | private static final String KEYCLOAK_EXISTING_USER_EMAIL = "keycloak+user@smartling.com"; 57 | private static final String KEYCLOAK_EXISTING_USER_USERNAME = KEYCLOAK_EXISTING_USER_EMAIL; 58 | 59 | private static final String USER_ID = UUID.randomUUID().toString(); 60 | 61 | private RemoteUserFederationProvider provider; 62 | 63 | @Mock 64 | private FederatedUserModel federatedUserModel; 65 | 66 | @Mock 67 | private FederatedUserService federatedUserService; 68 | 69 | @Mock 70 | private KeycloakSession keycloakSession; 71 | 72 | @Mock 73 | private RealmModel realmModel; 74 | 75 | @Mock 76 | private UserFederationProviderModel userFederationProviderModel; 77 | 78 | @Mock 79 | private UserModel userModel; 80 | 81 | @Mock 82 | private UserProvider userProvider; 83 | 84 | @Mock 85 | private UserFederationManager userFederationManager; 86 | 87 | @Before 88 | public void setUp() throws Exception { 89 | MockitoAnnotations.initMocks(this); 90 | provider = new RemoteUserFederationProvider(keycloakSession, userFederationProviderModel, federatedUserService); 91 | when(userModel.getUsername()).thenReturn(FEDERATED_USER_KNOWN_USERNAME); 92 | when(userModel.getEmail()).thenReturn(FEDERATED_USER_KNOWN_EMAIL); 93 | when(userModel.getId()).thenReturn(USER_ID); 94 | 95 | when(keycloakSession.userStorage()).thenReturn(userProvider); 96 | 97 | when(userProvider.addUser(eq(realmModel), eq(FEDERATED_USER_KNOWN_USERNAME))).thenReturn(mock(UserModel.class)); 98 | when(userProvider.getUserByUsername(eq(KEYCLOAK_EXISTING_USER_USERNAME), eq(realmModel))).thenReturn(userModel); 99 | when(userProvider.getUserByEmail(eq(KEYCLOAK_EXISTING_USER_EMAIL), eq(realmModel))).thenReturn(userModel); 100 | when(userProvider.getUserById(eq(USER_ID), eq(realmModel))).thenReturn(userModel); 101 | 102 | when(federatedUserModel.getEmail()).thenReturn(FEDERATED_USER_KNOWN_EMAIL); 103 | when(federatedUserModel.getUsername()).thenReturn(FEDERATED_USER_KNOWN_USERNAME); 104 | 105 | 106 | when(federatedUserService.getUserDetails(eq(FEDERATED_USER_KNOWN_EMAIL))).thenReturn(federatedUserModel); 107 | when(federatedUserService.getUserDetails(eq(FEDERATED_USER_KNOWN_USERNAME))).thenReturn(federatedUserModel); 108 | when(federatedUserService.validateUserExists(eq(FEDERATED_USER_KNOWN_USERNAME))).thenReturn(Response.accepted().build()); 109 | when(federatedUserService.validateLogin(eq(FEDERATED_USER_KNOWN_USERNAME), any(UserCredentialsDto.class))).thenReturn(Response.status(Status.PRECONDITION_FAILED).build()); 110 | when(federatedUserService.validateLogin(eq(FEDERATED_USER_KNOWN_USERNAME), eq(FEDERATED_USER_CREDENTIALS_DTO))).thenReturn(Response.ok().build()); 111 | 112 | when(federatedUserService.validateUserExists(anyString())).thenReturn(Response.status(Status.PRECONDITION_FAILED).build()); 113 | when(federatedUserService.validateUserExists(eq(FEDERATED_USER_KNOWN_USERNAME))).thenReturn(Response.status(Status.PRECONDITION_FAILED).build()); 114 | } 115 | 116 | @Test 117 | public void testProxy() throws Exception { 118 | assertEquals(userModel, provider.validateAndProxy(realmModel, userModel)); 119 | } 120 | 121 | @Test 122 | public void testSynchronizeRegistrations() throws Exception { 123 | provider.synchronizeRegistrations(); 124 | verifyZeroInteractions(keycloakSession, realmModel, userModel); 125 | } 126 | 127 | @Test 128 | public void testRegister() throws Exception { 129 | assertNull(provider.register(realmModel, userModel)); 130 | verifyZeroInteractions(keycloakSession, realmModel, userModel, federatedUserService); 131 | } 132 | 133 | @Test 134 | public void testRemoveUser() throws Exception { 135 | assertTrue(provider.removeUser(realmModel, userModel)); 136 | verifyZeroInteractions(keycloakSession); 137 | } 138 | 139 | @Test 140 | public void testGetUserByUsername() throws Exception { 141 | assertNotNull(provider.getUserByUsername(realmModel, FEDERATED_USER_KNOWN_USERNAME)); 142 | verify(federatedUserService).getUserDetails(eq(FEDERATED_USER_KNOWN_EMAIL)); 143 | verify(federatedUserService, never()).getUserDetails(eq(KEYCLOAK_EXISTING_USER_USERNAME)); 144 | } 145 | 146 | @Test 147 | public void testGetUserByUsernameWithRole() throws Exception { 148 | 149 | when(federatedUserModel.getRoles()).thenReturn(Sets.newSet(FEDERATED_USER_ROLE)); 150 | when(realmModel.getRole(FEDERATED_USER_ROLE)).thenReturn(mock(RoleModel.class)); 151 | when(keycloakSession.userStorage().addUser(eq(realmModel), eq(FEDERATED_USER_KNOWN_USERNAME))).thenReturn(userModel); 152 | 153 | assertNotNull(provider.getUserByUsername(realmModel, FEDERATED_USER_KNOWN_USERNAME)); 154 | verify(federatedUserService).getUserDetails(eq(FEDERATED_USER_KNOWN_EMAIL)); 155 | verify(userModel).grantRole(any(RoleModel.class)); 156 | } 157 | 158 | @Test 159 | public void testGetUserByUsernameWithNullRoles() throws Exception { 160 | 161 | when(federatedUserModel.getRoles()).thenReturn(null); 162 | when(keycloakSession.userStorage().addUser(eq(realmModel), eq(FEDERATED_USER_KNOWN_USERNAME))).thenReturn(userModel); 163 | 164 | assertNotNull(provider.getUserByUsername(realmModel, FEDERATED_USER_KNOWN_USERNAME)); 165 | verify(federatedUserService).getUserDetails(eq(FEDERATED_USER_KNOWN_EMAIL)); 166 | verify(realmModel, never()).getRole(anyString()); 167 | verify(userModel, never()).grantRole(any(RoleModel.class)); 168 | } 169 | 170 | @Test 171 | public void testGetUserByUsernameMixedCase() throws Exception { 172 | assertNotNull(provider.getUserByUsername(realmModel, FEDERATED_USER_KNOWN_USERNAME.toUpperCase())); 173 | verify(federatedUserService).getUserDetails(eq(FEDERATED_USER_KNOWN_USERNAME)); 174 | } 175 | 176 | @Test 177 | public void testGetUserByUsernameWithLeadingSpace() throws Exception { 178 | assertNotNull(provider.getUserByUsername(realmModel, " " + FEDERATED_USER_KNOWN_USERNAME)); 179 | verify(federatedUserService).getUserDetails(eq(FEDERATED_USER_KNOWN_USERNAME)); 180 | } 181 | 182 | @Test 183 | public void testGetUserByUsernameWithLeadingAndTrailingSpace() throws Exception { 184 | assertNotNull(provider.getUserByUsername(realmModel, " " + FEDERATED_USER_KNOWN_USERNAME + " \t")); 185 | verify(federatedUserService).getUserDetails(eq(FEDERATED_USER_KNOWN_USERNAME)); 186 | } 187 | 188 | @Test 189 | public void testGetUserByUsernameWithTrailingSpace() throws Exception { 190 | assertNotNull(provider.getUserByUsername(realmModel, FEDERATED_USER_KNOWN_USERNAME + " ")); 191 | verify(federatedUserService).getUserDetails(eq(FEDERATED_USER_KNOWN_USERNAME)); 192 | } 193 | 194 | @Test 195 | public void testGetUserByUsernameWithAttributes() throws Exception { 196 | provider.getUserByUsername(realmModel, FEDERATED_USER_KNOWN_USERNAME); 197 | verify(federatedUserModel, times(2)).getAttributes(); 198 | } 199 | 200 | @Test 201 | public void testGetUserByUsernameWithoutAttributes() throws Exception { 202 | UserModel user = provider.getUserByUsername(realmModel, FEDERATED_USER_KNOWN_USERNAME); 203 | verify(federatedUserModel, times(2)).getAttributes(); 204 | verify(user, never()).setAttribute(anyString(), anyListOf(String.class)); 205 | } 206 | 207 | @Test 208 | public void testGetUserByEmail() throws Exception { 209 | assertNotNull(provider.getUserByUsername(realmModel, FEDERATED_USER_KNOWN_EMAIL)); 210 | verify(federatedUserService).getUserDetails(eq(FEDERATED_USER_KNOWN_EMAIL)); 211 | } 212 | 213 | @Test 214 | public void testGetUserByEmailMixedCase() throws Exception { 215 | assertNotNull(provider.getUserByUsername(realmModel, FEDERATED_USER_KNOWN_EMAIL.toUpperCase())); 216 | verify(federatedUserService).getUserDetails(eq(FEDERATED_USER_KNOWN_EMAIL)); 217 | } 218 | 219 | @Test 220 | public void testSearchByAttributes() throws Exception { 221 | assertTrue(provider.searchByAttributes(null, realmModel, 10).isEmpty()); 222 | verifyZeroInteractions(federatedUserService); 223 | } 224 | 225 | @Test 226 | public void testIsValid() throws Exception { 227 | 228 | when(userModel.getUsername()).thenReturn(FEDERATED_USER_KNOWN_USERNAME); 229 | when(federatedUserService.validateUserExists(eq(FEDERATED_USER_KNOWN_USERNAME))).thenReturn(Response.ok().build()); 230 | 231 | assertTrue(provider.isValid(realmModel, userModel)); 232 | verify(federatedUserService).validateUserExists(eq(FEDERATED_USER_KNOWN_USERNAME)); 233 | } 234 | 235 | @Test 236 | public void testGetSupportedCredentialTypes() throws Exception { 237 | assertEquals(Collections.singleton(UserCredentialModel.PASSWORD), provider.getSupportedCredentialTypes(userModel)); 238 | } 239 | 240 | @Test 241 | public void testGetSupportedCredentialTypes1() throws Exception { 242 | assertEquals(Collections.singleton(UserCredentialModel.PASSWORD), provider.getSupportedCredentialTypes()); 243 | } 244 | 245 | @Test 246 | public void testValidCredentialsVarArg() throws Exception { 247 | assertTrue(provider.validCredentials(realmModel, userModel, UserCredentialModel.password(FEDERATED_USER_KNOWN_PASSWORD))); 248 | } 249 | 250 | @Test 251 | public void testValidCredentialsList() throws Exception { 252 | assertFalse(provider.validCredentials(realmModel, userModel, Arrays.asList(UserCredentialModel.password(UUID.randomUUID().toString())))); 253 | assertTrue(provider.validCredentials(realmModel, userModel, Arrays.asList(UserCredentialModel.password(FEDERATED_USER_KNOWN_PASSWORD)))); 254 | } 255 | 256 | @Test 257 | public void testValidCredentialsListInvalid() throws Exception { 258 | assertFalse(provider.validCredentials(realmModel, userModel, Arrays.asList(UserCredentialModel.password(UUID.randomUUID().toString())))); 259 | } 260 | 261 | @Test 262 | public void testValidCredentialsNoUser() throws Exception { 263 | CredentialValidationOutput result = provider.validCredentials(realmModel, UserCredentialModel.password(UUID.randomUUID().toString())); 264 | assertEquals(CredentialValidationOutput.failed().getAuthStatus(), result.getAuthStatus()); 265 | } 266 | 267 | @Test 268 | public void testClose() throws Exception { 269 | provider.close(); 270 | verifyZeroInteractions(keycloakSession, realmModel, federatedUserService); 271 | } 272 | } -------------------------------------------------------------------------------- /user-model/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Smartling, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | plugins { 18 | id "nebula.provided-base" version "3.0.3" 19 | } 20 | 21 | sourceCompatibility = 1.7 22 | targetCompatibility = 1.7 23 | 24 | dependencies { 25 | provided "javax.ws.rs:javax.ws.rs-api:2.0.1" 26 | } 27 | 28 | -------------------------------------------------------------------------------- /user-model/src/main/java/com/smartling/keycloak/federation/FederatedUserModel.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Smartling, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.smartling.keycloak.federation; 17 | 18 | import java.io.Serializable; 19 | import java.util.List; 20 | import java.util.Map; 21 | import java.util.Set; 22 | 23 | /** 24 | * Provides a federated user model. 25 | * 26 | * @author Scott Rossillo 27 | */ 28 | public class FederatedUserModel implements Serializable 29 | { 30 | private String username; 31 | private String email; 32 | private boolean emailVerified; 33 | private boolean enabled; 34 | private String firstName; 35 | private String lastName; 36 | private Map> attributes; 37 | private Set roles; 38 | 39 | public String getUsername() 40 | { 41 | return username; 42 | } 43 | 44 | public void setUsername(String username) 45 | { 46 | this.username = username; 47 | } 48 | 49 | public boolean isEnabled() 50 | { 51 | return enabled; 52 | } 53 | 54 | public void setEnabled(boolean enabled) 55 | { 56 | this.enabled = enabled; 57 | } 58 | 59 | public String getFirstName() 60 | { 61 | return firstName; 62 | } 63 | 64 | public void setFirstName(String firstName) 65 | { 66 | this.firstName = firstName; 67 | } 68 | 69 | public String getLastName() 70 | { 71 | return lastName; 72 | } 73 | 74 | public void setLastName(String lastName) 75 | { 76 | this.lastName = lastName; 77 | } 78 | 79 | public String getEmail() 80 | { 81 | return email; 82 | } 83 | 84 | public void setEmail(String email) 85 | { 86 | this.email = email; 87 | } 88 | 89 | public boolean isEmailVerified() 90 | { 91 | return emailVerified; 92 | } 93 | 94 | public void setEmailVerified(boolean emailVerified) 95 | { 96 | this.emailVerified = emailVerified; 97 | } 98 | 99 | public Map> getAttributes() 100 | { 101 | return attributes; 102 | } 103 | 104 | public void setAttributes(Map> attributes) 105 | { 106 | this.attributes = attributes; 107 | } 108 | 109 | public Set getRoles() { 110 | return roles; 111 | } 112 | 113 | public void setRoles(Set roles) { 114 | this.roles = roles; 115 | } 116 | 117 | } 118 | -------------------------------------------------------------------------------- /user-model/src/main/java/com/smartling/keycloak/federation/FederatedUserService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Smartling, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.smartling.keycloak.federation; 17 | 18 | import javax.ws.rs.Consumes; 19 | import javax.ws.rs.GET; 20 | import javax.ws.rs.HEAD; 21 | import javax.ws.rs.POST; 22 | import javax.ws.rs.Path; 23 | import javax.ws.rs.PathParam; 24 | import javax.ws.rs.Produces; 25 | import javax.ws.rs.core.MediaType; 26 | import javax.ws.rs.core.Response; 27 | 28 | /** 29 | * Federated user service. 30 | * 31 | * @author Scott Rossillo 32 | */ 33 | @Consumes(MediaType.APPLICATION_JSON) 34 | @Produces(MediaType.APPLICATION_JSON) 35 | public interface FederatedUserService 36 | { 37 | @GET 38 | @Path("/api/users/{username}/") 39 | FederatedUserModel getUserDetails(@PathParam("username") String username); 40 | 41 | @HEAD 42 | @Path("/api/users/{username}/") 43 | Response validateUserExists(@PathParam("username") String username); 44 | 45 | @POST 46 | @Path("/api/users/{username}/") 47 | Response validateLogin(@PathParam("username") String username, UserCredentialsDto passwordDto); 48 | } 49 | -------------------------------------------------------------------------------- /user-model/src/main/java/com/smartling/keycloak/federation/UserCredentialsDto.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Smartling, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.smartling.keycloak.federation; 17 | 18 | import java.io.Serializable; 19 | import java.util.Objects; 20 | 21 | /** 22 | * Simple password data transfer object. 23 | * 24 | * @author Scott Rossillo 25 | */ 26 | public class UserCredentialsDto implements Serializable 27 | { 28 | private String password; 29 | 30 | public UserCredentialsDto() 31 | { 32 | } 33 | 34 | public UserCredentialsDto(String password) 35 | { 36 | this.password = password; 37 | } 38 | 39 | @Override 40 | public boolean equals(Object o) 41 | { 42 | if (this == o) 43 | { 44 | return true; 45 | } 46 | if (!(o instanceof UserCredentialsDto)) 47 | { 48 | return false; 49 | } 50 | UserCredentialsDto that = (UserCredentialsDto) o; 51 | return Objects.equals(getPassword(), that.getPassword()); 52 | } 53 | 54 | @Override 55 | public int hashCode() 56 | { 57 | return Objects.hash(getPassword()); 58 | } 59 | 60 | public String getPassword() 61 | { 62 | return password; 63 | } 64 | 65 | public void setPassword(String password) 66 | { 67 | this.password = password; 68 | } 69 | } 70 | --------------------------------------------------------------------------------