necouchman commented on code in PR #1198: URL: https://github.com/apache/guacamole-client/pull/1198#discussion_r3061614236
########## extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/token/TokenValidationService.java: ########## @@ -20,17 +20,24 @@ package org.apache.guacamole.auth.openid.token; import com.google.inject.Inject; +import java.net.URLEncoder; +import java.net.URI; +import java.nio.charset.StandardCharsets; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; + Review Comment: Please remove this additional blank line. ########## extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/util/JsonUrlReader.java: ########## @@ -0,0 +1,98 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.guacamole.auth.openid.util; + +import java.io.BufferedReader; +import java.io.OutputStream; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import org.apache.guacamole.GuacamoleException; +import org.jose4j.json.JsonUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/* + * Utility class to open a http connection to a URL, send a body + * and receive a response in the form of a parsed JSON + */ +public final class JsonUrlReader { Review Comment: The Apache Commons IO project has some classes that might already do this. Maybe it would be better to use one of those than to re-invent it? https://www.baeldung.com/java-read-json-from-url ########## extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/conf/OpenIDWellKnown.java: ########## @@ -0,0 +1,214 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.guacamole.auth.openid.conf; + +import com.google.inject.Inject; +import com.google.inject.Singleton; +import java.io.IOException; +import java.net.URI; +import java.net.URL; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.Map; +import javax.ws.rs.core.UriBuilder; +import org.apache.guacamole.auth.openid.util.JsonUrlReader; +import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.environment.Environment; +import org.apache.guacamole.properties.URIGuacamoleProperty; +import org.jose4j.json.JsonUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Class for retrieving well-known endpoint data. + */ +@Singleton +public class OpenIDWellKnown { + + /** + * Logger for this class. + */ + private final Logger logger = LoggerFactory.getLogger(OpenIDWellKnown.class); + + + /** + * The number of attempts to the well-known endpoint to get the values before giving up + */ + private static final int MAX_ATTEMPTS = 24; + + /** + * The delay between each attempt to well-known endpoint in seconds + */ + private static final long DELAY_SECONDS = 5; + + /** + * The detected issuer + */ + private static String issuer = null; + + /** + * The detected authorization edpoint + */ + private static URI authorization_endpoint = null; + + /** + * The detected token edpoint + */ + private static URI token_endpoint = null; + + /** + * The detected jwks_uri + */ + private static URI jwks_uri = null; + + /** + * The well-known endpoint (URI) of the OIDC service. + */ + private static final URIGuacamoleProperty OPENID_WELL_KNOWN_ENDPOINT = + new URIGuacamoleProperty() { + @Override + public String getName() { + return "openid-well-known-endpoint"; + } + }; + + /** + * Returns the well-known endpoint (URI) of the OIDC service as + * configured with guacamole.properties. + * + * @return + * The well-known endpoint of the OIDC service, as configured with + * guacamole.properties. + * + * @throws GuacamoleException + * If guacamole.properties cannot be parsed, or if the authorization + * endpoint property is missing. + */ + public URI getWellKnownEndpoint() throws GuacamoleException { + return environment.getProperty(OPENID_WELL_KNOWN_ENDPOINT); + } + + /** + * Returns the issuer to expect for all received ID tokens, as configured + * from the well_known endpoint. + * + * @return + * The issuer to expect for all received ID tokens, as returned by the + * well-known endpoint. + */ + public String getIssuer() { + return issuer; + } + + /** + * Returns the authorization endpoint (URI) of the OpenID service as + * configured from the well_known endpoint. + * + * @return + * The authorization endpoint of the OpenID service, as returned by the + * well-known endpoint. + */ + public URI getAuthorizationEndpoint() { + return authorization_endpoint; + } + + /** + * Returns the token endpoint (URI) of the OpenID service as + * configured from the well_known endpoint. + * + * @return + * The token endpoint of the OpenID service, as returned by the + * well-known endpoint. + */ + public URI getTokenEndpoint() { + return token_endpoint; + } + + /** + * Returns the endpoint (URI) of the JWKS service which defines how + * received ID tokens (JWTs) shall be validated, as configured from + * the well-known endpoint. + * + * @return + * The endpoint (URI) of the JWKS service which defines how received ID + * tokens (JWTs) shall be validated, as configured from the + * well-known endpoint. + */ + public URI getJWKSEndpoint() { + return jwks_uri; + } + + /** + * The Guacamole server environment. + */ + @Inject + private Environment environment; + + /* + * Creates an OpenIDWellKnown class that reads the json from an OIDC + * well-known endpoint and saves these values for later use. Use Guice + * to ensure environment exists before initializing. + */ + public OpenIDWellKnown() { + } Review Comment: Having a variable and a constructor this far down, while not explicitly violating any style guideline, is a bit unconventional and not how we tend to do things in the existing code base. The order tends to be (more or less): variables, constructors, and then methods. ########## extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/OpenIDAuthenticationProviderModule.java: ########## @@ -42,7 +45,7 @@ protected void configure() { bind(ConfigurationService.class); bind(NonceService.class).in(Scopes.SINGLETON); bind(TokenValidationService.class); - + bind(OpenIDAuthenticationSessionManager.class); Review Comment: The blank line can stay, and this new line should be above it - it separates the OpenID-specific binds from the generic one. ########## extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/token/TokenValidationService.java: ########## @@ -131,6 +138,119 @@ public JwtClaims validateToken(String token) throws GuacamoleException { return null; } + /** + * Validates the given ID token, using code flow, returning the JwtClaims + * contained therein. If the ID token is invalid, null is returned. + * + * @param code + * The code to validate and receive the id_token. + * + * @param verifier + * A PKCE verifier or null if not used. + * + * @return + * The JWT claims contained within the given ID token if it passes tests, + * or null if the token is not valid. + * + * @throws GuacamoleException + * If guacamole.properties could not be parsed. + */ + public JwtClaims validateCode(String code, String verifier) throws GuacamoleException { + // Validating the token requires a JWKS key resolver + HttpsJwks jwks = new HttpsJwks(confService.getJWKSEndpoint().toString()); + HttpsJwksVerificationKeyResolver resolver = new HttpsJwksVerificationKeyResolver(jwks); + + /* Exchange code → token */ + String token = exchangeCode(code, verifier); + + // Create JWT consumer for validating received token + JwtConsumer jwtConsumer = new JwtConsumerBuilder() + .setRequireExpirationTime() + .setMaxFutureValidityInMinutes(confService.getMaxTokenValidity()) + .setAllowedClockSkewInSeconds(confService.getAllowedClockSkew()) + .setRequireSubject() + .setExpectedIssuer(confService.getIssuer()) + .setExpectedAudience(confService.getClientID()) + .setVerificationKeyResolver(resolver) + .build(); + + try { + // Validate JWT + return jwtConsumer.processToClaims(token); + } + // Log any failures to validate/parse the JWT + catch (InvalidJwtException e) { + logger.info("Rejected invalid OpenID token: {}", e.getMessage(), e); + } + + return null; + } Review Comment: It seems like this new method does almost exactly the same thing as the existing `validateToken()` method, save the call to the `exchangeCode()` method. Surely we can avoid the duplication, here. ########## extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/util/JsonUrlReader.java: ########## @@ -0,0 +1,98 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.guacamole.auth.openid.util; + +import java.io.BufferedReader; +import java.io.OutputStream; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import org.apache.guacamole.GuacamoleException; +import org.jose4j.json.JsonUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/* + * Utility class to open a http connection to a URL, send a body + * and receive a response in the form of a parsed JSON + */ +public final class JsonUrlReader { + /** + * Logger for this class. + */ + private static final Logger logger = LoggerFactory.getLogger(JsonUrlReader.class); + + private JsonUrlReader() {} Review Comment: Please document this constructor. ########## extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/token/TokenValidationService.java: ########## @@ -131,6 +138,119 @@ public JwtClaims validateToken(String token) throws GuacamoleException { return null; } + /** + * Validates the given ID token, using code flow, returning the JwtClaims + * contained therein. If the ID token is invalid, null is returned. + * + * @param code + * The code to validate and receive the id_token. + * + * @param verifier + * A PKCE verifier or null if not used. + * + * @return + * The JWT claims contained within the given ID token if it passes tests, + * or null if the token is not valid. + * + * @throws GuacamoleException + * If guacamole.properties could not be parsed. + */ + public JwtClaims validateCode(String code, String verifier) throws GuacamoleException { + // Validating the token requires a JWKS key resolver + HttpsJwks jwks = new HttpsJwks(confService.getJWKSEndpoint().toString()); + HttpsJwksVerificationKeyResolver resolver = new HttpsJwksVerificationKeyResolver(jwks); + + /* Exchange code → token */ + String token = exchangeCode(code, verifier); + + // Create JWT consumer for validating received token + JwtConsumer jwtConsumer = new JwtConsumerBuilder() + .setRequireExpirationTime() + .setMaxFutureValidityInMinutes(confService.getMaxTokenValidity()) + .setAllowedClockSkewInSeconds(confService.getAllowedClockSkew()) + .setRequireSubject() + .setExpectedIssuer(confService.getIssuer()) + .setExpectedAudience(confService.getClientID()) + .setVerificationKeyResolver(resolver) + .build(); + + try { + // Validate JWT + return jwtConsumer.processToClaims(token); + } + // Log any failures to validate/parse the JWT + catch (InvalidJwtException e) { + logger.info("Rejected invalid OpenID token: {}", e.getMessage(), e); + } + + return null; + } + + /** + * URLEncodes a key/value pair + * + * @param key + * The key to encode + * + * @param value + * The value to encode + * + * @return + * The urlencoded kay/value pair + */ + private String urlencode(String key, String value) { + StringBuilder builder = new StringBuilder(); + return builder.append(URLEncoder.encode(key, StandardCharsets.UTF_8)) + .append("=") + .append(URLEncoder.encode(value, StandardCharsets.UTF_8)) + .toString(); + } + + /** + * Exchanges the authorization code for tokens. + * + * @param code + * The authorization code received from the IdP. + * @param codeVerifier Review Comment: Please add a blank line in between these `@param` blocks. ########## extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/conf/OpenIDWellKnown.java: ########## @@ -0,0 +1,214 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.guacamole.auth.openid.conf; + +import com.google.inject.Inject; +import com.google.inject.Singleton; +import java.io.IOException; +import java.net.URI; +import java.net.URL; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.Map; +import javax.ws.rs.core.UriBuilder; +import org.apache.guacamole.auth.openid.util.JsonUrlReader; +import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.environment.Environment; +import org.apache.guacamole.properties.URIGuacamoleProperty; +import org.jose4j.json.JsonUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Class for retrieving well-known endpoint data. + */ +@Singleton +public class OpenIDWellKnown { + + /** + * Logger for this class. + */ + private final Logger logger = LoggerFactory.getLogger(OpenIDWellKnown.class); + + + /** + * The number of attempts to the well-known endpoint to get the values before giving up + */ + private static final int MAX_ATTEMPTS = 24; + + /** + * The delay between each attempt to well-known endpoint in seconds + */ + private static final long DELAY_SECONDS = 5; + + /** + * The detected issuer + */ + private static String issuer = null; + + /** + * The detected authorization edpoint + */ + private static URI authorization_endpoint = null; + + /** + * The detected token edpoint + */ + private static URI token_endpoint = null; + + /** + * The detected jwks_uri + */ + private static URI jwks_uri = null; + + /** + * The well-known endpoint (URI) of the OIDC service. + */ + private static final URIGuacamoleProperty OPENID_WELL_KNOWN_ENDPOINT = + new URIGuacamoleProperty() { + @Override + public String getName() { + return "openid-well-known-endpoint"; + } + }; + + /** + * Returns the well-known endpoint (URI) of the OIDC service as + * configured with guacamole.properties. + * + * @return + * The well-known endpoint of the OIDC service, as configured with + * guacamole.properties. + * + * @throws GuacamoleException + * If guacamole.properties cannot be parsed, or if the authorization + * endpoint property is missing. + */ + public URI getWellKnownEndpoint() throws GuacamoleException { + return environment.getProperty(OPENID_WELL_KNOWN_ENDPOINT); + } Review Comment: Aside from being called inside this class in the `init()` method, the only other call that I see is in the `ConfigurationService`, which, as I mention in the comment above, may not be necessary. I think that call can be removed and this can be changed to `private`. ########## extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/conf/ConfigurationService.java: ########## @@ -334,7 +419,130 @@ public String getIssuer() throws GuacamoleException { * property is missing. */ public URI getJWKSEndpoint() throws GuacamoleException { - return environment.getRequiredProperty(OPENID_JWKS_ENDPOINT); + URI jwks_uri = environment.getProperty(OPENID_JWKS_ENDPOINT); + jwks_uri = jwks_uri == null ? confWellKnown.getJWKSEndpoint() : jwks_uri; + if (jwks_uri == null) { + throw new GuacamoleException("Property openid-jwks-endpoint or openid-well-known-endpoint is required"); + } + return jwks_uri; + } + + /** + * Returns the token endpoint (URI) of the OIDC service as + * configured with guacamole.properties. + * + * @return + * The token endpoint of the OIDC service, as configured with + * guacamole.properties. + * + * @throws GuacamoleException + * If guacamole.properties cannot be parsed, or if the token + * endpoint property is missing. + */ + public URI getTokenEndpoint() throws GuacamoleException { + URI token_endpoint = environment.getProperty(OPENID_TOKEN_ENDPOINT); + token_endpoint = token_endpoint == null ? confWellKnown.getTokenEndpoint() : token_endpoint; + if (token_endpoint == null) { + throw new GuacamoleException("Property openid-token-endpoint or openid-well-known-endpoint is required"); + } + return token_endpoint; + } + + /** + * Returns the well-known endpoint (URI) of the OIDC service as + * configured with guacamole.properties. + * + * @return + * The well-known endpoint of the OIDC service, as configured with + * guacamole.properties. + * + * @throws GuacamoleException + * If guacamole.properties cannot be parsed, or if the well-known + * endpoint property is missing. + */ + public URI getWellKnownEndpoint() throws GuacamoleException { + return confWellKnown.getWellKnownEndpoint(); + } Review Comment: Is this method really necessary? I don't see this being actually called anywhere in the code? It seems like the well-known endpoint is only ever used inside the `OpenIDWellKnown` class. ########## extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/conf/OpenIDWellKnown.java: ########## @@ -0,0 +1,214 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.guacamole.auth.openid.conf; + +import com.google.inject.Inject; +import com.google.inject.Singleton; +import java.io.IOException; +import java.net.URI; +import java.net.URL; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.Map; +import javax.ws.rs.core.UriBuilder; +import org.apache.guacamole.auth.openid.util.JsonUrlReader; +import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.environment.Environment; +import org.apache.guacamole.properties.URIGuacamoleProperty; +import org.jose4j.json.JsonUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Class for retrieving well-known endpoint data. + */ +@Singleton +public class OpenIDWellKnown { + + /** + * Logger for this class. + */ + private final Logger logger = LoggerFactory.getLogger(OpenIDWellKnown.class); + + + /** + * The number of attempts to the well-known endpoint to get the values before giving up + */ + private static final int MAX_ATTEMPTS = 24; + + /** + * The delay between each attempt to well-known endpoint in seconds + */ + private static final long DELAY_SECONDS = 5; + + /** + * The detected issuer + */ + private static String issuer = null; + + /** + * The detected authorization edpoint + */ + private static URI authorization_endpoint = null; + + /** + * The detected token edpoint + */ + private static URI token_endpoint = null; + + /** + * The detected jwks_uri + */ + private static URI jwks_uri = null; + + /** + * The well-known endpoint (URI) of the OIDC service. + */ + private static final URIGuacamoleProperty OPENID_WELL_KNOWN_ENDPOINT = + new URIGuacamoleProperty() { + @Override + public String getName() { + return "openid-well-known-endpoint"; + } + }; + + /** + * Returns the well-known endpoint (URI) of the OIDC service as + * configured with guacamole.properties. + * + * @return + * The well-known endpoint of the OIDC service, as configured with + * guacamole.properties. + * + * @throws GuacamoleException + * If guacamole.properties cannot be parsed, or if the authorization + * endpoint property is missing. + */ + public URI getWellKnownEndpoint() throws GuacamoleException { + return environment.getProperty(OPENID_WELL_KNOWN_ENDPOINT); + } + + /** + * Returns the issuer to expect for all received ID tokens, as configured + * from the well_known endpoint. + * + * @return + * The issuer to expect for all received ID tokens, as returned by the + * well-known endpoint. + */ + public String getIssuer() { + return issuer; + } + + /** + * Returns the authorization endpoint (URI) of the OpenID service as + * configured from the well_known endpoint. + * + * @return + * The authorization endpoint of the OpenID service, as returned by the + * well-known endpoint. + */ + public URI getAuthorizationEndpoint() { + return authorization_endpoint; + } + + /** + * Returns the token endpoint (URI) of the OpenID service as + * configured from the well_known endpoint. + * + * @return + * The token endpoint of the OpenID service, as returned by the + * well-known endpoint. + */ + public URI getTokenEndpoint() { + return token_endpoint; + } + + /** + * Returns the endpoint (URI) of the JWKS service which defines how + * received ID tokens (JWTs) shall be validated, as configured from + * the well-known endpoint. + * + * @return + * The endpoint (URI) of the JWKS service which defines how received ID + * tokens (JWTs) shall be validated, as configured from the + * well-known endpoint. + */ + public URI getJWKSEndpoint() { + return jwks_uri; + } + + /** + * The Guacamole server environment. + */ + @Inject + private Environment environment; + + /* + * Creates an OpenIDWellKnown class that reads the json from an OIDC + * well-known endpoint and saves these values for later use. Use Guice + * to ensure environment exists before initializing. + */ + public OpenIDWellKnown() { + } + + @Inject + public void init() { + // Call to well-known endpoint might fail, so allow several tries before giving up + ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); + + Runnable task = new Runnable() { + int attempts = 0; + + @Override + public void run() { + attempts++; + + try { + Map<String,Object> json = JsonUrlReader.fetch("GET", getWellKnownEndpoint().toURL(), ""); Review Comment: What happens, here, if the administrator has not provided a well-known endpoint? The call, above, to `getProperty()` in the `getWellKnownEndpoint()` implementation, will return `null` if the property has not been provided. My guess is that this should be checked before kicking off this scheduler? Am I missing where that check happens? ########## extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/conf/ConfigurationService.java: ########## @@ -20,12 +20,17 @@ package org.apache.guacamole.auth.openid.conf; import com.google.inject.Inject; + Review Comment: We generally don't break between `import` blocks, you can remove this line. ########## extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/conf/ConfigurationService.java: ########## @@ -110,6 +120,66 @@ public class ConfigurationService { }; + /** + * The token endpoint (URI) of the OIDC service. + */ + private static final URIGuacamoleProperty OPENID_TOKEN_ENDPOINT = + new URIGuacamoleProperty() { + @Override + public String getName() { + return "openid-token-endpoint"; + } + }; + + /** + * The reponse type of the OpenID service. + */ + private static final EnumGuacamoleProperty<OpenIDResponseType> OPENID_RESPONSE_TYPE = + new EnumGuacamoleProperty<OpenIDResponseType>(OpenIDResponseType.class) { + + @Override + public String getName() { return "openid-response-type"; } + + }; + + /** + * OIDC client secret which should be submitted to the OIDC service when + * validating tokens with code flow + */ + private static final StringGuacamoleProperty OPENID_CLIENT_SECRET = + new StringGuacamoleProperty() { + @Override + public String getName() { + return "openid-client-secret"; + } + }; + + /** + * True if "Proof Key for Code Exchange" (PKCE) must be used. + */ + private static final BooleanGuacamoleProperty OPENID_PKCE_REQUIRED = + new BooleanGuacamoleProperty() { + @Override + public String getName() { + return "openid-pkce-required"; + } + }; + + /** + * The maximum amount of time to allow for an in-progress OpenID + * authentication attempt to be completed, in minutes. A user that takes + * longer than this amount of time to complete authentication with their + * identity provider will be redirected back to the identity provider to + * try again. + */ + private static final IntegerGuacamoleProperty OPENID_AUTH_TIMEOUT = + new IntegerGuacamoleProperty() { + @Override + public String getName() { return "openid-auth-timeout"; } + + }; + + Review Comment: This block of code needs to be consistent in its style, both internal (within this block) and also with established style throughout the code: * There are varying occurrences of blank lines before the `@Override` and after the functions. * Sometimes you put the braces and `return` statement on a single line, sometimes you used multiple lines. ########## extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/conf/OpenIDWellKnown.java: ########## @@ -0,0 +1,214 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.guacamole.auth.openid.conf; + +import com.google.inject.Inject; +import com.google.inject.Singleton; +import java.io.IOException; +import java.net.URI; +import java.net.URL; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.Map; +import javax.ws.rs.core.UriBuilder; +import org.apache.guacamole.auth.openid.util.JsonUrlReader; +import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.environment.Environment; +import org.apache.guacamole.properties.URIGuacamoleProperty; +import org.jose4j.json.JsonUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Class for retrieving well-known endpoint data. + */ +@Singleton +public class OpenIDWellKnown { + + /** + * Logger for this class. + */ + private final Logger logger = LoggerFactory.getLogger(OpenIDWellKnown.class); Review Comment: This should be static. ########## extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/util/PKCEUtil.java: ########## @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.guacamole.auth.openid.util; + +import java.security.MessageDigest; +import java.security.SecureRandom; + +/** + * Utility class for generating PKCE parameters. + * + * Supports: + * - code_verifier (random Base64URL) + * - code_challenge (S256) + */ +public final class PKCEUtil { + + private static final SecureRandom RANDOM = new SecureRandom(); + + private PKCEUtil() {} + + /** + * Generates a high-entropy PKCE code_verifier. + */ Review Comment: Please complete the documentation for this method (at the very least needs an `@return` block). ########## extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/util/PKCEUtil.java: ########## @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.guacamole.auth.openid.util; + +import java.security.MessageDigest; +import java.security.SecureRandom; + +/** + * Utility class for generating PKCE parameters. + * + * Supports: + * - code_verifier (random Base64URL) + * - code_challenge (S256) + */ +public final class PKCEUtil { + + private static final SecureRandom RANDOM = new SecureRandom(); + + private PKCEUtil() {} + + /** + * Generates a high-entropy PKCE code_verifier. + */ + public static String generateCodeVerifier() { + byte[] bytes = new byte[64]; + RANDOM.nextBytes(bytes); + return base64Url(bytes); + } + + /** + * Computes the PKCE code_challenge = BASE64URL(SHA256(code_verifier)). + */ + public static String generateCodeChallenge(String verifier) throws Exception { + MessageDigest sha = MessageDigest.getInstance("SHA-256"); + byte[] hash = sha.digest(verifier.getBytes("US-ASCII")); + return base64Url(hash); + } + + /** + * Base64URL encoding without padding. + */ Review Comment: Please complete documentation for this method. ########## extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/util/JsonUrlReader.java: ########## @@ -0,0 +1,98 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.guacamole.auth.openid.util; + +import java.io.BufferedReader; +import java.io.OutputStream; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import org.apache.guacamole.GuacamoleException; +import org.jose4j.json.JsonUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/* + * Utility class to open a http connection to a URL, send a body + * and receive a response in the form of a parsed JSON + */ +public final class JsonUrlReader { + /** + * Logger for this class. + */ + private static final Logger logger = LoggerFactory.getLogger(JsonUrlReader.class); + + private JsonUrlReader() {} + + public static Map<String,Object> fetch(String method, URL url, String body) throws GuacamoleException { + if (url == null) { + throw new GuacamoleException("JsonUrlReader : Missing URL"); + } + + try { + // Open connection, using HttpURLConnection + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + + conn.setRequestMethod(method); + conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8"); + + if (! method.equalsIgnoreCase("GET")) { Review Comment: Do we really expect the case of the HTTP method to vary? I see it fairly consistently as "GET" (in capital letters) - are there situations where we think this won't be the case? ########## extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/util/JsonUrlReader.java: ########## @@ -0,0 +1,98 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.guacamole.auth.openid.util; + +import java.io.BufferedReader; +import java.io.OutputStream; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import org.apache.guacamole.GuacamoleException; +import org.jose4j.json.JsonUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/* + * Utility class to open a http connection to a URL, send a body + * and receive a response in the form of a parsed JSON + */ +public final class JsonUrlReader { + /** + * Logger for this class. + */ + private static final Logger logger = LoggerFactory.getLogger(JsonUrlReader.class); + + private JsonUrlReader() {} + + public static Map<String,Object> fetch(String method, URL url, String body) throws GuacamoleException { + if (url == null) { Review Comment: What if the URL is an empty string? ########## extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/util/JsonUrlReader.java: ########## @@ -0,0 +1,98 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.guacamole.auth.openid.util; + +import java.io.BufferedReader; +import java.io.OutputStream; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import org.apache.guacamole.GuacamoleException; +import org.jose4j.json.JsonUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/* + * Utility class to open a http connection to a URL, send a body + * and receive a response in the form of a parsed JSON + */ +public final class JsonUrlReader { + /** + * Logger for this class. + */ + private static final Logger logger = LoggerFactory.getLogger(JsonUrlReader.class); + + private JsonUrlReader() {} + + public static Map<String,Object> fetch(String method, URL url, String body) throws GuacamoleException { Review Comment: Please document this method. Also, if this is intended to be a more generic utility class, then maybe it should throw something other than a `GuacamoleException`? ########## extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/util/PKCEUtil.java: ########## @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.guacamole.auth.openid.util; + +import java.security.MessageDigest; +import java.security.SecureRandom; + +/** + * Utility class for generating PKCE parameters. + * + * Supports: + * - code_verifier (random Base64URL) + * - code_challenge (S256) + */ +public final class PKCEUtil { + + private static final SecureRandom RANDOM = new SecureRandom(); Review Comment: Please document this. ########## extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/OpenIDAuthenticationSession.java: ########## @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.guacamole.auth.openid; + +import org.apache.guacamole.net.auth.AuthenticationSession; + +/** + * Representation of an in-progress OpenID authentication attempt. + */ +public class OpenIDAuthenticationSession extends AuthenticationSession { + /** Review Comment: Should be a blank line above this one. ########## extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/util/JsonUrlReader.java: ########## @@ -0,0 +1,98 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.guacamole.auth.openid.util; + +import java.io.BufferedReader; +import java.io.OutputStream; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import org.apache.guacamole.GuacamoleException; +import org.jose4j.json.JsonUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/* + * Utility class to open a http connection to a URL, send a body + * and receive a response in the form of a parsed JSON + */ +public final class JsonUrlReader { + /** + * Logger for this class. + */ + private static final Logger logger = LoggerFactory.getLogger(JsonUrlReader.class); + + private JsonUrlReader() {} + + public static Map<String,Object> fetch(String method, URL url, String body) throws GuacamoleException { + if (url == null) { + throw new GuacamoleException("JsonUrlReader : Missing URL"); Review Comment: If you are going to throw `GuacamoleException` types, it should probably be something more specific than just the generic `GuacamoleException`. ########## extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/token/TokenValidationService.java: ########## @@ -131,6 +138,119 @@ public JwtClaims validateToken(String token) throws GuacamoleException { return null; } + /** + * Validates the given ID token, using code flow, returning the JwtClaims + * contained therein. If the ID token is invalid, null is returned. + * + * @param code + * The code to validate and receive the id_token. + * + * @param verifier + * A PKCE verifier or null if not used. + * + * @return + * The JWT claims contained within the given ID token if it passes tests, + * or null if the token is not valid. + * + * @throws GuacamoleException + * If guacamole.properties could not be parsed. + */ + public JwtClaims validateCode(String code, String verifier) throws GuacamoleException { + // Validating the token requires a JWKS key resolver + HttpsJwks jwks = new HttpsJwks(confService.getJWKSEndpoint().toString()); + HttpsJwksVerificationKeyResolver resolver = new HttpsJwksVerificationKeyResolver(jwks); + + /* Exchange code → token */ + String token = exchangeCode(code, verifier); + + // Create JWT consumer for validating received token + JwtConsumer jwtConsumer = new JwtConsumerBuilder() + .setRequireExpirationTime() + .setMaxFutureValidityInMinutes(confService.getMaxTokenValidity()) + .setAllowedClockSkewInSeconds(confService.getAllowedClockSkew()) + .setRequireSubject() + .setExpectedIssuer(confService.getIssuer()) + .setExpectedAudience(confService.getClientID()) + .setVerificationKeyResolver(resolver) + .build(); + + try { + // Validate JWT + return jwtConsumer.processToClaims(token); + } + // Log any failures to validate/parse the JWT + catch (InvalidJwtException e) { + logger.info("Rejected invalid OpenID token: {}", e.getMessage(), e); + } + + return null; + } + + /** + * URLEncodes a key/value pair + * + * @param key + * The key to encode + * + * @param value + * The value to encode + * + * @return + * The urlencoded kay/value pair + */ + private String urlencode(String key, String value) { + StringBuilder builder = new StringBuilder(); + return builder.append(URLEncoder.encode(key, StandardCharsets.UTF_8)) + .append("=") + .append(URLEncoder.encode(value, StandardCharsets.UTF_8)) + .toString(); + } + + /** + * Exchanges the authorization code for tokens. + * + * @param code + * The authorization code received from the IdP. + * @param codeVerifier + * The PKCE verifier (or null if PKCE is disabled). + * + * @return + * The token string returned. + * + * @throws GuacamoleException + * If a valid token is not returned. + */ + private String exchangeCode(String code, String verifier) throws GuacamoleException { + + try { + StringBuilder bodyBuilder = new StringBuilder(); + bodyBuilder.append(urlencode("grant_type", "authorization_code")).append("&"); + bodyBuilder.append(urlencode("code", code)).append("&"); + bodyBuilder.append(urlencode("redirect_uri", confService.getRedirectURI().toString())).append("&"); + bodyBuilder.append(urlencode("scope", confService.getScope())).append("&"); + bodyBuilder.append(urlencode("client_id", confService.getClientID())); + + String clientSecret = confService.getClientSecret(); + if (clientSecret != null && !clientSecret.trim().isEmpty()) { + bodyBuilder.append("&").append(urlencode("client_secret", clientSecret)); + } + + if (confService.isPKCERequired()) { + bodyBuilder.append("&").append(urlencode("code_verifier", verifier)); + } + + Map<String,Object> json = + JsonUrlReader.fetch("POST", confService.getTokenEndpoint().toURL(), + bodyBuilder.toString()); + + return (String) json.get("id_token"); + + } catch (Exception e) { Review Comment: Please don't cuddle the braces. ########## extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/util/PKCEUtil.java: ########## @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.guacamole.auth.openid.util; + +import java.security.MessageDigest; +import java.security.SecureRandom; + +/** + * Utility class for generating PKCE parameters. + * + * Supports: + * - code_verifier (random Base64URL) + * - code_challenge (S256) + */ +public final class PKCEUtil { + + private static final SecureRandom RANDOM = new SecureRandom(); + + private PKCEUtil() {} Review Comment: Please document this constructor. ########## extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/util/PKCEUtil.java: ########## @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.guacamole.auth.openid.util; + +import java.security.MessageDigest; +import java.security.SecureRandom; + +/** + * Utility class for generating PKCE parameters. + * + * Supports: + * - code_verifier (random Base64URL) + * - code_challenge (S256) + */ +public final class PKCEUtil { + + private static final SecureRandom RANDOM = new SecureRandom(); + + private PKCEUtil() {} + + /** + * Generates a high-entropy PKCE code_verifier. + */ + public static String generateCodeVerifier() { + byte[] bytes = new byte[64]; + RANDOM.nextBytes(bytes); + return base64Url(bytes); + } + + /** + * Computes the PKCE code_challenge = BASE64URL(SHA256(code_verifier)). + */ Review Comment: Please complete documentation for this method. ########## extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/util/JsonUrlReader.java: ########## @@ -0,0 +1,98 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.guacamole.auth.openid.util; + +import java.io.BufferedReader; +import java.io.OutputStream; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import org.apache.guacamole.GuacamoleException; +import org.jose4j.json.JsonUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/* + * Utility class to open a http connection to a URL, send a body + * and receive a response in the form of a parsed JSON + */ +public final class JsonUrlReader { + /** Review Comment: Please add a blank line above this line. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
