diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6240411 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.iml +.idea +target diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md index 9d372a3..dcc789f 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,24 @@ # Qproj4MP -## Quarking a generated MicroProfile projec +## Quarking a generated MicroProfile project -Note: The contents of the src directory of this project came from generating an MP 2.1 project for Thorntail V2 using the [MicroProfile Starter](https://start.microprofile.io). +This is a demonstration project that consists of 2 subprojects illustrating MicroProfile 2.2 features. +This project depends on KeyCloak for the MP-JWT token generation. To launch Keycloak in a docker +container run: -First, clone this project to your Mac, build it by changing directory to it and then running this command to exexute the application in development mode: +```bash +docker run -d --name keycloak -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=admin -p 8180:8180 -v `pwd`/demo-realm.json:/config/quarkus-kc-quickstart.json -it jboss/keycloak:6.0.1 -b 0.0.0.0 -Djboss.http.port=8180 -Dkeycloak.migration.action=import -Dkeycloak.migration.provider=singleFile -Dkeycloak.migration.file=/config/quarkus-kc-quickstart.json -Dkeycloak.migration.strategy=OVERWRITE_EXISTING +``` + +This project depends on Jaeger for the collection of the MP OpenTracing information. To launch +the Jaeger tracing system in a docker container run: +```bash +docker run -d -e COLLECTOR_ZIPKIN_HTTP_PORT=9411 -p 5775:5775/udp -p 6831:6831/udp -p 6832:6832/udp -p 5778:5778 -p 16686:16686 -p 14268:14268 -p 9411:9411 jaegertracing/all-in-one:latest +``` + +### Web application and Demo1 image +First, clone this project to your Mac, build it by cd to the web subdirectory to it and then running this + command to exexute the web application in development mode: ./mvnw compile quarkus:dev @@ -12,22 +26,40 @@ Once application is up and running, point your browser to: http://localhost:8080/index.html +### Demo2 image +Second, open a new shell and cd to the protected subdirectory, and then run the app using +./mvnw compile quarkus:dev + +The demo2 image contains services that the demo1/web image access. + ### Generating an executable JAR -To generate the standalone executable JAR file, run the following from the command line: +To generate the standalone executable JAR file, run the following from the command line in either the +web or proteced subprojects: ./mvn clean package -Once the build is finished, to run the jar, enter: +Once the build is finished, to run the web subproject jar, enter: -java -jar target/demo-1.0-SNAPSHOT-runner.jar +java -jar target/demo-web-runner.jar + +To run the protected subproject jar, enter: + +java -jar target/demo-protected-runner.jar ### Compiling to native -To compile the project to native code, run the following from the command line: +To compile the project to native code, you need to have the [GraalVM](https://github.com/oracle/graal/releases) installed +and a GRAALVM_HOME environment variable set to the location of the GraalVM home. + +Once you have GraalVM install, build the native executable by running the following from the command line +in either the web or protected subprojects: ./mvnw package -Pnative Once the compilation is finished, to run the executable, enter: -./target/demo-1.0-SNAPSHOT-runner -Dinjected.value="hi" -Dvalue="hola" +./target/demo-runner -Dinjected.value="hi" -Dvalue="hola" + +## Testing the Endpoints +Use the tabs in the web app to access the URLs for the various services. diff --git a/demo-realm.json b/demo-realm.json new file mode 100644 index 0000000..df855c1 --- /dev/null +++ b/demo-realm.json @@ -0,0 +1,1665 @@ +{ + "id" : "quarkus-quickstart", + "realm" : "quarkus-quickstart", + "notBefore" : 0, + "revokeRefreshToken" : false, + "refreshTokenMaxReuse" : 0, + "accessTokenLifespan" : 300, + "accessTokenLifespanForImplicitFlow" : 900, + "ssoSessionIdleTimeout" : 1800, + "ssoSessionMaxLifespan" : 36000, + "ssoSessionIdleTimeoutRememberMe" : 0, + "ssoSessionMaxLifespanRememberMe" : 0, + "offlineSessionIdleTimeout" : 2592000, + "offlineSessionMaxLifespanEnabled" : false, + "offlineSessionMaxLifespan" : 5184000, + "accessCodeLifespan" : 60, + "accessCodeLifespanUserAction" : 300, + "accessCodeLifespanLogin" : 1800, + "actionTokenGeneratedByAdminLifespan" : 43200, + "actionTokenGeneratedByUserLifespan" : 300, + "enabled" : true, + "sslRequired" : "external", + "registrationAllowed" : false, + "registrationEmailAsUsername" : false, + "rememberMe" : false, + "verifyEmail" : false, + "loginWithEmailAllowed" : true, + "duplicateEmailsAllowed" : false, + "resetPasswordAllowed" : false, + "editUsernameAllowed" : false, + "bruteForceProtected" : false, + "permanentLockout" : false, + "maxFailureWaitSeconds" : 900, + "minimumQuickLoginWaitSeconds" : 60, + "waitIncrementSeconds" : 60, + "quickLoginCheckMilliSeconds" : 1000, + "maxDeltaTimeSeconds" : 43200, + "failureFactor" : 30, + "roles" : { + "realm" : [ { + "id" : "526004d6-8e40-40d6-bd09-df8581beb906", + "name" : "WorldClockSubscriber", + "description" : "A subscriber to the WorldClockApi service", + "composite" : false, + "clientRole" : false, + "containerId" : "quarkus-quickstart", + "attributes" : { } + }, { + "id" : "4875a4b5-fcdc-490e-83f0-e5870ac1d95e", + "name" : "uma_authorization", + "description" : "${role_uma_authorization}", + "composite" : false, + "clientRole" : false, + "containerId" : "quarkus-quickstart", + "attributes" : { } + }, { + "id" : "ba90acf5-2106-43b5-b7f4-7d815e52bb4d", + "name" : "user", + "composite" : false, + "clientRole" : false, + "containerId" : "quarkus-quickstart", + "attributes" : { } + }, { + "id" : "c975ba8d-0714-4f40-95f3-dca1508aae34", + "name" : "GOTO-attendee", + "description" : "An attendee of the GOTO conference", + "composite" : false, + "clientRole" : false, + "containerId" : "quarkus-quickstart", + "attributes" : { } + }, { + "id" : "ff5dccc5-71e7-4d33-8837-2e31bebba7bc", + "name" : "offline_access", + "description" : "${role_offline-access}", + "composite" : false, + "clientRole" : false, + "containerId" : "quarkus-quickstart", + "attributes" : { } + } ], + "client" : { + "quarkus-front" : [ ], + "microprofile-jwt-client" : [ { + "id" : "705127d4-30ab-470e-8ef5-5a993dca296a", + "name" : "MPJWTCompatible", + "description" : "Indicates an MP-JWT compatible bearer token", + "composite" : false, + "clientRole" : true, + "containerId" : "66557d8d-651b-4604-b865-a4b08eb4716a", + "attributes" : { } + } ], + "realm-management" : [ { + "id" : "7cb5c26a-eed3-4c7e-a38c-2c46b28fee20", + "name" : "create-client", + "description" : "${role_create-client}", + "composite" : false, + "clientRole" : true, + "containerId" : "df94d122-f41f-4f2f-80fc-f1395124cc05", + "attributes" : { } + }, { + "id" : "948c986f-52ce-4695-a11b-8403e6ab61ba", + "name" : "impersonation", + "description" : "${role_impersonation}", + "composite" : false, + "clientRole" : true, + "containerId" : "df94d122-f41f-4f2f-80fc-f1395124cc05", + "attributes" : { } + }, { + "id" : "dae52537-3d4f-466e-882d-337ad218cbd3", + "name" : "realm-admin", + "description" : "${role_realm-admin}", + "composite" : true, + "composites" : { + "client" : { + "realm-management" : [ "create-client", "impersonation", "manage-identity-providers", "view-clients", "view-identity-providers", "view-realm", "query-groups", "manage-events", "view-authorization", "view-users", "manage-realm", "query-realms", "query-clients", "manage-users", "view-events", "manage-clients", "manage-authorization", "query-users" ] + } + }, + "clientRole" : true, + "containerId" : "df94d122-f41f-4f2f-80fc-f1395124cc05", + "attributes" : { } + }, { + "id" : "584eedd7-0fe8-44b1-96a3-dfea690b9362", + "name" : "manage-identity-providers", + "description" : "${role_manage-identity-providers}", + "composite" : false, + "clientRole" : true, + "containerId" : "df94d122-f41f-4f2f-80fc-f1395124cc05", + "attributes" : { } + }, { + "id" : "eab3546c-5db9-4465-9837-f363a33b5762", + "name" : "view-clients", + "description" : "${role_view-clients}", + "composite" : true, + "composites" : { + "client" : { + "realm-management" : [ "query-clients" ] + } + }, + "clientRole" : true, + "containerId" : "df94d122-f41f-4f2f-80fc-f1395124cc05", + "attributes" : { } + }, { + "id" : "74412e18-7f8e-40b0-b925-0fc690753fc0", + "name" : "view-identity-providers", + "description" : "${role_view-identity-providers}", + "composite" : false, + "clientRole" : true, + "containerId" : "df94d122-f41f-4f2f-80fc-f1395124cc05", + "attributes" : { } + }, { + "id" : "48114796-cd32-421e-ab8d-55e4bed300b5", + "name" : "view-realm", + "description" : "${role_view-realm}", + "composite" : false, + "clientRole" : true, + "containerId" : "df94d122-f41f-4f2f-80fc-f1395124cc05", + "attributes" : { } + }, { + "id" : "889b4bec-84a7-4158-81d1-ab42df8743cb", + "name" : "manage-events", + "description" : "${role_manage-events}", + "composite" : false, + "clientRole" : true, + "containerId" : "df94d122-f41f-4f2f-80fc-f1395124cc05", + "attributes" : { } + }, { + "id" : "389ee808-3b86-4a4e-a0c6-1887ff174bf2", + "name" : "query-groups", + "description" : "${role_query-groups}", + "composite" : false, + "clientRole" : true, + "containerId" : "df94d122-f41f-4f2f-80fc-f1395124cc05", + "attributes" : { } + }, { + "id" : "a74d54f6-883d-4faa-9817-c73182cd893e", + "name" : "view-authorization", + "description" : "${role_view-authorization}", + "composite" : false, + "clientRole" : true, + "containerId" : "df94d122-f41f-4f2f-80fc-f1395124cc05", + "attributes" : { } + }, { + "id" : "89b3e6c0-6e1b-46be-82e3-ad3cc3a15635", + "name" : "view-users", + "description" : "${role_view-users}", + "composite" : true, + "composites" : { + "client" : { + "realm-management" : [ "query-groups", "query-users" ] + } + }, + "clientRole" : true, + "containerId" : "df94d122-f41f-4f2f-80fc-f1395124cc05", + "attributes" : { } + }, { + "id" : "5ea916a3-4028-4821-8f76-9ae1d476decd", + "name" : "manage-realm", + "description" : "${role_manage-realm}", + "composite" : false, + "clientRole" : true, + "containerId" : "df94d122-f41f-4f2f-80fc-f1395124cc05", + "attributes" : { } + }, { + "id" : "c8d33bb9-62f4-471b-924e-60bc631adaaf", + "name" : "query-clients", + "description" : "${role_query-clients}", + "composite" : false, + "clientRole" : true, + "containerId" : "df94d122-f41f-4f2f-80fc-f1395124cc05", + "attributes" : { } + }, { + "id" : "8df853fc-bd17-494e-85cb-43c40cec2e59", + "name" : "query-realms", + "description" : "${role_query-realms}", + "composite" : false, + "clientRole" : true, + "containerId" : "df94d122-f41f-4f2f-80fc-f1395124cc05", + "attributes" : { } + }, { + "id" : "ff1df86c-2ded-42ce-b2a8-d3ad069aad3d", + "name" : "manage-users", + "description" : "${role_manage-users}", + "composite" : false, + "clientRole" : true, + "containerId" : "df94d122-f41f-4f2f-80fc-f1395124cc05", + "attributes" : { } + }, { + "id" : "799e754b-8858-43b7-bb06-e044a65db5d8", + "name" : "view-events", + "description" : "${role_view-events}", + "composite" : false, + "clientRole" : true, + "containerId" : "df94d122-f41f-4f2f-80fc-f1395124cc05", + "attributes" : { } + }, { + "id" : "2ca8c8f4-a2c0-447b-99ba-096757ba5b8c", + "name" : "manage-clients", + "description" : "${role_manage-clients}", + "composite" : false, + "clientRole" : true, + "containerId" : "df94d122-f41f-4f2f-80fc-f1395124cc05", + "attributes" : { } + }, { + "id" : "e702a177-17e0-472e-8a33-0f94d905510a", + "name" : "manage-authorization", + "description" : "${role_manage-authorization}", + "composite" : false, + "clientRole" : true, + "containerId" : "df94d122-f41f-4f2f-80fc-f1395124cc05", + "attributes" : { } + }, { + "id" : "f5d5670c-5af4-440e-9174-2e91a2d52189", + "name" : "query-users", + "description" : "${role_query-users}", + "composite" : false, + "clientRole" : true, + "containerId" : "df94d122-f41f-4f2f-80fc-f1395124cc05", + "attributes" : { } + } ], + "security-admin-console" : [ ], + "admin-cli" : [ ], + "broker" : [ { + "id" : "b6824708-9a54-4de1-b1e3-8c6e82b37236", + "name" : "read-token", + "description" : "${role_read-token}", + "composite" : false, + "clientRole" : true, + "containerId" : "d36113c3-480e-4f46-98b0-95ff47e9f220", + "attributes" : { } + } ], + "account" : [ { + "id" : "0c47191a-cf77-432b-aba0-9588bd061724", + "name" : "manage-account-links", + "description" : "${role_manage-account-links}", + "composite" : false, + "clientRole" : true, + "containerId" : "29d7d4c8-f7a0-48ea-b21f-300f692e6fd9", + "attributes" : { } + }, { + "id" : "b7609fc3-c307-4bd6-92b3-51e98943dec9", + "name" : "manage-account", + "description" : "${role_manage-account}", + "composite" : true, + "composites" : { + "client" : { + "account" : [ "manage-account-links" ] + } + }, + "clientRole" : true, + "containerId" : "29d7d4c8-f7a0-48ea-b21f-300f692e6fd9", + "attributes" : { } + }, { + "id" : "c63accbc-12d2-42fd-9fc6-2d5deeeb248b", + "name" : "view-profile", + "description" : "${role_view-profile}", + "composite" : false, + "clientRole" : true, + "containerId" : "29d7d4c8-f7a0-48ea-b21f-300f692e6fd9", + "attributes" : { } + } ] + } + }, + "groups" : [ { + "id" : "9ebfaac4-7bae-4765-b582-9dc88814e44b", + "name" : "User", + "path" : "/User", + "attributes" : { }, + "realmRoles" : [ ], + "clientRoles" : { }, + "subGroups" : [ ] + } ], + "defaultRoles" : [ "uma_authorization", "offline_access" ], + "requiredCredentials" : [ "password" ], + "otpPolicyType" : "totp", + "otpPolicyAlgorithm" : "HmacSHA1", + "otpPolicyInitialCounter" : 0, + "otpPolicyDigits" : 6, + "otpPolicyLookAheadWindow" : 1, + "otpPolicyPeriod" : 30, + "otpSupportedApplications" : [ "FreeOTP", "Google Authenticator" ], + "users" : [ { + "id" : "e11538e0-9997-46fc-be3a-9dde3d98f4f0", + "createdTimestamp" : 1556330044675, + "username" : "goto-user", + "enabled" : true, + "totp" : false, + "emailVerified" : false, + "firstName" : "GOTO", + "lastName" : "Chicago", + "email" : "goto-user@jboss.com", + "attributes" : { + "zoneinfo" : [ "CST" ] + }, + "credentials" : [ { + "type" : "password", + "hashedSaltedValue" : "8B81XbfkBucxuxMl/IG+fI7iPDNukdZjMH0AjcI/9NLeKL2H8/10/+iZBEiFDQSmnNf8w26EMtdM9t5LaaQSIg==", + "salt" : "oKVk4LYHzvIUQhSm/fIgew==", + "hashIterations" : 27500, + "counter" : 0, + "algorithm" : "pbkdf2-sha256", + "digits" : 0, + "period" : 0, + "createdDate" : 1556330092040, + "config" : { } + } ], + "disableableCredentialTypes" : [ "password" ], + "requiredActions" : [ ], + "realmRoles" : [ "WorldClockSubscriber", "uma_authorization", "user", "GOTO-attendee", "offline_access" ], + "clientRoles" : { + "account" : [ "manage-account", "view-profile" ] + }, + "notBefore" : 0, + "groups" : [ "/User" ] + }, { + "id" : "a19b2afc-e96e-4939-82bf-aa4b589de136", + "createdTimestamp" : 1553266393903, + "username" : "test", + "enabled" : true, + "totp" : false, + "emailVerified" : false, + "credentials" : [ { + "type" : "password", + "hashedSaltedValue" : "n6Gtq/VSa+xAkwQ/nsJkq0Rgh0g9wtEM9yMjhKeISVQ7/MoiCKBCj05rB96mBbgkUw7MClbSa4VQl4gRSsyktg==", + "salt" : "ir+W1U7oHdLT4/80KdI3hw==", + "hashIterations" : 27500, + "counter" : 0, + "algorithm" : "pbkdf2-sha256", + "digits" : 0, + "period" : 0, + "createdDate" : 1553266401320, + "config" : { } + } ], + "disableableCredentialTypes" : [ "password" ], + "requiredActions" : [ ], + "realmRoles" : [ "uma_authorization", "user", "offline_access" ], + "clientRoles" : { + "account" : [ "manage-account", "view-profile" ] + }, + "notBefore" : 0, + "groups" : [ ] + } ], + "scopeMappings" : [ { + "clientScope" : "offline_access", + "roles" : [ "offline_access" ] + } ], + "clients" : [ { + "id" : "29d7d4c8-f7a0-48ea-b21f-300f692e6fd9", + "clientId" : "account", + "name" : "${client_account}", + "baseUrl" : "/auth/realms/quarkus-quickstart/account", + "surrogateAuthRequired" : false, + "enabled" : true, + "clientAuthenticatorType" : "client-secret", + "secret" : "**********", + "defaultRoles" : [ "manage-account", "view-profile" ], + "redirectUris" : [ "/auth/realms/quarkus-quickstart/account/*" ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : false, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "defaultClientScopes" : [ "web-origins", "role_list", "roles", "profile", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access" ] + }, { + "id" : "032c09f3-4fa5-485e-bd07-a3ca1667c38a", + "clientId" : "admin-cli", + "name" : "${client_admin-cli}", + "surrogateAuthRequired" : false, + "enabled" : true, + "clientAuthenticatorType" : "client-secret", + "secret" : "**********", + "redirectUris" : [ ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : false, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : true, + "serviceAccountsEnabled" : false, + "publicClient" : true, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "defaultClientScopes" : [ "web-origins", "role_list", "roles", "profile", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access" ] + }, { + "id" : "d36113c3-480e-4f46-98b0-95ff47e9f220", + "clientId" : "broker", + "name" : "${client_broker}", + "surrogateAuthRequired" : false, + "enabled" : true, + "clientAuthenticatorType" : "client-secret", + "secret" : "**********", + "redirectUris" : [ ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : false, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "defaultClientScopes" : [ "web-origins", "role_list", "roles", "profile", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access" ] + }, { + "id" : "66557d8d-651b-4604-b865-a4b08eb4716a", + "clientId" : "microprofile-jwt-client", + "name" : "MicroProfile JWT Client", + "description" : "A MicroProfile JWT based client", + "rootUrl" : "http://localhost:8080", + "adminUrl" : "http://localhost:8080", + "surrogateAuthRequired" : false, + "enabled" : true, + "clientAuthenticatorType" : "client-secret", + "secret" : "**********", + "redirectUris" : [ "http://localhost:8080/*" ], + "webOrigins" : [ "http://localhost:8080" ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : true, + "serviceAccountsEnabled" : false, + "publicClient" : true, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { + "saml.assertion.signature" : "false", + "saml.force.post.binding" : "false", + "saml.multivalued.roles" : "false", + "saml.encrypt" : "false", + "saml.server.signature" : "false", + "saml.server.signature.keyinfo.ext" : "false", + "exclude.session.state.from.auth.response" : "false", + "saml_force_name_id_format" : "false", + "saml.client.signature" : "false", + "tls.client.certificate.bound.access.tokens" : "false", + "saml.authnstatement" : "false", + "display.on.consent.screen" : "false", + "saml.onetimeuse.condition" : "false" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : true, + "nodeReRegistrationTimeout" : -1, + "protocolMappers" : [ { + "id" : "94bfb7ba-b9c9-4671-8542-3a59cd794313", + "name" : "groups", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-realm-role-mapper", + "consentRequired" : false, + "config" : { + "multivalued" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "foo", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "groups", + "jsonType.label" : "String" + } + }, { + "id" : "8ce26d97-24be-4e8a-9f7b-d331e2ec1043", + "name" : "username", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "username", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "preferred_username", + "jsonType.label" : "String" + } + }, { + "id" : "61295a9c-a2f5-4975-87e7-929444229303", + "name" : "upn", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "username", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "upn", + "jsonType.label" : "String" + } + }, { + "id" : "097beac7-1722-4d63-849d-55b2d9b6522d", + "name" : "locale", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "locale", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "locale", + "jsonType.label" : "String" + } + }, { + "id" : "1613c860-0917-4240-8891-24df39b04b5f", + "name" : "zoneinfo", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "zoneinfo", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "zoneinfo", + "jsonType.label" : "String" + } + }, { + "id" : "7629b518-6b7a-43cb-98fe-30d1dc07e722", + "name" : "email", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "email", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "email", + "jsonType.label" : "String" + } + } ], + "defaultClientScopes" : [ "web-origins", "role_list", "roles", "profile", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access" ] + }, { + "id" : "f943c108-d8d6-46fe-9c44-a16965fb50ea", + "clientId" : "quarkus-front", + "rootUrl" : "http://localhost:8080", + "adminUrl" : "http://localhost:8080", + "surrogateAuthRequired" : false, + "enabled" : true, + "clientAuthenticatorType" : "client-secret", + "secret" : "**********", + "redirectUris" : [ "http://localhost:8080/*" ], + "webOrigins" : [ "http://localhost:8080" ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : true, + "serviceAccountsEnabled" : false, + "publicClient" : true, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : true, + "nodeReRegistrationTimeout" : -1, + "protocolMappers" : [ { + "id" : "f9ef3f89-82b7-4840-9833-6f9ab0be0aeb", + "name" : "upn", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "username", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "upn", + "jsonType.label" : "String" + } + }, { + "id" : "dc4a2eb6-d601-4510-b0f0-038cd8302ca4", + "name" : "email", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "email", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "email", + "jsonType.label" : "String" + } + }, { + "id" : "5611ae18-0db8-41cf-8d49-e360fb1b6f14", + "name" : "realm roles", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-realm-role-mapper", + "consentRequired" : false, + "config" : { + "user.attribute" : "foo", + "access.token.claim" : "true", + "claim.name" : "groups", + "jsonType.label" : "String", + "multivalued" : "true" + } + } ], + "defaultClientScopes" : [ "web-origins", "role_list", "roles", "profile", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access" ] + }, { + "id" : "df94d122-f41f-4f2f-80fc-f1395124cc05", + "clientId" : "realm-management", + "name" : "${client_realm-management}", + "surrogateAuthRequired" : false, + "enabled" : true, + "clientAuthenticatorType" : "client-secret", + "secret" : "**********", + "redirectUris" : [ ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : true, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : false, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "defaultClientScopes" : [ "web-origins", "role_list", "roles", "profile", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access" ] + }, { + "id" : "34cd583d-aba1-4421-bd75-0db99c533e0f", + "clientId" : "security-admin-console", + "name" : "${client_security-admin-console}", + "baseUrl" : "/auth/admin/quarkus-quickstart/console/index.html", + "surrogateAuthRequired" : false, + "enabled" : true, + "clientAuthenticatorType" : "client-secret", + "secret" : "**********", + "redirectUris" : [ "/auth/admin/quarkus-quickstart/console/*" ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : true, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "protocolMappers" : [ { + "id" : "6e970b27-78fa-40d9-9457-b2d038f9d10e", + "name" : "locale", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "locale", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "locale", + "jsonType.label" : "String" + } + } ], + "defaultClientScopes" : [ "web-origins", "role_list", "roles", "profile", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access" ] + } ], + "clientScopes" : [ { + "id" : "00fa6d87-0bc7-4b40-ad84-4545e09408c6", + "name" : "address", + "description" : "OpenID Connect built-in scope: address", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "display.on.consent.screen" : "true", + "consent.screen.text" : "${addressScopeConsentText}" + }, + "protocolMappers" : [ { + "id" : "27c29249-7a2f-4c86-90cf-34d3ee83786b", + "name" : "address", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-address-mapper", + "consentRequired" : false, + "config" : { + "user.attribute.formatted" : "formatted", + "user.attribute.country" : "country", + "user.attribute.postal_code" : "postal_code", + "userinfo.token.claim" : "true", + "user.attribute.street" : "street", + "id.token.claim" : "true", + "user.attribute.region" : "region", + "access.token.claim" : "true", + "user.attribute.locality" : "locality" + } + } ] + }, { + "id" : "31a2a3d3-a750-4bf8-a5f4-e19cd4ce7ba3", + "name" : "email", + "description" : "OpenID Connect built-in scope: email", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "display.on.consent.screen" : "true", + "consent.screen.text" : "${emailScopeConsentText}" + }, + "protocolMappers" : [ { + "id" : "566d6b22-b306-4e09-99a2-8c901a9cfaeb", + "name" : "email", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "email", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "email", + "jsonType.label" : "String" + } + }, { + "id" : "994d732e-3ec8-47ec-9e87-c1a93a85dc0b", + "name" : "email verified", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "emailVerified", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "email_verified", + "jsonType.label" : "boolean" + } + } ] + }, { + "id" : "c7604a83-afdd-4d10-87f1-6065a7db66e9", + "name" : "offline_access", + "description" : "OpenID Connect built-in scope: offline_access", + "protocol" : "openid-connect", + "attributes" : { + "consent.screen.text" : "${offlineAccessScopeConsentText}", + "display.on.consent.screen" : "true" + } + }, { + "id" : "94ca9143-1b05-4a75-8620-d161a301e1ab", + "name" : "phone", + "description" : "OpenID Connect built-in scope: phone", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "display.on.consent.screen" : "true", + "consent.screen.text" : "${phoneScopeConsentText}" + }, + "protocolMappers" : [ { + "id" : "28153a56-bef3-4f4a-848e-020d4e772c80", + "name" : "phone number verified", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "phoneNumberVerified", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "phone_number_verified", + "jsonType.label" : "boolean" + } + }, { + "id" : "d6984c72-fc4e-4806-9184-f24dbb1562c4", + "name" : "phone number", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "phoneNumber", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "phone_number", + "jsonType.label" : "String" + } + } ] + }, { + "id" : "c5231f53-2b58-43cd-94b0-99b362b521b4", + "name" : "profile", + "description" : "OpenID Connect built-in scope: profile", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "display.on.consent.screen" : "true", + "consent.screen.text" : "${profileScopeConsentText}" + }, + "protocolMappers" : [ { + "id" : "ead3ce30-0014-4f5a-9c5a-d2b3ff69ec69", + "name" : "birthdate", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "birthdate", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "birthdate", + "jsonType.label" : "String" + } + }, { + "id" : "27341395-8081-44db-8e80-d1f8d1b444b2", + "name" : "website", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "website", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "website", + "jsonType.label" : "String" + } + }, { + "id" : "3df539e2-dd32-4aa6-8955-bfcd3c48ba31", + "name" : "family name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "lastName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "family_name", + "jsonType.label" : "String" + } + }, { + "id" : "9a722698-1ea0-4e00-b909-5a451e855aaa", + "name" : "username", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "username", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "preferred_username", + "jsonType.label" : "String" + } + }, { + "id" : "c7aae599-f895-447f-be06-7b579e81a51a", + "name" : "updated at", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "updatedAt", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "updated_at", + "jsonType.label" : "String" + } + }, { + "id" : "04a4e1e5-20ca-4ad0-9449-61f353deab8d", + "name" : "nickname", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "nickname", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "nickname", + "jsonType.label" : "String" + } + }, { + "id" : "7f47144a-cb2a-4ca2-9acc-242a9fda642e", + "name" : "picture", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "picture", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "picture", + "jsonType.label" : "String" + } + }, { + "id" : "8f0ea5b5-cfd5-47a1-adac-636477854d3d", + "name" : "middle name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "middleName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "middle_name", + "jsonType.label" : "String" + } + }, { + "id" : "c4368f07-ce04-4082-809f-65ab432bc655", + "name" : "profile", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "profile", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "profile", + "jsonType.label" : "String" + } + }, { + "id" : "89e0406f-011d-4430-bc2e-ce12183fda3f", + "name" : "given name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "firstName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "given_name", + "jsonType.label" : "String" + } + }, { + "id" : "9a0f410f-d401-4287-bd3c-a19eaaafb307", + "name" : "zoneinfo", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "zoneinfo", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "zoneinfo", + "jsonType.label" : "String" + } + }, { + "id" : "7133475d-e119-4006-8c57-c5aeee02281c", + "name" : "full name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-full-name-mapper", + "consentRequired" : false, + "config" : { + "id.token.claim" : "true", + "access.token.claim" : "true", + "userinfo.token.claim" : "true" + } + }, { + "id" : "a1598f7c-c9fd-49d9-b932-2a8f2f2208aa", + "name" : "locale", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "locale", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "locale", + "jsonType.label" : "String" + } + }, { + "id" : "c6d91274-4429-4ea6-a02a-af494d77c99c", + "name" : "gender", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "gender", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "gender", + "jsonType.label" : "String" + } + } ] + }, { + "id" : "1ad94696-c33f-4dd7-bc27-62b4b139391e", + "name" : "role_list", + "description" : "SAML role list", + "protocol" : "saml", + "attributes" : { + "consent.screen.text" : "${samlRoleListScopeConsentText}", + "display.on.consent.screen" : "true" + }, + "protocolMappers" : [ { + "id" : "42842c24-1530-4289-b6c3-66cec01ebe30", + "name" : "role list", + "protocol" : "saml", + "protocolMapper" : "saml-role-list-mapper", + "consentRequired" : false, + "config" : { + "single" : "false", + "attribute.nameformat" : "Basic", + "attribute.name" : "Role" + } + } ] + }, { + "id" : "b5d6f8c7-ede4-497d-8fdc-26b26002b504", + "name" : "roles", + "description" : "OpenID Connect scope for add user roles to the access token", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "false", + "display.on.consent.screen" : "true", + "consent.screen.text" : "${rolesScopeConsentText}" + }, + "protocolMappers" : [ { + "id" : "a07d65ca-6861-4fd1-ba2c-ace78c0b3190", + "name" : "realm roles", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-realm-role-mapper", + "consentRequired" : false, + "config" : { + "user.attribute" : "foo", + "access.token.claim" : "true", + "claim.name" : "realm_access.roles", + "jsonType.label" : "String", + "multivalued" : "true" + } + }, { + "id" : "2849f8d3-c631-4852-a19e-1257ad43df09", + "name" : "audience resolve", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-audience-resolve-mapper", + "consentRequired" : false, + "config" : { } + }, { + "id" : "763fa554-fa7d-431e-9e9e-5470159c7350", + "name" : "client roles", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-client-role-mapper", + "consentRequired" : false, + "config" : { + "user.attribute" : "foo", + "access.token.claim" : "true", + "claim.name" : "resource_access.${client_id}.roles", + "jsonType.label" : "String", + "multivalued" : "true" + } + } ] + }, { + "id" : "1217c5a0-b87d-4be8-a975-6128a4131bab", + "name" : "web-origins", + "description" : "OpenID Connect scope for add allowed web origins to the access token", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "false", + "display.on.consent.screen" : "false", + "consent.screen.text" : "" + }, + "protocolMappers" : [ { + "id" : "87a8c55f-8d87-42a7-a2da-25d7a4fcbd93", + "name" : "allowed web origins", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-allowed-origins-mapper", + "consentRequired" : false, + "config" : { } + } ] + } ], + "defaultDefaultClientScopes" : [ "web-origins", "role_list", "email", "roles", "profile" ], + "defaultOptionalClientScopes" : [ "address", "phone", "offline_access" ], + "browserSecurityHeaders" : { + "contentSecurityPolicyReportOnly" : "", + "xContentTypeOptions" : "nosniff", + "xRobotsTag" : "none", + "xFrameOptions" : "SAMEORIGIN", + "xXSSProtection" : "1; mode=block", + "contentSecurityPolicy" : "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", + "strictTransportSecurity" : "max-age=31536000; includeSubDomains" + }, + "smtpServer" : { }, + "eventsEnabled" : false, + "eventsListeners" : [ "jboss-logging" ], + "enabledEventTypes" : [ ], + "adminEventsEnabled" : false, + "adminEventsDetailsEnabled" : false, + "components" : { + "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy" : [ { + "id" : "2a33b301-fad6-482e-ba5e-e1593242a986", + "name" : "Consent Required", + "providerId" : "consent-required", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { } + }, { + "id" : "b57223db-9905-408d-8fa3-60ea5a1190e1", + "name" : "Allowed Protocol Mapper Types", + "providerId" : "allowed-protocol-mappers", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "allowed-protocol-mapper-types" : [ "oidc-sha256-pairwise-sub-mapper", "oidc-usermodel-property-mapper", "saml-user-property-mapper", "oidc-address-mapper", "saml-role-list-mapper", "saml-user-attribute-mapper", "oidc-full-name-mapper", "oidc-usermodel-attribute-mapper" ] + } + }, { + "id" : "cabd60da-1b1c-4247-b351-2aa2641628db", + "name" : "Allowed Protocol Mapper Types", + "providerId" : "allowed-protocol-mappers", + "subType" : "authenticated", + "subComponents" : { }, + "config" : { + "allowed-protocol-mapper-types" : [ "oidc-address-mapper", "oidc-usermodel-property-mapper", "oidc-usermodel-attribute-mapper", "oidc-sha256-pairwise-sub-mapper", "saml-user-property-mapper", "oidc-full-name-mapper", "saml-user-attribute-mapper", "saml-role-list-mapper" ] + } + }, { + "id" : "2c0469ef-4e25-4176-8fd1-2bf90cd2e055", + "name" : "Trusted Hosts", + "providerId" : "trusted-hosts", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "host-sending-registration-request-must-match" : [ "true" ], + "client-uris-must-match" : [ "true" ] + } + }, { + "id" : "2571af38-43f4-4835-8d6e-c5b8d1f20ff3", + "name" : "Allowed Client Scopes", + "providerId" : "allowed-client-templates", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "allow-default-scopes" : [ "true" ] + } + }, { + "id" : "663eaea9-ee71-4cb1-8afb-731d3cd130ca", + "name" : "Full Scope Disabled", + "providerId" : "scope", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { } + }, { + "id" : "2e7e2102-0fe5-458e-8622-df68140fe13e", + "name" : "Max Clients Limit", + "providerId" : "max-clients", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "max-clients" : [ "200" ] + } + }, { + "id" : "4b52e27f-9112-45ed-b6a4-b4c2ea5e38b0", + "name" : "Allowed Client Scopes", + "providerId" : "allowed-client-templates", + "subType" : "authenticated", + "subComponents" : { }, + "config" : { + "allow-default-scopes" : [ "true" ] + } + } ], + "org.keycloak.keys.KeyProvider" : [ { + "id" : "d89ada22-9397-47e0-8722-fd24185b334c", + "name" : "aes-generated", + "providerId" : "aes-generated", + "subComponents" : { }, + "config" : { + "kid" : [ "6bbc2a3c-448a-40fb-a8bc-dbcb3362d8f8" ], + "secret" : [ "pifQ3rz8jTtyUjxxDRt2ig" ], + "priority" : [ "100" ] + } + }, { + "id" : "0a7be844-8a9d-453f-9af0-3a8decde0af1", + "name" : "rsa-generated", + "providerId" : "rsa-generated", + "subComponents" : { }, + "config" : { + "privateKey" : [ "MIIEowIBAAKCAQEApmpYuqsAzLvHiWvPtiPggzEgsjpj1zw55Q8soArey+jEWf2sHyPHj8ymtZzo3GFl5ncXD5bfHJzMiDfleQLJbaCUNKEd9JWb5Nefs8/N5zeNUkpcP58FjY/u0wv0TOmnxjrVn3DkG64SYI5t9CWphkrsAbYKpgWzVXa/t/xBVoCzK4/f2kLqlIaxaWcEseklC7HRCs0j7j79/e0A4sFmw/ZNEnTxecY5qBU77Tkd1ToLCo/mEXp3cQR7YG55DuoCP3OyeYQFTq0648pn9jgUaRhqLmDiihHDKeDN3zU2N0MaePBlrmepqFev+v3QmvowzkaPBFYx93Yf7gv41MTMKwIDAQABAoIBAHwGaCh+x7LOBDEzy3MYm/vKgm6yO7Suqws+2uhQEanwyV20RDJVDSbrxBc4YQfWAwbXHLW+oHuHlJu05O3iGNL+SVZL+bJOSggsd1ageflnmoiI6OV1k8LkUX6yuxkTTrnbIViZ3zeN7R5PKD08UHLIpcnErXz8ZKI+RTZO5zcarGf1LMFdIxqF5ca2mcEhLVLY6H74TQ0RLKf0t0sCGLBOESB3tXcKRhwVtKh1S7IK5PXZA+2ZMUtLC6A8MPZ4/GZNYtQbn2eI9gLxFnzYuxW2KhNt/dXhKuzqxhaR19tP11GQMWtTBUHqPSzRj+RWV/UN7HYiCeXqpI5CUXPQAZkCgYEA+tPaLCCiIR+kA36QrGFl/j1jN6lgx/pA5CJe4Dw+6p5CR9atD7755s2WA2QqqMdr94MY0o5tN2sL6cF5ukyB2vdcqRCFoHqLRTpIP+Vk9S+umyw6jkNIdOUMj+FqPBXZ6Q7Lu8WBp6ucyoOuxL4w1WCf/yqU9xaIaiEVtvcb120CgYEAqdjfd1JdaGUhUlVL13QnRP7cboSalPtIMs+KS3vP6oAZ1IH7v/Yez2fNjETuNkmsToK7ferQFGMYqAlS10OgLvVk5ow6f4rqTgfbMsuDSCgIK3zbuTAHV7PmD/9QLpr9Qt8IGUt4Du2kntQ3ln6y9FCBJZ1fYrO+8JEAmDh2evcCgYEA9FgayjAEIOZ03V6XSwYa2KAunlOnT0hvnI4LBMBLvjzrbpYnwxYU1g1iXO1gTUjv2Y9hSAStSnZChbYPgjfzrD6aiqvosNuP/qF3Lp+mSN/pZYqhhGLxG8biBOvflKbowAvE1qfh1Vfthq7fOxPX+JWHZ4iw0z+lJlYSlEmHWsUCgYBoDGe5XUfDsFBdc8tmTc3QwqCfQ7S/F8D+/40k+NBCQ/+O46Ip0sjFHj4ZdBDKSQ+5ePULClI2aw6rwipxnQgAPGx3n+gmQh+PddieoXXnOj7nBTJFnFqjK54AptOj+Wa1FJSNimIeIw76Dd+P3kYazUaQmHvap2qNTQckOVK7uQKBgA2ZWRnNhoz7sUo2kaUqR0KRRbn3P1CwDwR+oLNFTHPXkTFFK+COzfSuycPxBl9lyXtRzUvsHQyG5qBDpqbm5DQNUgT9IcRH9AiqOyEkBKIp6nQd0Rw4T+WlCa371Af1n4Z0zNHdVWHdV1bvkz8rm9z9BHOztdbLyQCTq+W/80XU" ], + "certificate" : [ "MIICszCCAZsCBgFqXH5GiDANBgkqhkiG9w0BAQsFADAdMRswGQYDVQQDDBJxdWFya3VzLXF1aWNrc3RhcnQwHhcNMTkwNDI3MDE1MDQwWhcNMjkwNDI3MDE1MjIwWjAdMRswGQYDVQQDDBJxdWFya3VzLXF1aWNrc3RhcnQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCmali6qwDMu8eJa8+2I+CDMSCyOmPXPDnlDyygCt7L6MRZ/awfI8ePzKa1nOjcYWXmdxcPlt8cnMyIN+V5AsltoJQ0oR30lZvk15+zz83nN41SSlw/nwWNj+7TC/RM6afGOtWfcOQbrhJgjm30JamGSuwBtgqmBbNVdr+3/EFWgLMrj9/aQuqUhrFpZwSx6SULsdEKzSPuPv397QDiwWbD9k0SdPF5xjmoFTvtOR3VOgsKj+YRendxBHtgbnkO6gI/c7J5hAVOrTrjymf2OBRpGGouYOKKEcMp4M3fNTY3Qxp48GWuZ6moV6/6/dCa+jDORo8EVjH3dh/uC/jUxMwrAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAEvykg5Vis7Zq13b7GFzYfCkExH1updZB5JGjPT/xyO5w/ZQayqYPSUDrkuzLqgaN6XAmwH0WpHa0pNLBg3VlzByle//zPEaTJSdxsbg7YGNzvNqTb48pRveKz8S7wtD+74sLj+yslT0k4oZL1NPDcjoqqH4zcnGYlVJ2lXjC9EIMlwli3Yh3+QXjq8KHDIeVzZXxBzQPd9icm9sTXy8dZ7sHumpl2/o1lrPSF9XQbiQ5QKZbT91kjvrgNLGtHzSwvoOO4SFj2/bghFO8o3H9hAhBrEP+CqOop7WkqGzlBhEZ/eV8Gdt3k0mtXxgalzeecrumZceI8c9I293XL/b17Y=" ], + "priority" : [ "100" ] + } + }, { + "id" : "2e64aa1a-79b5-4339-b1da-a0af40d6c6c8", + "name" : "hmac-generated", + "providerId" : "hmac-generated", + "subComponents" : { }, + "config" : { + "kid" : [ "4c82c717-cee2-48e8-8d6c-74f2dd3cab7c" ], + "secret" : [ "60rLsBxjUPCZpzYPprH_bCHBNpDC1IHOGLMuy3r5c3RRrfQ6x5sJi-7Sp9tUVc9k_Th-kZfev27aLw9N26hxew" ], + "priority" : [ "100" ], + "algorithm" : [ "HS256" ] + } + } ] + }, + "internationalizationEnabled" : false, + "supportedLocales" : [ ], + "authenticationFlows" : [ { + "id" : "e0ab01a0-1d61-4eee-b26e-e40a23672d1a", + "alias" : "Handle Existing Account", + "description" : "Handle what to do if there is existing account with same email/username like authenticated identity provider", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "idp-confirm-link", + "requirement" : "REQUIRED", + "priority" : 10, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticator" : "idp-email-verification", + "requirement" : "ALTERNATIVE", + "priority" : 20, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "requirement" : "ALTERNATIVE", + "priority" : 30, + "flowAlias" : "Verify Existing Account by Re-authentication", + "userSetupAllowed" : false, + "autheticatorFlow" : true + } ] + }, { + "id" : "06914cfc-5d57-43ea-bb5e-0d4e3dec5731", + "alias" : "Verify Existing Account by Re-authentication", + "description" : "Reauthentication of existing account", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "idp-username-password-form", + "requirement" : "REQUIRED", + "priority" : 10, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticator" : "auth-otp-form", + "requirement" : "OPTIONAL", + "priority" : 20, + "userSetupAllowed" : false, + "autheticatorFlow" : false + } ] + }, { + "id" : "a9855829-1982-42a9-86e0-8e91eb560cd2", + "alias" : "browser", + "description" : "browser based authentication", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "auth-cookie", + "requirement" : "ALTERNATIVE", + "priority" : 10, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticator" : "auth-spnego", + "requirement" : "DISABLED", + "priority" : 20, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticator" : "identity-provider-redirector", + "requirement" : "ALTERNATIVE", + "priority" : 25, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "requirement" : "ALTERNATIVE", + "priority" : 30, + "flowAlias" : "forms", + "userSetupAllowed" : false, + "autheticatorFlow" : true + } ] + }, { + "id" : "0006bf3d-03c4-476a-8bf8-86ae1d850975", + "alias" : "clients", + "description" : "Base authentication for clients", + "providerId" : "client-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "client-secret", + "requirement" : "ALTERNATIVE", + "priority" : 10, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticator" : "client-jwt", + "requirement" : "ALTERNATIVE", + "priority" : 20, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticator" : "client-secret-jwt", + "requirement" : "ALTERNATIVE", + "priority" : 30, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticator" : "client-x509", + "requirement" : "ALTERNATIVE", + "priority" : 40, + "userSetupAllowed" : false, + "autheticatorFlow" : false + } ] + }, { + "id" : "279d12b8-4d08-4cc2-99d6-8d981dc0a432", + "alias" : "direct grant", + "description" : "OpenID Connect Resource Owner Grant", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "direct-grant-validate-username", + "requirement" : "REQUIRED", + "priority" : 10, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticator" : "direct-grant-validate-password", + "requirement" : "REQUIRED", + "priority" : 20, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticator" : "direct-grant-validate-otp", + "requirement" : "OPTIONAL", + "priority" : 30, + "userSetupAllowed" : false, + "autheticatorFlow" : false + } ] + }, { + "id" : "cb2d2348-869c-42a9-981e-8c37c273bdf8", + "alias" : "docker auth", + "description" : "Used by Docker clients to authenticate against the IDP", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "docker-http-basic-authenticator", + "requirement" : "REQUIRED", + "priority" : 10, + "userSetupAllowed" : false, + "autheticatorFlow" : false + } ] + }, { + "id" : "2c9c204a-95fc-4dd0-8df8-7d07acf08f01", + "alias" : "first broker login", + "description" : "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticatorConfig" : "review profile config", + "authenticator" : "idp-review-profile", + "requirement" : "REQUIRED", + "priority" : 10, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticatorConfig" : "create unique user config", + "authenticator" : "idp-create-user-if-unique", + "requirement" : "ALTERNATIVE", + "priority" : 20, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "requirement" : "ALTERNATIVE", + "priority" : 30, + "flowAlias" : "Handle Existing Account", + "userSetupAllowed" : false, + "autheticatorFlow" : true + } ] + }, { + "id" : "1c836414-4b16-404c-80e9-cf7c128b0916", + "alias" : "forms", + "description" : "Username, password, otp and other auth forms.", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "auth-username-password-form", + "requirement" : "REQUIRED", + "priority" : 10, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticator" : "auth-otp-form", + "requirement" : "OPTIONAL", + "priority" : 20, + "userSetupAllowed" : false, + "autheticatorFlow" : false + } ] + }, { + "id" : "e1c331a9-60d8-435e-98ee-10c585e77ab6", + "alias" : "http challenge", + "description" : "An authentication flow based on challenge-response HTTP Authentication Schemes", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "no-cookie-redirect", + "requirement" : "REQUIRED", + "priority" : 10, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticator" : "basic-auth", + "requirement" : "REQUIRED", + "priority" : 20, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticator" : "basic-auth-otp", + "requirement" : "DISABLED", + "priority" : 30, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticator" : "auth-spnego", + "requirement" : "DISABLED", + "priority" : 40, + "userSetupAllowed" : false, + "autheticatorFlow" : false + } ] + }, { + "id" : "71f1fea7-995e-42af-a94b-d21308ddf07a", + "alias" : "registration", + "description" : "registration flow", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "registration-page-form", + "requirement" : "REQUIRED", + "priority" : 10, + "flowAlias" : "registration form", + "userSetupAllowed" : false, + "autheticatorFlow" : true + } ] + }, { + "id" : "471241a1-c62c-4f8c-a21d-bd45fce07218", + "alias" : "registration form", + "description" : "registration form", + "providerId" : "form-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "registration-user-creation", + "requirement" : "REQUIRED", + "priority" : 20, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticator" : "registration-profile-action", + "requirement" : "REQUIRED", + "priority" : 40, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticator" : "registration-password-action", + "requirement" : "REQUIRED", + "priority" : 50, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticator" : "registration-recaptcha-action", + "requirement" : "DISABLED", + "priority" : 60, + "userSetupAllowed" : false, + "autheticatorFlow" : false + } ] + }, { + "id" : "3fc9e05b-01b8-4e29-9c35-844c976161ff", + "alias" : "reset credentials", + "description" : "Reset credentials for a user if they forgot their password or something", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "reset-credentials-choose-user", + "requirement" : "REQUIRED", + "priority" : 10, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticator" : "reset-credential-email", + "requirement" : "REQUIRED", + "priority" : 20, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticator" : "reset-password", + "requirement" : "REQUIRED", + "priority" : 30, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticator" : "reset-otp", + "requirement" : "OPTIONAL", + "priority" : 40, + "userSetupAllowed" : false, + "autheticatorFlow" : false + } ] + }, { + "id" : "4ae6df43-b86a-4d51-8f05-0ed0b456a62f", + "alias" : "saml ecp", + "description" : "SAML ECP Profile Authentication Flow", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "http-basic-authenticator", + "requirement" : "REQUIRED", + "priority" : 10, + "userSetupAllowed" : false, + "autheticatorFlow" : false + } ] + } ], + "authenticatorConfig" : [ { + "id" : "2ee81ba0-ed5f-4d72-80b1-3ce8290f415b", + "alias" : "create unique user config", + "config" : { + "require.password.update.after.registration" : "false" + } + }, { + "id" : "5ab1122f-9f8b-4266-b36e-7370106da242", + "alias" : "review profile config", + "config" : { + "update.profile.on.first.login" : "missing" + } + } ], + "requiredActions" : [ { + "alias" : "CONFIGURE_TOTP", + "name" : "Configure OTP", + "providerId" : "CONFIGURE_TOTP", + "enabled" : true, + "defaultAction" : false, + "priority" : 10, + "config" : { } + }, { + "alias" : "terms_and_conditions", + "name" : "Terms and Conditions", + "providerId" : "terms_and_conditions", + "enabled" : false, + "defaultAction" : false, + "priority" : 20, + "config" : { } + }, { + "alias" : "UPDATE_PASSWORD", + "name" : "Update Password", + "providerId" : "UPDATE_PASSWORD", + "enabled" : true, + "defaultAction" : false, + "priority" : 30, + "config" : { } + }, { + "alias" : "UPDATE_PROFILE", + "name" : "Update Profile", + "providerId" : "UPDATE_PROFILE", + "enabled" : true, + "defaultAction" : false, + "priority" : 40, + "config" : { } + }, { + "alias" : "VERIFY_EMAIL", + "name" : "Verify Email", + "providerId" : "VERIFY_EMAIL", + "enabled" : true, + "defaultAction" : false, + "priority" : 50, + "config" : { } + } ], + "browserFlow" : "browser", + "registrationFlow" : "registration", + "directGrantFlow" : "direct grant", + "resetCredentialsFlow" : "reset credentials", + "clientAuthenticationFlow" : "clients", + "dockerAuthenticationFlow" : "docker auth", + "attributes" : { + "_browser_header.xXSSProtection" : "1; mode=block", + "_browser_header.xFrameOptions" : "SAMEORIGIN", + "_browser_header.strictTransportSecurity" : "max-age=31536000; includeSubDomains", + "permanentLockout" : "false", + "quickLoginCheckMilliSeconds" : "1000", + "_browser_header.xRobotsTag" : "none", + "maxFailureWaitSeconds" : "900", + "minimumQuickLoginWaitSeconds" : "60", + "failureFactor" : "30", + "actionTokenGeneratedByUserLifespan" : "300", + "maxDeltaTimeSeconds" : "43200", + "_browser_header.xContentTypeOptions" : "nosniff", + "offlineSessionMaxLifespan" : "5184000", + "actionTokenGeneratedByAdminLifespan" : "43200", + "_browser_header.contentSecurityPolicyReportOnly" : "", + "bruteForceProtected" : "false", + "_browser_header.contentSecurityPolicy" : "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", + "waitIncrementSeconds" : "60", + "offlineSessionMaxLifespanEnabled" : "false" + }, + "keycloakVersion" : "6.0.1", + "userManagedAccessAllowed" : false +} \ No newline at end of file diff --git a/docs/GOTO_Chicago_Talk.pdf b/docs/GOTO_Chicago_Talk.pdf new file mode 100644 index 0000000..917b18d Binary files /dev/null and b/docs/GOTO_Chicago_Talk.pdf differ diff --git a/pom.xml b/pom.xml index 1fb38ca..99aff4e 100644 --- a/pom.xml +++ b/pom.xml @@ -3,11 +3,13 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 4.0.0 com.example - demo + demo-parent 1.0-SNAPSHOT + pom + 2.22.0 - 0.12.0 + 0.14.0 UTF-8 1.8 1.8 @@ -23,127 +25,5 @@ - - - io.quarkus - quarkus-resteasy - - - io.quarkus - quarkus-junit5 - test - - - io.rest-assured - rest-assured - test - - - io.quarkus - quarkus-smallrye-fault-tolerance - - - io.quarkus - quarkus-smallrye-health - - - io.quarkus - quarkus-smallrye-jwt - - - io.quarkus - quarkus-resteasy-jsonb - - - io.quarkus - quarkus-smallrye-metrics - - - io.quarkus - quarkus-smallrye-openapi - - - org.bouncycastle - bcpkix-jdk15on - 1.53 - test - - - com.nimbusds - nimbus-jose-jwt - 6.7 - test - - - - - - io.quarkus - quarkus-maven-plugin - ${quarkus.version} - - - - build - - - - - - maven-surefire-plugin - ${surefire-plugin.version} - - - org.jboss.logmanager.LogManager - - - - - - - - native - - - native - - - - - - io.quarkus - quarkus-maven-plugin - ${quarkus.version} - - - - native-image - - - true - - - - - - maven-failsafe-plugin - ${surefire-plugin.version} - - - - integration-test - verify - - - - ${project.build.directory}/${project.build.finalName}-runner - - - - - - - - - + diff --git a/protected/pom.xml b/protected/pom.xml new file mode 100644 index 0000000..6d4a7b6 --- /dev/null +++ b/protected/pom.xml @@ -0,0 +1,131 @@ + + + 4.0.0 + com.example + demo-protected + 1.0-SNAPSHOT + + + 2.22.0 + 0.14.0 + UTF-8 + 1.8 + 1.8 + + + + + io.quarkus + quarkus-bom + ${quarkus.version} + pom + import + + + + + + + io.quarkus + quarkus-resteasy + + + io.quarkus + quarkus-smallrye-health + + + io.quarkus + quarkus-smallrye-jwt + + + io.quarkus + quarkus-resteasy-jsonb + + + io.quarkus + quarkus-smallrye-metrics + + + io.quarkus + quarkus-smallrye-rest-client + + + io.quarkus + quarkus-smallrye-opentracing + + + + + ${project.artifactId} + + + io.quarkus + quarkus-maven-plugin + ${quarkus.version} + + + + build + + + + + + maven-surefire-plugin + ${surefire-plugin.version} + + + org.jboss.logmanager.LogManager + + + + + + + + native + + + native + + + + + + io.quarkus + quarkus-maven-plugin + ${quarkus.version} + + + + native-image + + + true + + + + + + maven-failsafe-plugin + ${surefire-plugin.version} + + + + integration-test + verify + + + + ${project.build.directory}/${project.build.finalName}-runner + + + + + + + + + + diff --git a/protected/src/main/java/com/example/demo/SecureRestApplication.java b/protected/src/main/java/com/example/demo/SecureRestApplication.java new file mode 100644 index 0000000..92f2a79 --- /dev/null +++ b/protected/src/main/java/com/example/demo/SecureRestApplication.java @@ -0,0 +1,24 @@ +package com.example.demo; + +import io.quarkus.runtime.ShutdownEvent; +import io.quarkus.runtime.StartupEvent; +import org.eclipse.microprofile.auth.LoginConfig; + +import javax.annotation.security.DeclareRoles; + +import javax.enterprise.event.Observes; +import javax.ws.rs.ApplicationPath; +import javax.ws.rs.core.Application; + +@ApplicationPath("/demo2") +@LoginConfig(authMethod = "MP-JWT", realmName = "quarkus-quickstart") +public class SecureRestApplication extends Application { + void onStart(@Observes StartupEvent event) { + System.out.printf("onStart, event=%s%n", event); + } + + void onStop(@Observes ShutdownEvent event) { + System.out.printf("onStop, event=%s%n", event); + } +} + diff --git a/protected/src/main/java/com/example/demo/health/KeycloakHealthCheck.java b/protected/src/main/java/com/example/demo/health/KeycloakHealthCheck.java new file mode 100644 index 0000000..3ea89ed --- /dev/null +++ b/protected/src/main/java/com/example/demo/health/KeycloakHealthCheck.java @@ -0,0 +1,58 @@ +package com.example.demo.health; + +import java.net.URI; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; + +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.eclipse.microprofile.health.Health; +import org.eclipse.microprofile.health.HealthCheck; +import org.eclipse.microprofile.health.HealthCheckResponse; +import org.eclipse.microprofile.health.HealthCheckResponseBuilder; +import org.eclipse.microprofile.rest.client.RestClientBuilder; + +/** + * Validate the keycloak realm this service depends on as the health check + */ +@Health +@ApplicationScoped +public class KeycloakHealthCheck implements HealthCheck { + @Inject + @ConfigProperty(name = "keycloak.baseURL") + String baseURL; + @Inject + @ConfigProperty(name = "keycloak.realmName") + String realmName; + + @Override + public HealthCheckResponse call() { + HealthCheckResponseBuilder builder = HealthCheckResponse.named("keycloak"); + checkKeycloakRealm(builder); + return builder.build(); + } + + /** + * Queries the configured keycloak realm using RestClient and the {@link KeycloakService} typed interface + * @param builder - health check response builder + */ + private void checkKeycloakRealm(HealthCheckResponseBuilder builder) { + try { + URI baseURI = URI.create(baseURL); + System.out.printf("Checking keycloak(%s), baseURI: %s\n", realmName, baseURI); + KeycloakService keycloakService = RestClientBuilder.newBuilder() + .baseUri(baseURI) + .build(KeycloakService.class); + KeycloakRealm kcRealm = keycloakService.getRealm(realmName); + builder.withData("realm", kcRealm.getRealm()) + .withData("publicKey", kcRealm.getPublicKey()) + .withData("accountService", kcRealm.getAccountService()) + .withData("tokenService", kcRealm.getTokenService()) + .up(); + } catch (Exception e) { + e.printStackTrace(); + builder.withData("exception", e.getMessage()) + .down(); + } + } +} diff --git a/protected/src/main/java/com/example/demo/health/KeycloakRealm.java b/protected/src/main/java/com/example/demo/health/KeycloakRealm.java new file mode 100644 index 0000000..bdf2166 --- /dev/null +++ b/protected/src/main/java/com/example/demo/health/KeycloakRealm.java @@ -0,0 +1,57 @@ +package com.example.demo.health; + +import java.net.URL; + +import javax.json.bind.annotation.JsonbProperty; + +/* +Data object for: +{"realm":"quarkus-quickstart", +"public_key":"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApmpYuqsAzLvHiWvPtiPggzEgsjpj1zw55Q8soArey...", +"token-service":"http://localhost:8180/auth/realms/quarkus-quickstart/protocol/openid-connect", +"account-service":"http://localhost:8180/auth/realms/quarkus-quickstart/account", +"tokens-not-before":0 +} + */ +public class KeycloakRealm { + String realm; + @JsonbProperty("public_key") + String publicKey; + @JsonbProperty("token-service") + String tokenService; + @JsonbProperty("account-service") + String accountService; + + + public String getRealm() { + return realm; + } + + public void setRealm(String realm) { + this.realm = realm; + } + + public String getPublicKey() { + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + public String getTokenService() { + return tokenService; + } + + public void setTokenService(String tokenService) { + this.tokenService = tokenService; + } + + public String getAccountService() { + return accountService; + } + + public void setAccountService(String accountService) { + this.accountService = accountService; + } +} diff --git a/protected/src/main/java/com/example/demo/health/KeycloakService.java b/protected/src/main/java/com/example/demo/health/KeycloakService.java new file mode 100644 index 0000000..d6edcc1 --- /dev/null +++ b/protected/src/main/java/com/example/demo/health/KeycloakService.java @@ -0,0 +1,17 @@ +package com.example.demo.health; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; + +@RegisterRestClient(baseUri = "http://localhost:8180/auth") +public interface KeycloakService { + @GET + @Path("/realms/{realmName}") + @Produces(MediaType.APPLICATION_JSON) + KeycloakRealm getRealm(@PathParam("realmName") String realmName); +} diff --git a/protected/src/main/java/com/example/demo/secure/CORSFilter.java b/protected/src/main/java/com/example/demo/secure/CORSFilter.java new file mode 100644 index 0000000..fe0fc4c --- /dev/null +++ b/protected/src/main/java/com/example/demo/secure/CORSFilter.java @@ -0,0 +1,19 @@ +package com.example.demo.secure; + +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ContainerResponseContext; +import javax.ws.rs.container.ContainerResponseFilter; +import javax.ws.rs.ext.Provider; +import java.io.IOException; + +@Provider +public class CORSFilter implements ContainerResponseFilter { + @Override + public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException { + responseContext.getHeaders().add("Access-Control-Allow-Origin", "*"); + responseContext.getHeaders().add("Access-Control-Allow-Headers", "origin, content-type, accept, authorization"); + responseContext.getHeaders().add("Access-Control-Allow-Credentials", "true"); + responseContext.getHeaders().add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, HEAD"); + responseContext.getHeaders().add("Access-Control-Max-Age", "1209600"); + } +} \ No newline at end of file diff --git a/protected/src/main/java/com/example/demo/secure/Now.java b/protected/src/main/java/com/example/demo/secure/Now.java new file mode 100644 index 0000000..e845bda --- /dev/null +++ b/protected/src/main/java/com/example/demo/secure/Now.java @@ -0,0 +1,63 @@ +package com.example.demo.secure; + +/** + * Data object returned by WorldClockApi and TimeService + */ +public class Now { + String currentDateTime; + String utcOffset; + boolean isDayLightSavingsTime; + String dayOfTheWeek; + String timeZoneName; + + public String getCurrentDateTime() { + return currentDateTime; + } + + public void setCurrentDateTime(String currentDateTime) { + this.currentDateTime = currentDateTime; + } + + public String getUtcOffset() { + return utcOffset; + } + + public void setUtcOffset(String utcOffset) { + this.utcOffset = utcOffset; + } + + public boolean isDayLightSavingsTime() { + return isDayLightSavingsTime; + } + + public void setDayLightSavingsTime(boolean dayLightSavingsTime) { + isDayLightSavingsTime = dayLightSavingsTime; + } + + public String getDayOfTheWeek() { + return dayOfTheWeek; + } + + public void setDayOfTheWeek(String dayOfTheWeek) { + this.dayOfTheWeek = dayOfTheWeek; + } + + public String getTimeZoneName() { + return timeZoneName; + } + + public void setTimeZoneName(String timeZoneName) { + this.timeZoneName = timeZoneName; + } + + @Override + public String toString() { + return "Now{" + + "currentDateTime='" + currentDateTime + '\'' + + ", utcOffset='" + utcOffset + '\'' + + ", isDayLightSavingsTime=" + isDayLightSavingsTime + + ", dayOfTheWeek='" + dayOfTheWeek + '\'' + + ", timeZoneName='" + timeZoneName + '\'' + + '}'; + } +} diff --git a/protected/src/main/java/com/example/demo/secure/TimeService.java b/protected/src/main/java/com/example/demo/secure/TimeService.java new file mode 100644 index 0000000..58ec044 --- /dev/null +++ b/protected/src/main/java/com/example/demo/secure/TimeService.java @@ -0,0 +1,32 @@ +package com.example.demo.secure; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +import org.eclipse.microprofile.rest.client.inject.RestClient; + +@Path("/time") +@ApplicationScoped +public class +TimeService { + @Inject + @RestClient + WorldClockApi clockApi; + @Inject + @RestClient + UserTimeZoneService userTimeZone; + + @GET + @Path("/userNow") + @Produces(MediaType.APPLICATION_JSON) + public Now userNow() { + String tz = userTimeZone.getUserTZ(); + Now userTime = clockApi.tz(tz); + System.out.printf("TimeService.userNow: %s\n", userTime); + return userTime; + } +} diff --git a/protected/src/main/java/com/example/demo/secure/UserTimeZoneService.java b/protected/src/main/java/com/example/demo/secure/UserTimeZoneService.java new file mode 100644 index 0000000..3e29f57 --- /dev/null +++ b/protected/src/main/java/com/example/demo/secure/UserTimeZoneService.java @@ -0,0 +1,18 @@ +package com.example.demo.secure; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders; +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; + +@RegisterRestClient(baseUri = "http://localhost:8081/demo2/secure") +@RegisterClientHeaders +public interface UserTimeZoneService { + @GET + @Path("/userTZ") + @Produces(MediaType.TEXT_PLAIN) + String getUserTZ(); +} diff --git a/protected/src/main/java/com/example/demo/secure/WorldClockApi.java b/protected/src/main/java/com/example/demo/secure/WorldClockApi.java new file mode 100644 index 0000000..976057e --- /dev/null +++ b/protected/src/main/java/com/example/demo/secure/WorldClockApi.java @@ -0,0 +1,38 @@ +package com.example.demo.secure; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; + +/** + * http://worldclockapi.com/api/json/utc/now + * { + * "$id": "1", + * "currentDateTime": "2019-04-25T03:23Z", + * "utcOffset": "00:00:00", + * "isDayLightSavingsTime": false, + * "dayOfTheWeek": "Thursday", + * "timeZoneName": "UTC", + * "currentFileTime": 132006362107999360, + * "ordinalDate": "2019-115", + * "serviceResponse": null + * } + */ +@RegisterRestClient(baseUri = WorldClockApi.BASE_URL) +public interface WorldClockApi { + static final String BASE_URL = "http://worldclockapi.com/api/json"; + + @GET + @Path("/utc/now") + @Produces(MediaType.APPLICATION_JSON) + Now utc(); + + @GET + @Path("{tz}/now") + @Produces(MediaType.APPLICATION_JSON) + Now tz(@PathParam("tz") String tz); +} diff --git a/protected/src/main/java/com/example/demo/secure/ZoneInfoEndpoint.java b/protected/src/main/java/com/example/demo/secure/ZoneInfoEndpoint.java new file mode 100755 index 0000000..5d86065 --- /dev/null +++ b/protected/src/main/java/com/example/demo/secure/ZoneInfoEndpoint.java @@ -0,0 +1,38 @@ +package com.example.demo.secure; + +import org.eclipse.microprofile.jwt.Claim; +import org.eclipse.microprofile.jwt.ClaimValue; +import org.eclipse.microprofile.jwt.JsonWebToken; +import org.eclipse.microprofile.metrics.annotation.Timed; + +import javax.annotation.security.RolesAllowed; +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +/** + * A secured endpoint that provides access to the zoneinfo claim + */ +@Path("/protected") +@RequestScoped +public class ZoneInfoEndpoint { + + @Inject + @Claim("zoneinfo") + private String zoneinfo; + @Inject + JsonWebToken jwt; + + @GET + @Path("/userTZ") + @RolesAllowed("WorldClockSubscriber") + @Produces(MediaType.TEXT_PLAIN) + @Timed + public String getSubscriberZoneInfo() { + System.out.printf("Zoneinfo for %s: %s\n", jwt.getName(), zoneinfo); + return zoneinfo; + } +} diff --git a/protected/src/main/resources/application.properties b/protected/src/main/resources/application.properties new file mode 100755 index 0000000..2145a84 --- /dev/null +++ b/protected/src/main/resources/application.properties @@ -0,0 +1,35 @@ +quarkus.http.port=8081 + +# Keycloak health check +keycloak.baseURL=http://localhost:8180/auth +keycloak.realmName=quarkus-quickstart + +# MP-JWT Config +mp.jwt.verify.publickey.location=http://localhost:8180/auth/realms/quarkus-quickstart/protocol/openid-connect/certs +mp.jwt.verify.issuer=http://localhost:8180/auth/realms/quarkus-quickstart +quarkus.smallrye-jwt.auth-mechanism=MP-JWT +quarkus.smallrye-jwt.enabled=true + +# This would override the WorldClockApi baseUri. +com.example.demo.secure.WorldClockApi/mp-rest/url=http://worldclockapi.com/api/json +com.example.demo.secure.UserTimeZoneService/mp-rest/url=http://localhost:8081/demo2/protected +org.eclipse.microprofile.rest.client.propagateHeaders=Authorization + +# OpenTracing +quarkus.jaeger.service-name=QuarkusMPDemo +quarkus.jaeger.sampler-type=const +quarkus.jaeger.sampler-param=1 +quarkus.jaeger.endpoint=http://localhost:14268/api/traces + +# Root logger at DEBUG +quarkus.log.level=DEBUG +# INFO console logging +quarkus.log.console.format=%d{HH:mm:ss} %-5p [%c{2.}]] (%t) %s%e%n +quarkus.log.console.level=INFO + +# Debug log file +quarkus.log.file.enable=true +quarkus.log.file.path=target/trace.log +quarkus.log.file.level=TRACE +quarkus.log.category."io.quarkus.smallrye".level=TRACE +quarkus.log.category."org.jboss.resteasy.core".level=TRACE \ No newline at end of file diff --git a/src/main/java/com/example/demo/DemoRestApplication.java b/src/main/java/com/example/demo/DemoRestApplication.java deleted file mode 100755 index cec135e..0000000 --- a/src/main/java/com/example/demo/DemoRestApplication.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.example.demo; - -import org.eclipse.microprofile.auth.LoginConfig; - -import javax.annotation.security.DeclareRoles; - -import javax.ws.rs.ApplicationPath; -import javax.ws.rs.core.Application; - -/** - * - */ -@ApplicationPath("/data") - -@LoginConfig(authMethod = "MP-JWT", realmName = "jwt-jaspi") -@DeclareRoles({"protected"}) - -public class DemoRestApplication extends Application { -} diff --git a/src/main/java/com/example/demo/config/ConfigTestController.java b/src/main/java/com/example/demo/config/ConfigTestController.java deleted file mode 100755 index 2ea9f76..0000000 --- a/src/main/java/com/example/demo/config/ConfigTestController.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.example.demo.config; - -import org.eclipse.microprofile.config.Config; -import org.eclipse.microprofile.config.ConfigProvider; -import org.eclipse.microprofile.config.inject.ConfigProperty; - -import javax.enterprise.context.RequestScoped; -import javax.inject.Inject; -import javax.ws.rs.GET; -import javax.ws.rs.Path; - -@Path("/config") -@RequestScoped -public class ConfigTestController { - - @Inject - @ConfigProperty(name = "injected.value") - private String injectedValue; - - @Path("/injected") - @GET - public String getInjectedConfigValue() { - return "Config value as Injected by CDI " + injectedValue; - } - - @Path("/lookup") - @GET - public String getLookupConfigValue() { - Config config = ConfigProvider.getConfig(); - String value = config.getValue("value", String.class); - return "Config value from ConfigProvider " + value; - } -} diff --git a/src/main/java/com/example/demo/secure/ProtectedController.java b/src/main/java/com/example/demo/secure/ProtectedController.java deleted file mode 100755 index d8aff69..0000000 --- a/src/main/java/com/example/demo/secure/ProtectedController.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.example.demo.secure; - -import org.eclipse.microprofile.jwt.Claim; -import org.eclipse.microprofile.jwt.ClaimValue; - -import javax.annotation.security.RolesAllowed; -import javax.enterprise.context.RequestScoped; -import javax.inject.Inject; -import javax.ws.rs.GET; -import javax.ws.rs.Path; - -/** - * - */ -@Path("/protected") -@RequestScoped -public class ProtectedController { - - @Inject - @Claim("custom-value") - private ClaimValue custom; - - @GET - @RolesAllowed("protected") - public String getJWTBasedValue() { - return "Protected Resource; Custom value : " + custom.getValue(); - } -} diff --git a/src/main/resources/.gitkeep b/src/main/resources/.gitkeep deleted file mode 100755 index e69de29..0000000 diff --git a/src/main/resources/META-INF/MP-JWT-SIGNER b/src/main/resources/META-INF/MP-JWT-SIGNER deleted file mode 100755 index 30f8ab9..0000000 --- a/src/main/resources/META-INF/MP-JWT-SIGNER +++ /dev/null @@ -1,9 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAq1yKub7iSaHhG2zxMNqr -oOhBRbU2HPrt996JpIAb9Bcq1rQ5jyo8NvrTGXQexxR49gJffxmRn4e5Ri59E3HP -V0Sb4QUMzmjZBbB8FVK9O7Qjy7bTxblSOoEwI5isd1GjwLIrsy6LopzhLc+vOsKQ -QsabO+0BfNj7tnQpNUGmojbDJawT4sL3zTBwB4nM4YML5Yy3MKqTxE9wtu+Qei4m -EwoQky+TXoDWhQ3y+8UN3QPtz8b5jyiqpbU99/5i/s/IU1bqLI7lz0Y+wmD4yXnC -kpSu6wv6dD6euk8PrXBFQ4JWhD4+gWMDntP7hUBAstQ9SdouWlvJxMASo6Q6aHs0 -JQIDAQAB ------END PUBLIC KEY----- \ No newline at end of file diff --git a/src/main/resources/META-INF/microprofile-config.properties b/src/main/resources/META-INF/microprofile-config.properties deleted file mode 100755 index 1215cb7..0000000 --- a/src/main/resources/META-INF/microprofile-config.properties +++ /dev/null @@ -1,2 +0,0 @@ -injected.value=Injected value -value=lookup value \ No newline at end of file diff --git a/src/main/resources/META-INF/resources/index.html b/src/main/resources/META-INF/resources/index.html deleted file mode 100755 index ad1361f..0000000 --- a/src/main/resources/META-INF/resources/index.html +++ /dev/null @@ -1,54 +0,0 @@ - - - - - Eclipse MicroProfile demo - - - -

