diff --git a/docs/src/main/asciidoc/images/oidc-slack-1.png b/docs/src/main/asciidoc/images/oidc-slack-1.png new file mode 100644 index 00000000000000..f455fb8a479d11 Binary files /dev/null and b/docs/src/main/asciidoc/images/oidc-slack-1.png differ diff --git a/docs/src/main/asciidoc/images/oidc-slack-2.png b/docs/src/main/asciidoc/images/oidc-slack-2.png new file mode 100644 index 00000000000000..186fb084608053 Binary files /dev/null and b/docs/src/main/asciidoc/images/oidc-slack-2.png differ diff --git a/docs/src/main/asciidoc/images/oidc-slack-3.png b/docs/src/main/asciidoc/images/oidc-slack-3.png new file mode 100644 index 00000000000000..3900deb4664bb3 Binary files /dev/null and b/docs/src/main/asciidoc/images/oidc-slack-3.png differ diff --git a/docs/src/main/asciidoc/images/oidc-slack-4.png b/docs/src/main/asciidoc/images/oidc-slack-4.png new file mode 100644 index 00000000000000..6425b138c333dc Binary files /dev/null and b/docs/src/main/asciidoc/images/oidc-slack-4.png differ diff --git a/docs/src/main/asciidoc/images/oidc-slack-5.png b/docs/src/main/asciidoc/images/oidc-slack-5.png new file mode 100644 index 00000000000000..76d742a85ace86 Binary files /dev/null and b/docs/src/main/asciidoc/images/oidc-slack-5.png differ diff --git a/docs/src/main/asciidoc/security-openid-connect-providers.adoc b/docs/src/main/asciidoc/security-openid-connect-providers.adoc index f24cbd85555211..898cc468bad843 100644 --- a/docs/src/main/asciidoc/security-openid-connect-providers.adoc +++ b/docs/src/main/asciidoc/security-openid-connect-providers.adoc @@ -406,6 +406,42 @@ quarkus.oidc.token.customizer-name=azure-access-token-customizer ==== +[[slack]] +=== Slack + +Create a https://api.slack.com/authentication/sign-in-with-slack#setup[Slack application]: + +image::oidc-slack-1.png[role="thumb"] + +Select application name, workspace, and remember it, you will need it later: + +image::oidc-slack-2.png[role="thumb"] + +Please save client id and secret displayed on the next page, you will need them later: + +image::oidc-slack-3.png[role="thumb"] + +Configure redirect URLs. +Slack provider requires HTTPS protocol, for development purposes, you can use ngrok: + +image::oidc-slack-4.png[role="thumb"] + +You can now configure your `application.properties`: + +[source,properties] +---- +quarkus.oidc.provider=slack +quarkus.oidc.client-id= +quarkus.oidc.credentials.secret= +quarkus.oidc.authentication.extra-params.team=quarkus-slack <1> +---- +<1> Use the `team` parameter to refer to the workspace you chose during the Slack OIDC application registration. + +Open your browser and navigate to your application `https://.ngrok-free.app/`. +Quarkus will redirect you to Slack provider on the first request where you can grant required permissions: + +image::oidc-slack-5.png[role="thumb"] + [[spotify]] === Spotify diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/OidcTenantConfig.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/OidcTenantConfig.java index a5cf9a739d8205..abc3b0a0627e93 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/OidcTenantConfig.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/OidcTenantConfig.java @@ -2009,6 +2009,7 @@ public static enum Provider { LINKEDIN, MASTODON, MICROSOFT, + SLACK, SPOTIFY, STRAVA, TWITCH, diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/providers/KnownOidcProviders.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/providers/KnownOidcProviders.java index 9a1e95130b5e01..4c47995c1e2579 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/providers/KnownOidcProviders.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/providers/KnownOidcProviders.java @@ -19,6 +19,7 @@ public static OidcTenantConfig provider(OidcTenantConfig.Provider provider) { case LINKEDIN -> linkedIn(); case MASTODON -> mastodon(); case MICROSOFT -> microsoft(); + case SLACK -> slack(); case SPOTIFY -> spotify(); case STRAVA -> strava(); case TWITCH -> twitch(); @@ -26,6 +27,15 @@ public static OidcTenantConfig provider(OidcTenantConfig.Provider provider) { }; } + private static OidcTenantConfig slack() { + OidcTenantConfig ret = new OidcTenantConfig(); + ret.setAuthServerUrl("https://slack.com"); + ret.setApplicationType(OidcTenantConfig.ApplicationType.WEB_APP); + ret.getToken().setPrincipalClaim("name"); + ret.getAuthentication().setForceRedirectHttpsScheme(true); + return ret; + } + private static OidcTenantConfig linkedIn() { OidcTenantConfig ret = new OidcTenantConfig(); ret.setAuthServerUrl("https://www.linkedin.com/oauth"); diff --git a/extensions/oidc/runtime/src/test/java/io/quarkus/oidc/runtime/KnownOidcProvidersTest.java b/extensions/oidc/runtime/src/test/java/io/quarkus/oidc/runtime/KnownOidcProvidersTest.java index 93571c0a964873..29a09e5c427ecd 100644 --- a/extensions/oidc/runtime/src/test/java/io/quarkus/oidc/runtime/KnownOidcProvidersTest.java +++ b/extensions/oidc/runtime/src/test/java/io/quarkus/oidc/runtime/KnownOidcProvidersTest.java @@ -588,4 +588,39 @@ public void testOverrideLinkedInProperties() throws Exception { assertFalse(config.getAuthentication().isForceRedirectHttpsScheme().get()); assertEquals(Method.BASIC, config.credentials.clientSecret.method.get()); } + + @Test + public void testAcceptSlackProperties() { + OidcTenantConfig tenant = new OidcTenantConfig(); + tenant.setTenantId(OidcUtils.DEFAULT_TENANT_ID); + OidcTenantConfig config = OidcUtils.mergeTenantConfig(tenant, KnownOidcProviders.provider(Provider.SLACK)); + + assertEquals(OidcUtils.DEFAULT_TENANT_ID, config.getTenantId().get()); + assertEquals(ApplicationType.WEB_APP, config.getApplicationType().get()); + assertTrue(config.isDiscoveryEnabled().orElse(true)); + assertEquals("https://slack.com", config.getAuthServerUrl().get()); + + assertEquals("name", config.token.principalClaim.get()); + assertTrue(config.authentication.forceRedirectHttpsScheme.orElse(false)); + } + + @Test + public void testOverrideSlackProperties() { + OidcTenantConfig tenant = new OidcTenantConfig(); + tenant.setTenantId("PattiSmith"); + tenant.setApplicationType(ApplicationType.SERVICE); + tenant.setDiscoveryEnabled(false); + tenant.setAuthServerUrl("https://private-slack.com"); + tenant.getToken().setPrincipalClaim("I you my own principal"); + tenant.getAuthentication().setForceRedirectHttpsScheme(false); + OidcTenantConfig config = OidcUtils.mergeTenantConfig(tenant, KnownOidcProviders.provider(Provider.SLACK)); + + assertEquals("PattiSmith", config.getTenantId().get()); + assertEquals(ApplicationType.SERVICE, config.getApplicationType().get()); + assertFalse(config.isDiscoveryEnabled().orElse(true)); + assertEquals("https://private-slack.com", config.getAuthServerUrl().get()); + + assertEquals("I you my own principal", config.token.principalClaim.get()); + assertFalse(config.authentication.forceRedirectHttpsScheme.orElse(false)); + } } diff --git a/integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/OidcDiscoveryJwksRequestCustomizer.java b/integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/OidcDiscoveryJwksRequestCustomizer.java index ac10fcb4822484..2587eaf55515d4 100644 --- a/integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/OidcDiscoveryJwksRequestCustomizer.java +++ b/integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/OidcDiscoveryJwksRequestCustomizer.java @@ -29,6 +29,7 @@ public void filter(OidcRequestContext rc) { private boolean isJwksRequest(HttpRequest request) { return request.uri().endsWith("/protocol/openid-connect/certs") || request.uri().endsWith("/auth/azure/jwk") - || request.uri().endsWith("/single-key-without-kid-thumbprint"); + || request.uri().endsWith("/single-key-without-kid-thumbprint") + || request.uri().endsWith("/openid/connect/keys"); } } diff --git a/integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/SlackCodeFlowResource.java b/integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/SlackCodeFlowResource.java new file mode 100644 index 00000000000000..f2f7f473341ca5 --- /dev/null +++ b/integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/SlackCodeFlowResource.java @@ -0,0 +1,26 @@ +package io.quarkus.it.keycloak; + +import jakarta.inject.Inject; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.core.SecurityContext; + +import io.quarkus.oidc.UserInfo; +import io.quarkus.security.Authenticated; + +@Path("/slack") +public class SlackCodeFlowResource { + + public record SlackResponseDto(String userPrincipalName, String userInfoEmail) { + } + + @Inject + UserInfo userInfo; + + @Authenticated + @GET + public SlackResponseDto get(SecurityContext securityContext) { + return new SlackResponseDto(securityContext.getUserPrincipal().getName(), userInfo.getEmail()); + } + +} diff --git a/integration-tests/oidc-wiremock/src/main/resources/application.properties b/integration-tests/oidc-wiremock/src/main/resources/application.properties index edeeaceebf8423..5c85055ef00611 100644 --- a/integration-tests/oidc-wiremock/src/main/resources/application.properties +++ b/integration-tests/oidc-wiremock/src/main/resources/application.properties @@ -270,3 +270,17 @@ quarkus.grpc.server.use-separate-server=false %issuer-based-resolver.quarkus.oidc.bearer-issuer-resolver.token.audience=https://correct-issuer.edu %issuer-based-resolver.quarkus.oidc.bearer-issuer-resolver.token.allow-jwt-introspection=false %issuer-based-resolver.quarkus.oidc.resolve-tenants-with-issuer=true + +# properties required to configure Slack provider +quarkus.oidc.slack.provider=slack +quarkus.oidc.slack.client-id=7925551513107.7922794171477 +quarkus.oidc.slack.credentials.secret=2b82d6039bc97946460fdec75fadd9b2 +quarkus.oidc.slack.authentication.extra-params.team=quarkus-oidc-slack-demo +# test properties required because Slack mock is not identical to Slack +quarkus.oidc.slack.tenant-paths=/slack +quarkus.oidc.slack.auth-server-url=http://localhost:8188 +quarkus.oidc.slack.token.lifespan-grace=2147483647 +quarkus.oidc.slack.authentication.force-redirect-https-scheme=false +quarkus.oidc.slack.authentication.verify-access-token=false +quarkus.oidc.slack.authentication.remove-redirect-parameters=false +quarkus.oidc.slack.authentication.scopes=oidc,profile,email diff --git a/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/BearerTokenOidcRecoveredTest.java b/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/BearerTokenOidcRecoveredTest.java index 4343bc1eebf823..50546df57be6bb 100644 --- a/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/BearerTokenOidcRecoveredTest.java +++ b/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/BearerTokenOidcRecoveredTest.java @@ -86,8 +86,8 @@ public void assertOidcServerAvailabilityReported() { String expectAuthServerUrl = RestAssured.get("/oidc-event/expected-auth-server-url").then().statusCode(200).extract() .asString(); RestAssured.given().get("/oidc-event/unavailable-auth-server-urls").then().statusCode(200) - .body(Matchers.is(expectAuthServerUrl)); + .body(Matchers.containsString(expectAuthServerUrl)); RestAssured.given().get("/oidc-event/available-auth-server-urls").then().statusCode(200) - .body(Matchers.is(expectAuthServerUrl)); + .body(Matchers.containsString(expectAuthServerUrl)); } } diff --git a/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/CodeFlowAuthorizationTest.java b/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/CodeFlowAuthorizationTest.java index 3c0f6461154c6b..48173f20b14dd2 100644 --- a/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/CodeFlowAuthorizationTest.java +++ b/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/CodeFlowAuthorizationTest.java @@ -42,6 +42,7 @@ import org.htmlunit.FailingHttpStatusCodeException; import org.htmlunit.SilentCssErrorHandler; import org.htmlunit.TextPage; +import org.htmlunit.UnexpectedPage; import org.htmlunit.WebClient; import org.htmlunit.WebRequest; import org.htmlunit.WebResponse; @@ -515,6 +516,19 @@ public void testCodeFlowTokenIntrospection() throws Exception { clearCache(); } + @Test + public void testSlackKnownProvider() throws IOException { + try (var ignored = new SlackWiremockTestResource(); var webClient = createWebClient()) { + webClient.getOptions().setRedirectEnabled(true); + UnexpectedPage page = webClient.getPage("http://localhost:8081/slack"); + var responseContent = page.getWebResponse().getContentAsString(); + assertTrue(responseContent.contains("\"userPrincipalName\":\"vavra\"")); + assertTrue(responseContent.contains("\"userInfoEmail\":\"example@example.com\"")); + webClient.getCookieManager().clearCookies(); + } + clearCache(); + } + private void doTestCodeFlowUserInfo(String tenantId, long internalIdTokenLifetime, boolean cacheUserInfoInIdToken, boolean tenantConfigResolver, int inMemoryCacheSize, int userInfoRequests) throws Exception { try (final WebClient webClient = createWebClient()) { diff --git a/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/SlackWiremockTestResource.java b/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/SlackWiremockTestResource.java new file mode 100644 index 00000000000000..08039d30ace421 --- /dev/null +++ b/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/SlackWiremockTestResource.java @@ -0,0 +1,150 @@ +package io.quarkus.it.keycloak; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.containing; +import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.client.WireMock.urlMatching; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; + +import java.io.Closeable; + +import org.jboss.logging.Logger; + +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.matching.AnythingPattern; + +public class SlackWiremockTestResource implements Closeable { + + private static final Logger LOG = Logger.getLogger(SlackWiremockTestResource.class); + private static final int PORT = 8188; + private final WireMockServer server; + + SlackWiremockTestResource() { + var config = wireMockConfig().port(PORT).globalTemplating(true); + this.server = new WireMockServer(config); + LOG.info("Starting Slack mock on port " + PORT); + this.server.start(); + configureStubs(); + } + + private void configureStubs() { + server.stubFor( + get(urlMatching("/.well-known/openid-configuration.*")) + .willReturn(aResponse() + .withHeader("Content-Type", "application/json") + .withBody( + """ + { + "issuer": "https://slack.com", + "authorization_endpoint": "http://localhost:8188/openid/connect/authorize", + "token_endpoint": "http://localhost:8188/api/openid.connect.token", + "userinfo_endpoint": "http://localhost:8188/api/openid.connect.userInfo", + "jwks_uri": "http://localhost:8188/openid/connect/keys", + "scopes_supported": ["openid","profile","email"], + "response_types_supported": ["code"], + "response_modes_supported": ["form_post"], + "grant_types_supported": ["authorization_code"], + "subject_types_supported": ["public"], + "id_token_signing_alg_values_supported": ["RS256"], + "claims_supported": ["sub","auth_time","iss"], + "claims_parameter_supported": false, + "request_parameter_supported": false, + "request_uri_parameter_supported": true, + "token_endpoint_auth_methods_supported": ["client_secret_post","client_secret_basic"] + } + """))); + + server.stubFor( + get(urlMatching("/openid/connect/authorize.*")) + .withQueryParam("response_type", equalTo("code")) + .withQueryParam("client_id", equalTo("7925551513107.7922794171477")) + .withQueryParam("scope", containing("openid")) + .withQueryParam("scope", containing("email")) + .withQueryParam("scope", containing("profile")) + .withQueryParam("scope", containing("profile")) + .withQueryParam("redirect_uri", equalTo("http://localhost:8081/slack")) + .withQueryParam("state", new AnythingPattern()) + .withQueryParam("team", equalTo("quarkus-oidc-slack-demo")) + .willReturn(aResponse() + .withStatus(302) + .withHeader("Set-Cookie", "{{request.headers.Set-Cookie}}") + .withHeader("Content-Type", "text/html") + .withHeader("Location", "http://localhost:8081/slack?code=7917304849541.79239831" + + "24323.1f4c41812b286422cbce183a9f083fa58f7c2761c281c2be483a376694f56274&state" + + "={{request.query.state}}") + .withBody(""))); + + server.stubFor( + get(urlMatching("/openid/connect/keys.*")) + .willReturn(aResponse() + .withHeader("Set-Cookie", "{{request.headers.Set-Cookie}}") + .withHeader("Content-Type", "application/json") + .withBody( + """ + { + "keys": [ + { + "e": "AQAB", + "n": "zQqzXfb677bpMKw0idKC5WkVLyqk04PWMsWYJDKqMUUuu_PmzdsvXBfHU7tcZiNoHDuVvGDqjqnkLPEzjXnaZY0DDDHvJKS0JI8fkxIfV1kNy3DkpQMMhgAwnftUiSXgb5clypOmotAEm59gHPYjK9JHBWoHS14NYEYZv9NVy0EkjauyYDSTz589aiKU5lA-cePG93JnqLw8A82kfTlrJ1IIJo2isyBGANr0YzR-d3b_5EvP7ivU7Ph2v5JcEUHeiLSRzIzP3PuyVFrPH659Deh-UAsDFOyJbIcimg9ITnk5_45sb_Xcd_UN6h5I7TGOAFaJN4oi4aaGD4elNi_K1Q", + "kty": "RSA", + "kid": "mB2MAyKSn555isd0EbdhKx6nkyAi9xLq8rvCEb_nOyY", + "alg": "RS256" + } + ] + } + """))); + + server.stubFor( + post(urlMatching("/api/openid\\.connect\\.token.*")) + .willReturn(aResponse() + .withHeader("Set-Cookie", "{{request.headers.Set-Cookie}}") + .withHeader("Content-Type", "application/json") + .withBody( + """ + { + "ok": true, + "access_token": "xoxp-7925551513107-7925645662178-7911177365927-reduced", + "token_type": "Bearer", + "id_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Im1CMk1BeUtTbjU1NWlzZDBFYmRoS3g2bmt5QWk5eExxOHJ2Q0ViX25PeVkifQ.eyJpc3MiOiJodHRwczpcL1wvc2xhY2suY29tIiwic3ViIjoiVTA3VDdKWktHNTgiLCJhdWQiOiI3OTI1NTUxNTEzMTA3Ljc5MjI3OTQxNzE0NzciLCJleHAiOjE3Mjk3MTEwNjEsImlhdCI6MTcyOTcxMDc2MSwiYXV0aF90aW1lIjoxNzI5NzEwNzYxLCJub25jZSI6IiIsImF0X2hhc2giOiJRZTRtQkhIUFl2ZUVzd0NFSUZ1Q3VnIiwiaHR0cHM6XC9cL3NsYWNrLmNvbVwvdGVhbV9pZCI6IlQwN1Q3RzdGMzM1IiwiaHR0cHM6XC9cL3NsYWNrLmNvbVwvdXNlcl9pZCI6IlUwN1Q3SlpLRzU4IiwiZW1haWwiOiJ2YXZyYS56YWxvaGFAZ21haWwuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImRhdGVfZW1haWxfdmVyaWZpZWQiOjE3Mjk3MDg5MzAsImxvY2FsZSI6ImVuLVVTIiwibmFtZSI6InZhdnJhIiwicGljdHVyZSI6Imh0dHBzOlwvXC9zZWN1cmUuZ3JhdmF0YXIuY29tXC9hdmF0YXJcL2E4NjE0NWY1M2E4MjcyZDU5NmVkYjgyMDkyOGM2Y2E1LmpwZz9zPTUxMiZkPWh0dHBzJTNBJTJGJTJGYS5zbGFjay1lZGdlLmNvbSUyRmRmMTBkJTJGaW1nJTJGYXZhdGFycyUyRmF2YV8wMDAzLTUxMi5wbmciLCJnaXZlbl9uYW1lIjoidmF2cmEiLCJmYW1pbHlfbmFtZSI6IiIsImh0dHBzOlwvXC9zbGFjay5jb21cL3RlYW1fbmFtZSI6InF1YXJrdXMtb2lkYy1zbGFjay1kZW1vLXdvcmtzcGFjZSIsImh0dHBzOlwvXC9zbGFjay5jb21cL3RlYW1fZG9tYWluIjoicXVhcmt1c29pZGNzbC1pcGE0OTc4IiwiaHR0cHM6XC9cL3NsYWNrLmNvbVwvdGVhbV9pbWFnZV8yMzAiOiJodHRwczpcL1wvYS5zbGFjay1lZGdlLmNvbVwvODA1ODhcL2ltZ1wvYXZhdGFycy10ZWFtc1wvYXZhXzAwMjYtMjMwLnBuZyIsImh0dHBzOlwvXC9zbGFjay5jb21cL3RlYW1faW1hZ2VfZGVmYXVsdCI6dHJ1ZX0.APy1FtKGxzgk65RhxB1lLO9cUt6MZgQVOTicm6o8sVUUy15W7oor2nBJcnFvhYs0W7i4GQFEBFYEQji8iQWYf14Vq5xuKFAcVi5cHqPxMGNiDLy0cLkEtUmkHImQhAl2aV6W-FZAJosJ3BdYd_Xs3GPwvl01763izwTKe2sWSyyN-eyXHgg48OxLn8pex4l4nvBzRqp3iB_UvW7iujgSppjRteE0UiUnL6hiD269v_O-KCul_HAdaUn5iKUoKsnbjeSE9GE_vXzFFSUl0Nu0N78NS2ENvpodfpSK4fQo4Buh3E2VlehFe_te-bNw1aYIPusDLefyw2lCxy4kqtmgzw", + "state": "{{request.query.state}}" + } + """))); + + server.stubFor( + get(urlMatching("/api/openid\\.connect\\.userInfo.*")) + .willReturn(aResponse() + .withHeader("Set-Cookie", "{{request.headers.Set-Cookie}}") + .withHeader("Content-Type", "application/json") + .withBody( + """ + { + "ok": true, + "sub": "U07TA484GLU", + "https:\\/\\/slack.com\\/user_id": "U0reducedGLU", + "https:\\/\\/slack.com\\/team_id": "T0reduced35", + "email": "example@example.com", + "email_verified": true, + "date_email_verified": 1729712670, + "name": "Michal No", + "picture": "https:\\/\\/avatars.slack-edge.com\\/2024-10-23\\/7948436985680_reduced.png", + "given_name": "Michal", + "family_name": "No", + "locale": "en-US", + "https:\\/\\/slack.com\\/team_name": "quarkus-oidc-slack-demo-workspace", + "https:\\/\\/slack.com\\/team_domain": "reduced-ipa4978", + "https:\\/\\/slack.com\\/user_image_24": "https:\\/\\/avatars.slack-edge.com\\/2024-10-23\\/7948436985680_reduced_a64ea0fba9db9b46c773_24.png", + "https:\\/\\/slack.com\\/team_image_34": "https:\\/\\/a.slack-edge.com\\/80588\\/img\\/avatars-teams\\/ava_7948436985680_reduced.png", + "https:\\/\\/slack.com\\/team_image_default": true + } + """))); + } + + @Override + public void close() { + server.stop(); + LOG.info("Slack mock was shut down"); + } + +}