MicroProfile

- -Hello JAX-RS endpoint
- - -

Config

-Injected config values
-Config values by lookup
- - - -

Fault tolerance

-Fallback after timeout
- - - -

Health

-Health status (with custom status ServiceHealthCheck)
- - - -

Metrics

-Timed endpoint
-Metrics page
- - - -

JWT Auth

-Look at readme.md on how to test protected endpoint. - - - -

Open API

-Open API Documentation
- - - -

Open Tracing

- - - -

Rest Client

- - - - \ No newline at end of file diff --git a/src/main/resources/jwt-roles.properties b/src/main/resources/jwt-roles.properties deleted file mode 100755 index 8d3552f..0000000 --- a/src/main/resources/jwt-roles.properties +++ /dev/null @@ -1,2 +0,0 @@ - -#group=role \ No newline at end of file diff --git a/src/main/resources/project-defaults.yml b/src/main/resources/project-defaults.yml deleted file mode 100755 index c6f57aa..0000000 --- a/src/main/resources/project-defaults.yml +++ /dev/null @@ -1,30 +0,0 @@ -# A project defaults for use with MP-JWT auth-method that include additional role mapping -swarm: - bind: - address: localhost - # Example of passing in token verification information via project file - microprofile: - jwtauth: - token: - issuedBy: "https://server.example.com" - security: - security-domains: - # name matches realm name used with LoginConfig - jwt-jaspi: - jaspi-authentication: - login-module-stacks: - roles-lm-stack: - login-modules: - # This stack performs the token verification and group to role mapping - - login-module: rm - code: org.wildfly.swarm.microprofile.jwtauth.deployment.auth.jaas.JWTLoginModule - flag: required - module-options: - rolesProperties: jwt-roles.properties - auth-modules: - # This module integrates the MP-JWT custom authentication mechanism into the web container - http: - code: org.wildfly.extension.undertow.security.jaspi.modules.HTTPSchemeServerAuthModule - module: org.wildfly.extension.undertow - flag: required - login-module-stack-ref: roles-lm-stack diff --git a/src/main/webapp/.gitkeep b/src/main/webapp/.gitkeep deleted file mode 100755 index e69de29..0000000 diff --git a/src/main/webapp/WEB-INF/beans.xml b/src/main/webapp/WEB-INF/beans.xml deleted file mode 100755 index 2777559..0000000 --- a/src/main/webapp/WEB-INF/beans.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml deleted file mode 100755 index 30e03d8..0000000 --- a/src/main/webapp/WEB-INF/web.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - Secure All - - Secure REST Endpoint - /data/protected - - - * - - - - \ No newline at end of file diff --git a/src/main/webapp/index.html b/src/main/webapp/index.html deleted file mode 100755 index ad1361f..0000000 --- a/src/main/webapp/index.html +++ /dev/null @@ -1,54 +0,0 @@ - - - - - Eclipse MicroProfile demo - - - -

MicroProfile

- -Hello JAX-RS endpoint
- - -

Config

-Injected config values
-Config values by lookup
- - - -

Fault tolerance

-Fallback after timeout
- - - -

Health

-Health status (with custom status ServiceHealthCheck)
- - - -

Metrics

-Timed endpoint
-Metrics page
- - - -

JWT Auth

-Look at readme.md on how to test protected endpoint. - - - -

Open API

-Open API Documentation
- - - -

Open Tracing

- - - -

Rest Client

- - - - \ No newline at end of file diff --git a/src/test/java/.gitkeep b/src/test/java/.gitkeep deleted file mode 100755 index e69de29..0000000 diff --git a/src/test/java/com/example/demo/JWTClient.java b/src/test/java/com/example/demo/JWTClient.java deleted file mode 100755 index 6b4eec9..0000000 --- a/src/test/java/com/example/demo/JWTClient.java +++ /dev/null @@ -1,89 +0,0 @@ -package com.example.demo; - -import com.nimbusds.jose.*; -import com.nimbusds.jose.crypto.RSASSASigner; -import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.bouncycastle.openssl.PEMKeyPair; -import org.bouncycastle.openssl.PEMParser; -import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; - -import javax.ws.rs.client.ClientBuilder; -import javax.ws.rs.client.WebTarget; -import javax.ws.rs.core.Response; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.security.KeyPair; -import java.security.PrivateKey; -import java.util.Arrays; -import java.util.UUID; - -/** - * - */ - -public class JWTClient { - - public static void main(String[] args) throws IOException { - // TODO Use Atbash JWT Support - PrivateKey key = readPrivateKey(); - - String jwt = generateJWT(key); - - System.out.println(jwt); - - WebTarget target = ClientBuilder.newClient().target("http://localhost:8080/data/protected"); - Response response = target.request().header("authorization", "Bearer " + jwt).buildGet().invoke(); - - System.out.println(response.getStatus()); - System.out.println(response.readEntity(String.class)); - - } - - private static String generateJWT(PrivateKey key) { - JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.RS256) - .type(JOSEObjectType.JWT) - .keyID("theKeyId") - .build(); - - MPJWTToken token = new MPJWTToken(); - token.setAud("targetService"); - token.setIss("https://server.example.com"); // Must match the expected issues configuration values - token.setJti(UUID.randomUUID().toString()); - - token.setSub("Jessie"); // Sub is required for WildFly Swarm - token.setUpn("Jessie"); - - token.setIat(System.currentTimeMillis()); - token.setExp(System.currentTimeMillis() + 30000); // 30 Seconds expiration! - - token.addAdditionalClaims("custom-value", "Jessie specific value"); - - token.setGroups(Arrays.asList("user", "protected")); - - JWSObject jwsObject = new JWSObject(header, new Payload(token.toJSONString())); - - // Apply the Signing protection - JWSSigner signer = new RSASSASigner(key); - - try { - jwsObject.sign(signer); - } catch (JOSEException e) { - e.printStackTrace(); - } - - return jwsObject.serialize(); - } - - public static PrivateKey readPrivateKey() throws IOException { - - InputStream inputStream = JWTClient.class.getResourceAsStream("/privateKey.pem"); - - PEMParser pemParser = new PEMParser(new InputStreamReader(inputStream)); - JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider(new BouncyCastleProvider()); - Object object = pemParser.readObject(); - KeyPair kp = converter.getKeyPair((PEMKeyPair) object); - return kp.getPrivate(); - } - -} diff --git a/src/test/java/com/example/demo/MPJWTToken.java b/src/test/java/com/example/demo/MPJWTToken.java deleted file mode 100755 index e2fbec1..0000000 --- a/src/test/java/com/example/demo/MPJWTToken.java +++ /dev/null @@ -1,162 +0,0 @@ -package com.example.demo; - -import net.minidev.json.JSONArray; -import net.minidev.json.JSONObject; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * Represent the MP Auth token - */ - -public class MPJWTToken { - - private String iss; // issuer - private String aud; // audience - private String jti; // Unique identifier - private Long exp; // expiration time - private Long iat; // issued at - private String sub; // subject - private String upn; // value for name in Principal - private String preferredUsername; // value for name in Principal - private List groups = new ArrayList<>(); - /* - "iss": "https://server.example.com", - "aud": "s6BhdRkqt3", - "jti": "a-123", - "exp": 1311281970, - "iat": 1311280970, - "sub": "24400320", - "upn": "jdoe@server.example.com", - "groups": ["red-group", "green-group", "admin-group", "admin"], - */ - - private List roles; - private Map additionalClaims; - - public String getIss() { - return iss; - } - - public void setIss(String iss) { - this.iss = iss; - } - - public String getAud() { - return aud; - } - - public void setAud(String aud) { - this.aud = aud; - } - - public String getJti() { - return jti; - } - - public void setJti(String jti) { - this.jti = jti; - } - - public Long getExp() { - return exp; - } - - public void setExp(Long exp) { - this.exp = exp; - } - - public Long getIat() { - return iat; - } - - public void setIat(Long iat) { - this.iat = iat; - } - - public String getSub() { - return sub; - } - - public void setSub(String sub) { - this.sub = sub; - } - - public String getUpn() { - return upn; - } - - public void setUpn(String upn) { - this.upn = upn; - } - - public String getPreferredUsername() { - return preferredUsername; - } - - public void setPreferredUsername(String preferredUsername) { - this.preferredUsername = preferredUsername; - } - - public List getGroups() { - return groups; - } - - public void setGroups(List groups) { - this.groups = groups; - } - - public List getRoles() { - return roles; - } - - public void setRoles(List roles) { - this.roles = roles; - } - - public Map getAdditionalClaims() { - return additionalClaims; - } - - public void setAdditionalClaims(Map additionalClaims) { - this.additionalClaims = additionalClaims; - } - - public void addAdditionalClaims(String key, String value) { - if (additionalClaims == null) { - additionalClaims = new HashMap<>(); - } - additionalClaims.put(key, value); - } - - public String toJSONString() { - - JSONObject jsonObject = new JSONObject(); - jsonObject.appendField("iss", iss); - jsonObject.appendField("aud", aud); - jsonObject.appendField("jti", jti); - jsonObject.appendField("exp", exp / 1000); - jsonObject.appendField("iat", iat / 1000); - jsonObject.appendField("sub", sub); - jsonObject.appendField("upn", upn); - jsonObject.appendField("preferred_username", preferredUsername); - - if (additionalClaims != null) { - for (Map.Entry entry : additionalClaims.entrySet()) { - jsonObject.appendField(entry.getKey(), entry.getValue()); - } - } - - JSONArray groupsArr = new JSONArray(); - for (String group : groups) { - groupsArr.appendElement(group); - } - jsonObject.appendField("groups", groupsArr); - - return jsonObject.toJSONString(); - } - -} diff --git a/src/test/resources/privateKey.pem b/src/test/resources/privateKey.pem deleted file mode 100755 index 12a52ea..0000000 --- a/src/test/resources/privateKey.pem +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEpQIBAAKCAQEAq1yKub7iSaHhG2zxMNqroOhBRbU2HPrt996JpIAb9Bcq1rQ5 -jyo8NvrTGXQexxR49gJffxmRn4e5Ri59E3HPV0Sb4QUMzmjZBbB8FVK9O7Qjy7bT -xblSOoEwI5isd1GjwLIrsy6LopzhLc+vOsKQQsabO+0BfNj7tnQpNUGmojbDJawT -4sL3zTBwB4nM4YML5Yy3MKqTxE9wtu+Qei4mEwoQky+TXoDWhQ3y+8UN3QPtz8b5 -jyiqpbU99/5i/s/IU1bqLI7lz0Y+wmD4yXnCkpSu6wv6dD6euk8PrXBFQ4JWhD4+ -gWMDntP7hUBAstQ9SdouWlvJxMASo6Q6aHs0JQIDAQABAoIBAAH4XunyzWQ+Vbm8 -f2tp4DZJ5VI5WqVrpyfF6bw38tpUF9x/FvhT6nxRVvMAeEgerqNZdKKf9yIn69Po -PezmzpT4x3gHWFXZM8GSfWK7YEya7hxt/jJURqNAj/6gxz7Z8/GHuuPlAZr9hcNC -k4ev6/uwj+FZAL7gOcinvj+MAS/1P2j02JFg1E8OvNEeug+kBh+HdsDZJePf7a1P -8YsCPQLLzv16ZNVDbp1vDrt/ZIPm77DKpY7kWVPPieca/RvUwsOt5Xdt9+Vri0Pp -6Fz5qEX23hYlkW64Tu672EuF6i8Y0FQYQm7DK98t7o0g+qWUineNhOzsQQ1PL/aV -xV53sRMCgYEA1XTYz13TV2VFsaZgYOGU5l3BFtvA0KE0qoQlBVRO64Q4UrGLqBBl -KoOW0MTHgHxpiNO+JyKnQ45LSgI41L6KjUVfSJSm6fEYjgUZ0GJeP5ptRSRU86lb -V5QNhtHjpfl/mn0caWJknOtUeA1YO5MiCreXb+NcZ7POXCp44MVY888CgYEAzYPk -Py1Q5povE11o5Ke8n6baTzIfXIaK35kDq5wttC0Ig4RrPtRjZmcpRmEToal+1+ln -UqpfA325NilzQ5FYOWBNMrmQN98MVQWV4p/WoNNp0Q4CbHL7Ladvj4cM+f9v0aIG -+6zNmg3EkugDRqzMLBWT83wz4fWu3BfAg9rU8csCgYEApsykD4b1+zk7+NJWRd6B -CHMCy+X1ChKuoQCaHUwAT7IGgTgUNcR6CXrCg2kBrLmLjkxILzS5F+U9mBucZUJv -6mznRERFGlI4KeJ6hYgYtgLqmDkH2U/h/NHIsC2bgrswp3xUk1U74hNS8m0bIZKb -61wDjn9QWgj7nLHju2D7XakCgYEAtLBFORaKjnT49rmlw4OGX4e/YOnckOjpqHVL -uNHzjPobBiaic/cf/aStsWcgb34qO4LGiPOp3dPnxyriYIGlY6y7suFbXwQIP6hN -g5asiXHXEyqSDu94z0bq2vIOAq8GjPU71oKzscJxFghO+RBr4kaGcgi9ROlQ98ka -RdNcbckCgYEAuJDL3gwiq6woEqTdyQkyQcbIQC+G7MJkpFLbLmNbpClwWLIr9dDr -a0gquYja3lmquP5RlnRfyspg+aXywELm82GW9Sz4ZUpDTXP8JcO7O0imfmzm8KDK -8jJy8wCbO4NGe0z6FB883HL0IkpqOhy1JkbzZNUmpN47kaBamqb1iec= ------END RSA PRIVATE KEY----- \ No newline at end of file diff --git a/web/pom.xml b/web/pom.xml new file mode 100644 index 0000000..79677c2 --- /dev/null +++ b/web/pom.xml @@ -0,0 +1,150 @@ + + + 4.0.0 + com.example + demo-web + 1.0-SNAPSHOT + + + 2.22.0 + 0.14.0 + UTF-8 + 1.8 + 1.8 + + + + + io.quarkus + quarkus-bom + ${quarkus.version} + pom + import + + + + + + + io.quarkus + quarkus-resteasy + + + io.quarkus + quarkus-junit5 + test + + + io.quarkus + quarkus-smallrye-fault-tolerance + + + io.quarkus + quarkus-smallrye-health + + + io.quarkus + quarkus-smallrye-jwt + + + io.quarkus + quarkus-resteasy-jsonb + + + io.quarkus + quarkus-smallrye-metrics + + + io.quarkus + quarkus-smallrye-openapi + + + io.quarkus + quarkus-smallrye-opentracing + + + io.quarkus + quarkus-smallrye-rest-client + + + io.rest-assured + rest-assured + 3.3.0 + test + + + + + ${project.artifactId} + + + io.quarkus + quarkus-maven-plugin + ${quarkus.version} + + + + build + + + + + + maven-surefire-plugin + ${surefire-plugin.version} + + + org.jboss.logmanager.LogManager + + + + + + + + native + + + native + + + + + + io.quarkus + quarkus-maven-plugin + ${quarkus.version} + + + + native-image + + + true + + + + + + maven-failsafe-plugin + ${surefire-plugin.version} + + + + integration-test + verify + + + + ${project.build.directory}/${project.build.finalName}-runner + + + + + + + + + + diff --git a/web/src/main/java/com/example/demo/DemoRestApplication.java b/web/src/main/java/com/example/demo/DemoRestApplication.java new file mode 100755 index 0000000..2fd3f9a --- /dev/null +++ b/web/src/main/java/com/example/demo/DemoRestApplication.java @@ -0,0 +1,48 @@ +package com.example.demo; + +import io.quarkus.runtime.ShutdownEvent; +import io.quarkus.runtime.StartupEvent; +import org.eclipse.microprofile.auth.LoginConfig; +import org.eclipse.microprofile.openapi.annotations.ExternalDocumentation; +import org.eclipse.microprofile.openapi.annotations.OpenAPIDefinition; +import org.eclipse.microprofile.openapi.annotations.info.Contact; +import org.eclipse.microprofile.openapi.annotations.info.Info; +import org.eclipse.microprofile.openapi.annotations.info.License; +import org.eclipse.microprofile.openapi.annotations.servers.Server; + +import javax.enterprise.event.Observes; +import javax.ws.rs.ApplicationPath; +import javax.ws.rs.core.Application; + +/** + * + */ +@ApplicationPath("/demo1") +@LoginConfig(authMethod = "MP-JWT", realmName = "quarkus-quickstart") +@OpenAPIDefinition( + info = @Info( + title = "Quarkus MicroProfile 2.2 Extensions Demo", + version = "1.0", + contact = @Contact( + name = "QUARKUS - COMMUNITY", + url = "https://quarkus.io/community/", + email = "quarkus-dev+subscribe@googlegroups.com"), + license = @License( + name = "Apache 2.0", + url = "http://www.apache.org/licenses/LICENSE-2.0.html") + ), + servers = { + @Server(url = "http://localhost:8080/", description = "demo1 host"), + @Server(url = "http://localhost:8081/", description = "demo2 host") + }, + externalDocs = @ExternalDocumentation(url="http://microprofile.io", description = "Eclipse MicroProfile Homepage") +) +public class DemoRestApplication extends Application { + void onStart(@Observes StartupEvent event) { + System.out.printf("DemoRestApplication.onStart, event=%s%n", event); + } + + void onStop(@Observes ShutdownEvent event) { + System.out.printf("DemoRestApplication.onStop, event=%s%n", event); + } +} diff --git a/src/main/java/com/example/demo/HelloController.java b/web/src/main/java/com/example/demo/HelloController.java similarity index 71% rename from src/main/java/com/example/demo/HelloController.java rename to web/src/main/java/com/example/demo/HelloController.java index af91595..7060cb0 100755 --- a/src/main/java/com/example/demo/HelloController.java +++ b/web/src/main/java/com/example/demo/HelloController.java @@ -3,6 +3,8 @@ import javax.inject.Singleton; import javax.ws.rs.GET; import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; /** * @@ -12,6 +14,7 @@ public class HelloController { @GET + @Produces(MediaType.TEXT_PLAIN) public String sayHello() { return "Hello World"; } diff --git a/web/src/main/java/com/example/demo/config/ConfigTestController.java b/web/src/main/java/com/example/demo/config/ConfigTestController.java new file mode 100755 index 0000000..0ebd90d --- /dev/null +++ b/web/src/main/java/com/example/demo/config/ConfigTestController.java @@ -0,0 +1,50 @@ +package com.example.demo.config; + +import org.eclipse.microprofile.config.Config; +import org.eclipse.microprofile.config.ConfigProvider; +import org.eclipse.microprofile.config.inject.ConfigProperty; + +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +/** + * Examples of config injection and API usage + */ +@Path("/config") +@RequestScoped +public class ConfigTestController { + + @Inject + @ConfigProperty(name = "injected.value") + String injectedValue; + @Inject + @ConfigProperty(name = "injected.piValue", defaultValue = "pi5=3.14159") + Double piValue; + + @Path("/injected") + @GET + @Produces(MediaType.TEXT_PLAIN) + public String getInjectedConfigValue() { + return String.format("Config value as Injected by CDI: '%s'", injectedValue); + } + + @Path("/injectedPi") + @GET + @Produces(MediaType.TEXT_PLAIN) + public String getInjectedPiValue() { + return String.format("Injected Pi value: '%.10f'", piValue.doubleValue()); + } + + @Path("/lookup") + @GET + @Produces(MediaType.TEXT_PLAIN) + public String getLookupConfigValue() { + Config config = ConfigProvider.getConfig(); + String value = config.getValue("lookup.value", String.class); + return String.format("Config value via API lookup: '%s'", value); + } +} diff --git a/web/src/main/java/com/example/demo/config/NamedNumber.java b/web/src/main/java/com/example/demo/config/NamedNumber.java new file mode 100644 index 0000000..ca4eee4 --- /dev/null +++ b/web/src/main/java/com/example/demo/config/NamedNumber.java @@ -0,0 +1,19 @@ +package com.example.demo.config; + +public class NamedNumber { + private String name; + private Number number; + + public NamedNumber(String name, Number number) { + this.name = name; + this.number = number; + } + + public String getName() { + return name; + } + + public Number getNumber() { + return number; + } +} diff --git a/web/src/main/java/com/example/demo/config/NamedNumberConverter.java b/web/src/main/java/com/example/demo/config/NamedNumberConverter.java new file mode 100644 index 0000000..f1a59e5 --- /dev/null +++ b/web/src/main/java/com/example/demo/config/NamedNumberConverter.java @@ -0,0 +1,19 @@ +package com.example.demo.config; + +import org.eclipse.microprofile.config.spi.Converter; + +/** + * Example custom converter. Not working in Quarkus 13.2. + */ +public class NamedNumberConverter implements Converter { + /** + * Parses an assignment type of expression into a name and number value + * @param value name=Number expression + * @return NamedNumber instance + */ + @Override + public NamedNumber convert(String value) { + String[] parts = value.split("="); + return null; + } +} diff --git a/web/src/main/java/com/example/demo/health/CheckDiskspace.java b/web/src/main/java/com/example/demo/health/CheckDiskspace.java new file mode 100644 index 0000000..f1c9935 --- /dev/null +++ b/web/src/main/java/com/example/demo/health/CheckDiskspace.java @@ -0,0 +1,48 @@ +package com.example.demo.health; + +import java.io.File; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; + +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.eclipse.microprofile.health.Health; +import org.eclipse.microprofile.health.HealthCheck; +import org.eclipse.microprofile.health.HealthCheckResponse; +import org.eclipse.microprofile.health.HealthCheckResponseBuilder; + +@Health +@ApplicationScoped +public class CheckDiskspace implements HealthCheck { + @Inject + @ConfigProperty(name = "health.pathToMonitor") + String pathToMonitor; + @Inject + @ConfigProperty(name = "health.freeSpaceThreshold") + long freeSpaceThreshold; + + @Override + public HealthCheckResponse call() { + HealthCheckResponseBuilder builder = HealthCheckResponse.named("diskspace"); + checkDiskspace(builder); + return builder.build(); + } + + private void checkDiskspace(HealthCheckResponseBuilder builder) { + File root = new File(pathToMonitor); + long usableSpace = root.getUsableSpace(); + long freeSpace = root.getFreeSpace(); + long pctFree = 0; + if (usableSpace > 0) { + pctFree = (100 * usableSpace) / freeSpace; + } + builder.withData("path", root.getAbsolutePath()) + .withData("exits", root.exists()) + .withData("usableSpace", usableSpace) + .withData("freeSpace", freeSpace) + .withData("pctFree", pctFree) + .state(freeSpace >= freeSpaceThreshold); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/demo/health/ServiceHealthCheck.java b/web/src/main/java/com/example/demo/health/ServiceHealthCheck.java similarity index 61% rename from src/main/java/com/example/demo/health/ServiceHealthCheck.java rename to web/src/main/java/com/example/demo/health/ServiceHealthCheck.java index 387df00..63f05b1 100755 --- a/src/main/java/com/example/demo/health/ServiceHealthCheck.java +++ b/web/src/main/java/com/example/demo/health/ServiceHealthCheck.java @@ -12,8 +12,11 @@ public class ServiceHealthCheck implements HealthCheck { @Override public HealthCheckResponse call() { - - return HealthCheckResponse.named(ServiceHealthCheck.class.getSimpleName()).up().build(); - + return HealthCheckResponse.named("service-check") + .withData("port", 12345) + .withData("isSecure", true) + .withData("hostname", "service.jboss.com") + .up() + .build(); } } diff --git a/web/src/main/java/com/example/demo/jwt/JwtEndpoint.java b/web/src/main/java/com/example/demo/jwt/JwtEndpoint.java new file mode 100644 index 0000000..a08757a --- /dev/null +++ b/web/src/main/java/com/example/demo/jwt/JwtEndpoint.java @@ -0,0 +1,57 @@ +package com.example.demo.jwt; + +import java.util.Optional; + +import javax.annotation.security.DenyAll; +import javax.annotation.security.PermitAll; +import javax.annotation.security.RolesAllowed; +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.SecurityContext; + +import org.eclipse.microprofile.jwt.Claim; +import org.eclipse.microprofile.jwt.ClaimValue; +import org.eclipse.microprofile.jwt.Claims; +import org.eclipse.microprofile.jwt.JsonWebToken; + +@Path("/jwt") +@DenyAll +@RequestScoped +public class JwtEndpoint { + @Inject + private JsonWebToken jwt; + @Inject + @Claim(standard = Claims.raw_token) + private ClaimValue jwtString; + @Inject + @Claim(standard = Claims.upn) + private ClaimValue upn; + @Context + private SecurityContext context; + + @GET + @Path("/openHello") + @Produces(MediaType.TEXT_PLAIN) + @PermitAll + public String openHello() { + String user = jwt == null ? "anonymous" : jwt.getName(); + String upnClaim = upn != null ? upn.getValue() : "no-upn"; + return String.format("Hello[open] user=%s, upn=%s", user, upnClaim); + } + + @GET + @Path("/secureHello") + @Produces(MediaType.TEXT_PLAIN) + @RolesAllowed("user") + public String secureHello() { + String user = jwt == null ? "anonymous" : jwt.getName(); + String scheme = context.getAuthenticationScheme(); + boolean isUserInRole = context.isUserInRole("GOTO-attendee"); + return String.format("Hello[secure] user=%s, upn=%s, scheme=%s, isUserInRole(GOTO-attendee)=%s", user, upn.getValue(), scheme, isUserInRole); + } +} diff --git a/src/main/java/com/example/demo/metric/MetricController.java b/web/src/main/java/com/example/demo/metric/MetricController.java similarity index 75% rename from src/main/java/com/example/demo/metric/MetricController.java rename to web/src/main/java/com/example/demo/metric/MetricController.java index 35bea1a..0675f18 100755 --- a/src/main/java/com/example/demo/metric/MetricController.java +++ b/web/src/main/java/com/example/demo/metric/MetricController.java @@ -6,28 +6,29 @@ import org.eclipse.microprofile.metrics.annotation.Metric; import org.eclipse.microprofile.metrics.annotation.Timed; -//import javax.enterprise.context.RequestScoped; import javax.enterprise.context.ApplicationScoped; import javax.inject.Inject; import javax.ws.rs.GET; import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import java.util.Random; @Path("/metric") -//@RequestScoped @ApplicationScoped //Required for @Gauge public class MetricController { @Inject @Metric(name = "endpoint_counter") - private Counter counter; @Path("timed") @Timed(name = "timed-request") @GET + @Produces(MediaType.TEXT_PLAIN) public String timedRequest() { + long start = System.currentTimeMillis(); // Demo, not production style int wait = new Random().nextInt(1000); try { @@ -36,13 +37,17 @@ public String timedRequest() { // Demo e.printStackTrace(); } + long end = System.currentTimeMillis(); + long delay = end - start; - return "Request is used in statistics, check with the Metrics call."; + long count = getCustomerCount(); + return String.format("MetricController#timedRequest, delay[0-1000]=%d, count=%d", delay, count); } @Path("increment") @GET + @Produces(MediaType.TEXT_PLAIN) public long doIncrement() { counter.inc(); return counter.getCount(); diff --git a/src/main/java/com/example/demo/resilient/ResilienceController.java b/web/src/main/java/com/example/demo/resilient/ResilienceController.java similarity index 87% rename from src/main/java/com/example/demo/resilient/ResilienceController.java rename to web/src/main/java/com/example/demo/resilient/ResilienceController.java index 036f63a..846d40e 100755 --- a/src/main/java/com/example/demo/resilient/ResilienceController.java +++ b/web/src/main/java/com/example/demo/resilient/ResilienceController.java @@ -6,6 +6,8 @@ import javax.enterprise.context.ApplicationScoped; import javax.ws.rs.GET; import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; @Path("/resilience") @ApplicationScoped @@ -14,6 +16,7 @@ public class ResilienceController { @Fallback(fallbackMethod = "fallback") // better use FallbackHandler @Timeout(500) @GET + @Produces(MediaType.TEXT_PLAIN) public String checkTimeout() { try { Thread.sleep(700L); diff --git a/web/src/main/java/com/example/demo/restclient/Now.java b/web/src/main/java/com/example/demo/restclient/Now.java new file mode 100644 index 0000000..d4c2905 --- /dev/null +++ b/web/src/main/java/com/example/demo/restclient/Now.java @@ -0,0 +1,63 @@ +package com.example.demo.restclient; + +/** + * Data object returned by WorldClockApi and TimeService + */ +public class Now { + String currentDateTime; + String utcOffset; + boolean isDayLightSavingsTime; + String dayOfTheWeek; + String timeZoneName; + + public String getCurrentDateTime() { + return currentDateTime; + } + + public void setCurrentDateTime(String currentDateTime) { + this.currentDateTime = currentDateTime; + } + + public String getUtcOffset() { + return utcOffset; + } + + public void setUtcOffset(String utcOffset) { + this.utcOffset = utcOffset; + } + + public boolean isDayLightSavingsTime() { + return isDayLightSavingsTime; + } + + public void setDayLightSavingsTime(boolean dayLightSavingsTime) { + isDayLightSavingsTime = dayLightSavingsTime; + } + + public String getDayOfTheWeek() { + return dayOfTheWeek; + } + + public void setDayOfTheWeek(String dayOfTheWeek) { + this.dayOfTheWeek = dayOfTheWeek; + } + + public String getTimeZoneName() { + return timeZoneName; + } + + public void setTimeZoneName(String timeZoneName) { + this.timeZoneName = timeZoneName; + } + + @Override + public String toString() { + return "Now{" + + "currentDateTime='" + currentDateTime + '\'' + + ", utcOffset='" + utcOffset + '\'' + + ", isDayLightSavingsTime=" + isDayLightSavingsTime + + ", dayOfTheWeek='" + dayOfTheWeek + '\'' + + ", timeZoneName='" + timeZoneName + '\'' + + '}'; + } +} diff --git a/web/src/main/java/com/example/demo/restclient/TimeService.java b/web/src/main/java/com/example/demo/restclient/TimeService.java new file mode 100644 index 0000000..048788e --- /dev/null +++ b/web/src/main/java/com/example/demo/restclient/TimeService.java @@ -0,0 +1,53 @@ +package com.example.demo.restclient; + +import java.net.URI; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.eclipse.microprofile.openapi.annotations.ExternalDocumentation; +import org.eclipse.microprofile.openapi.annotations.Operation; +import org.eclipse.microprofile.openapi.annotations.tags.Tag; +import org.eclipse.microprofile.rest.client.RestClientBuilder; +import org.eclipse.microprofile.rest.client.inject.RestClient; + +/** + * Access the public worldclockapi UTC time using RestClient + */ +@Path("/time") +@ApplicationScoped +public class TimeService { + @Inject + @RestClient + WorldClockApi clockApi; + @Inject + @ConfigProperty(name = "restclient.targetBaseUri", defaultValue = "http://localhost:8081/demo2") + String targetBaseUri; + + @GET + @Path("/now") + @Produces(MediaType.APPLICATION_JSON) + @Tag(name = "time", description = "time service methods") + @ExternalDocumentation(description = "Basic World Clock API Home.", + url = "http://worldclockapi.com/") + @Operation(summary = "Queries the WorldClockApi using the MP-RestClient", + description = "Uses the WorldClockApi type proxy injected by the MP-RestClient to access the worldclockapi.com service") + public Now utc() { + return clockApi.utc(); + } + + @GET + @Path("/proxyUserTZ") + @Produces(MediaType.APPLICATION_JSON) + public Now proxyUserTZ() { + UserTimeService remoteApi = RestClientBuilder.newBuilder() + .baseUri(URI.create(targetBaseUri)) + .build(UserTimeService.class); + return remoteApi.userNow(); + } +} diff --git a/web/src/main/java/com/example/demo/restclient/UserTimeService.java b/web/src/main/java/com/example/demo/restclient/UserTimeService.java new file mode 100644 index 0000000..0d45994 --- /dev/null +++ b/web/src/main/java/com/example/demo/restclient/UserTimeService.java @@ -0,0 +1,19 @@ +package com.example.demo.restclient; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders; +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; + +@RegisterRestClient(baseUri = "http://localhost:8081/demo2/protected") +@RegisterClientHeaders +@Path("/time") +public interface UserTimeService { + @GET + @Path("/userNow") + @Produces(MediaType.APPLICATION_JSON) + public Now userNow(); +} diff --git a/web/src/main/java/com/example/demo/restclient/WorldClockApi.java b/web/src/main/java/com/example/demo/restclient/WorldClockApi.java new file mode 100644 index 0000000..4aa684a --- /dev/null +++ b/web/src/main/java/com/example/demo/restclient/WorldClockApi.java @@ -0,0 +1,38 @@ +package com.example.demo.restclient; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; + +/** + * http://worldclockapi.com/api/json/utc/now + * { + * "$id": "1", + * "currentDateTime": "2019-04-25T03:23Z", + * "utcOffset": "00:00:00", + * "isDayLightSavingsTime": false, + * "dayOfTheWeek": "Thursday", + * "timeZoneName": "UTC", + * "currentFileTime": 132006362107999360, + * "ordinalDate": "2019-115", + * "serviceResponse": null + * } + */ +@RegisterRestClient(baseUri = WorldClockApi.BASE_URL) +public interface WorldClockApi { + static final String BASE_URL = "http://worldclockapi.com/api/json"; + + @GET + @Path("/utc/now") + @Produces(MediaType.APPLICATION_JSON) + Now utc(); + + @GET + @Path("{tz}/now") + @Produces(MediaType.APPLICATION_JSON) + Now tz(@PathParam("tz") String tz); +} diff --git a/web/src/main/java/com/example/demo/tracing/TracedEndpoint.java b/web/src/main/java/com/example/demo/tracing/TracedEndpoint.java new file mode 100644 index 0000000..75c67ce --- /dev/null +++ b/web/src/main/java/com/example/demo/tracing/TracedEndpoint.java @@ -0,0 +1,38 @@ +package com.example.demo.tracing; + +import javax.enterprise.context.ApplicationScoped; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +import org.eclipse.microprofile.opentracing.Traced; + +@ApplicationScoped +@Path("/traced") +public class TracedEndpoint { + @GET + @Path("/randomDelay") + @Produces(MediaType.TEXT_PLAIN) + @Traced(operationName = "TracedEndpoint#demoRandomDelay") + public String randomDelay() { + long start = System.currentTimeMillis(); + // 0-5 seconds random sleep + long sleep = Math.round(Math.random() * 5000); + try { + Thread.sleep(sleep); + } catch (InterruptedException e) { + e.printStackTrace(); + } + long end = System.currentTimeMillis(); + return String.format("TracedEndpoint.randomDelay[0-5000], elapsed=%d", (end - start)); + } + + @GET + @Path("/untraced") + @Produces(MediaType.TEXT_PLAIN) + @Traced(false) + public String untraced() { + return "No tracing"; + } +} diff --git a/web/src/main/resources/META-INF/resources/images/jdukeQ.jpg b/web/src/main/resources/META-INF/resources/images/jdukeQ.jpg new file mode 100644 index 0000000..fb7c018 Binary files /dev/null and b/web/src/main/resources/META-INF/resources/images/jdukeQ.jpg differ diff --git a/web/src/main/resources/META-INF/resources/index.html b/web/src/main/resources/META-INF/resources/index.html new file mode 100755 index 0000000..b4e9f0a --- /dev/null +++ b/web/src/main/resources/META-INF/resources/index.html @@ -0,0 +1,204 @@ + + + + + + + + + + + Eclipse MicroProfile Demo + + + + + +
+
+ +

Quarkusd MicroProfile Demo

+

A demo illustrating the MicroProfile 2.2 features in the Quarkus runtime.

+
+
+ +
+ +
+
+
+

Hello JAX-RS endpoint

+ curl http://localhost:8080/demo1/hello +
+
+

Config injected.value test

+ curl http://localhost:8080/demo1/config/injected +

Injected Pi value

+ demo1/config/injectedPi
+

Config values by lookup

+ demo1/config/lookup
+
+
+

Demo1 Health Status

+ Demo1 health status
+

Demo2 Health Status

+ Demo2 health status
+
+ +
+

Jaegar UI

+ demo1/traced/randomDelay
+

Jaegar UI

+ + http://localhost:16686/ +
+
+

Open API Document

+ /openapi
+

Swagger UI

+ /swagger-ui
+ +
+
+

RestClient access WorldClockApi

+ demo1/time/now
+

RestClient WorldClockApi Service on Demo2

+ Using Authentication: Bearer MP-JWT token...
+

RestClient proxy to WorldClockApi Service on Demo2

+ Using Authentication: Bearer MP-JWT token...
+
+ +
+

Access Endpoint Without JWT

+ demo1/jwt/openHello
+

Access Endpoint With JWT

+ Using Authentication: Bearer MP-JWT token...
+
+
+ +
+
+
+ + +
+
+
+
Refresh ? before token refresh
+
+ +
+ +
+
+

+ +

+
+ +
+
+
+ +
Access Token +

+                        
+ +
+
+
+
+ +
+
+

+ +

+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+ + + + + + + + + + diff --git a/web/src/main/resources/META-INF/resources/keycloak.json b/web/src/main/resources/META-INF/resources/keycloak.json new file mode 100644 index 0000000..233c234 --- /dev/null +++ b/web/src/main/resources/META-INF/resources/keycloak.json @@ -0,0 +1,8 @@ +{ + "realm": "quarkus-quickstart", + "auth-server-url": "http://localhost:8180/auth", + "ssl-required": "external", + "resource": "microprofile-jwt-client", + "public-client": true, + "confidential-port": 0 +} \ No newline at end of file diff --git a/web/src/main/resources/META-INF/resources/script/app.js b/web/src/main/resources/META-INF/resources/script/app.js new file mode 100644 index 0000000..61ca854 --- /dev/null +++ b/web/src/main/resources/META-INF/resources/script/app.js @@ -0,0 +1,149 @@ + +$(document).ready(function(){ + $('a[data-toggle="pill"]').on('show.bs.tab', function(e) { + localStorage.setItem('activeTab', $(e.target).attr('href')); + }); + var activeTab = localStorage.getItem('activeTab'); + if(activeTab){ + $('#v-pills-tab a[href="' + activeTab + '"]').tab('show'); + } +}); + +function refreshToken(minValidity) { + keycloak.updateToken(minValidity).success(function(refreshed) { + if (refreshed) { + //output(keycloak.tokenParsed); + } else { + output('Token not refreshed, valid for ' + Math.round(keycloak.tokenParsed.exp + keycloak.timeSkew - new Date().getTime() / 1000) + ' seconds'); + } + }).error(function() { + output('Failed to refresh token'); + }); +} + + +function output(data) { + if (typeof data === 'object') { + data = JSON.stringify(data, null, ' '); + } + document.getElementById('output').innerHTML = data; +} + +function event(event) { + document.getElementById('output').innerHTML = event; +} + +var keycloak = Keycloak(); +var authorization; +keycloak.onAuthSuccess = function () { + event('Auth Success'); +}; + +keycloak.onAuthError = function (errorData) { + event("Auth Error: " + JSON.stringify(errorData) ); +}; + +keycloak.onAuthRefreshSuccess = function () { + event('Auth Refresh Success'); + document.getElementById('token64').innerHTML = keycloak.token; + document.getElementById('refreshToken').innerHTML = JSON.stringify(keycloak.refreshTokenParsed, null, 4); + document.getElementById('idToken').innerHTML = JSON.stringify(keycloak.idTokenParsed,null, 4); + document.getElementById('token').innerHTML = JSON.stringify(keycloak.tokenParsed,null, 4); + +}; + +keycloak.onAuthRefreshError = function () { + event('Auth Refresh Error'); +}; + +keycloak.onAuthLogout = function () { + event('Logout'); +}; + +keycloak.onTokenExpired = function () { + event('Access token expired.'); + if(document.getElementById("refresh").checked) { + refreshToken(); + } +}; + +// Flow can be changed to 'implicit' or 'hybrid', but then client must enable implicit flow in admin console too +var initOptions = { + responseMode: 'fragment', + flow: 'standard', + onload: 'check-sso' +}; + +function startTimer(duration, display) { + var timer = duration, minutes, seconds; + setInterval(function () { + minutes = parseInt(timer / 60, 10) + seconds = parseInt(timer % 60, 10); + + minutes = minutes < 10 ? "0" + minutes : minutes; + seconds = seconds < 10 ? "0" + seconds : seconds; + + display.textContent = minutes + ":" + seconds; + + if (--timer < 0) { + timer = duration; + } + }, 1000); +} + +function loginChange(radio) { +console.log("loginChange, %o", radio); + if(radio.id == "login" && !keycloak.authenticated) { + console.log("Loging IN"); + keycloak.login(); + } else { + console.log("Loging OUT"); + keycloak.logout(); + } +} + +/** +*/ +function secureRequest(port, path) { + var oReq = new XMLHttpRequest(); + var url = "http://" + location.hostname +":"+port+"/" + path; + console.log("secureRequest, hostname=%s, base=%s, path=%s, url=%s", location.hostname, port, path, url); + oReq.open('GET', url, true); + oReq.setRequestHeader('Authorization', 'Bearer ' + keycloak.token); + oReq.onload = function () { + console.log("secureRequest.onLoad, %o", oReq); + if (oReq.readyState === oReq.DONE) { + if (oReq.status === 200) { + var contentWindow = document.getElementById('reply-content').contentWindow; + contentWindow.document.write("
\n"); + contentWindow.document.write(oReq.responseText); + } + } + }; + console.log("Send request, %s", url); + oReq.send(); +} + +keycloak.init(initOptions).success(function(authenticated) { + authorization = new KeycloakAuthorization(keycloak); + /* + document.getElementById('refreshToken').innerHTML = JSON.stringify(keycloak.refreshTokenParsed, null, 4); + document.getElementById('idToken').innerHTML = JSON.stringify(keycloak.idTokenParsed,null, 4); + */ + document.getElementById('token').innerHTML = JSON.stringify(keycloak.tokenParsed,null, 4); + document.getElementById('token64').innerHTML = keycloak.token; + output('Init Success (' + (authenticated ? 'Authenticated' : 'Not Authenticated') + ')'); + var fiveMinutes = Math.round(keycloak.tokenParsed.exp + keycloak.timeSkew - new Date().getTime() / 1000); + display = document.querySelector('#time'); + startTimer(fiveMinutes, display); + if(!authenticated) { + console.log("Not authenticated, selected Logout button"); + document.getElementById("logout").select(); + } + else { + console.log("Authenticated, selected Login button"); + document.getElementById("login").select(); + } +}).error(function() { + output('ERROR: Failed to initialize KeyCloak'); +}); \ No newline at end of file diff --git a/web/src/main/resources/META-INF/resources/style/grid.css b/web/src/main/resources/META-INF/resources/style/grid.css new file mode 100644 index 0000000..83f7317 --- /dev/null +++ b/web/src/main/resources/META-INF/resources/style/grid.css @@ -0,0 +1,45 @@ +* {box-sizing: border-box;} + + +.jumbotron { + /*background: #00FFFF*/ + background: blue; + color: white; +} + +.wrapper > div { + border: 2px solid rgb(54, 54, 54); + border-radius: 10px; + + padding: 1em; + word-wrap: break-word; + font-size: 10px; + overflow-y: scroll; +} +.wrapper { + display: grid; + height:75%; + grid-template-columns: 600px 400px 350px; + grid-gap: 10px; + grid-auto-rows: auto; + overflow-y: scroll; + } + +.one { + grid-column: 1 ; + grid-row: 1 ; +} +.two { + grid-column: 2; + grid-row: 1 ; +} +.three { + grid-column: 3; + grid-row: 1 ; +} + +pre.prettyprint { + border: none !important; + overflow: hidden; + text-align: -webkit-auto +} diff --git a/web/src/main/resources/application.properties b/web/src/main/resources/application.properties new file mode 100755 index 0000000..744bdcd --- /dev/null +++ b/web/src/main/resources/application.properties @@ -0,0 +1,41 @@ +quarkus.http.port=8080 + +# MP Config values for ConfigTestController +injected.value=Injected value +injected.piValue=3.1415926532 +lookup.value=A Lookup value + +# MP-JWT Config +mp.jwt.verify.publickey.location=http://localhost:8180/auth/realms/quarkus-quickstart/protocol/openid-connect/certs +mp.jwt.verify.issuer=http://localhost:8180/auth/realms/quarkus-quickstart +quarkus.smallrye-jwt.auth-mechanism=MP-JWT +quarkus.smallrye-jwt.enabled=true + +# Diskspace health check properties +health.pathToMonitor=/Users/starksm +health.freeSpaceThreshold=1073741824 + +# This would override the WorldClockApi baseUri. +com.example.demo.restclient.WorldClockApi/mp-rest/url=http://worldclockapi.com/api/json +com.example.demo.restclient.UserTimeService/mp-rest/url=http://localhost:8081/demo/protected +# Propagate Authentication and OpenTracing headers +org.eclipse.microprofile.rest.client.propagateHeaders=Authorization,X-B3-TraceId,X-B3-ParentSpanId,X-B3-SpanId,X-B3-Sampled + +# OpenTracing +quarkus.jaeger.service-name=QuarkusMPDemo +quarkus.jaeger.sampler-type=const +quarkus.jaeger.sampler-param=1 +quarkus.jaeger.endpoint=http://localhost:14268/api/traces + +# Root logger at DEBUG +quarkus.log.level=DEBUG +# INFO console logging +quarkus.log.console.format=%d{HH:mm:ss} %-5p [%c{2.}]] (%t) %s%e%n +quarkus.log.console.level=INFO + +# Debug log file +quarkus.log.file.enable=true +quarkus.log.file.path=target/trace.log +quarkus.log.file.level=TRACE +quarkus.log.category."io.quarkus.smallrye".level=TRACE +quarkus.log.category."org.jboss.resteasy.core".level=TRACE \ No newline at end of file diff --git a/web/src/test/java/com/example/demo/WorldClockApiTest.java b/web/src/test/java/com/example/demo/WorldClockApiTest.java new file mode 100644 index 0000000..69d979b --- /dev/null +++ b/web/src/test/java/com/example/demo/WorldClockApiTest.java @@ -0,0 +1,39 @@ +package com.example.demo; + +import java.io.StringReader; +import java.net.HttpURLConnection; +import java.net.URISyntaxException; + +import javax.json.Json; +import javax.json.JsonObject; +import javax.json.JsonReader; + +import com.example.demo.restclient.Now; +import io.quarkus.test.junit.QuarkusTest; +import io.restassured.response.Response; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import static io.restassured.RestAssured.given; + +@QuarkusTest +@Disabled +public class WorldClockApiTest { + @Test + @Disabled + public void testNow() throws URISyntaxException { + Response response = given() + .when() + .get("/data/time/now") + .andReturn(); + + Assertions.assertEquals(HttpURLConnection.HTTP_OK, response.getStatusCode()); + String replyString = response.body().asString(); + JsonReader jsonReader = Json.createReader(new StringReader(replyString)); + JsonObject reply = jsonReader.readObject(); + System.out.println(reply); + Now numbers = response.as(Now.class); + System.out.println(numbers); + } +}