- Add openid-well-known-endpoint and treat it to automatically find the :
* issuer, * authorization_endpoint, * token_endpoint, * jwks_uri - Replace openid-flow-type with openid-response-type - Use an Enum for openid-response-type to limit to the values 'id_token', 'token' or 'cod' - Treat the response type as an implicit flow type allowing use of AWS Cognito
This commit is contained in:
1 parent
4dfb4b5c66
commit
08bcd1f9e8
7 files changed
+476
-82
No files matched your search
+14
-19
@@ -63,6 +63,7 @@ public class AuthenticationProviderService implements SSOAuthenticationProviderS
|
|||||||
/**
|
/**
|
||||||
* The standard HTTP parameter which will be included within the URL by all
|
* The standard HTTP parameter which will be included within the URL by all
|
||||||
* OpenID services upon successful implicit flow authentication.
|
* OpenID services upon successful implicit flow authentication.
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
public static final String IMPLICIT_TOKEN_PARAMETER_NAME = "id_token";
|
public static final String IMPLICIT_TOKEN_PARAMETER_NAME = "id_token";
|
||||||
|
|
||||||
@@ -136,13 +137,13 @@ public class AuthenticationProviderService implements SSOAuthenticationProviderS
|
|||||||
Set<String> groups = null;
|
Set<String> groups = null;
|
||||||
Map<String,String> tokens = Collections.emptyMap();
|
Map<String,String> tokens = Collections.emptyMap();
|
||||||
|
|
||||||
logger.debug("OpenID authentication with {} flow (ID: {}, Secret: {}, PKCE: {})",
|
logger.debug("OpenID authentication with '{}' reponse type (ID: {}, Secret: {}, PKCE: {})",
|
||||||
confService.getFlowType(),
|
confService.getResponseType(),
|
||||||
confService.getClientID(),
|
confService.getClientID(),
|
||||||
confService.getClientSecret(),
|
confService.getClientSecret(),
|
||||||
confService.isPKCERequired());
|
confService.isPKCERequired());
|
||||||
|
|
||||||
if (confService.getFlowType().equals("implicit")) {
|
if (confService.isImplicitFlow()) {
|
||||||
String token = credentials.getParameter(IMPLICIT_TOKEN_PARAMETER_NAME);
|
String token = credentials.getParameter(IMPLICIT_TOKEN_PARAMETER_NAME);
|
||||||
if (token != null) {
|
if (token != null) {
|
||||||
JwtClaims claims = tokenService.validateToken(token);
|
JwtClaims claims = tokenService.validateToken(token);
|
||||||
@@ -194,21 +195,15 @@ public class AuthenticationProviderService implements SSOAuthenticationProviderS
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public URI getLoginURI() throws GuacamoleException {
|
public URI getLoginURI() throws GuacamoleException {
|
||||||
if (confService.getFlowType().equals("implicit")) {
|
UriBuilder builder = UriBuilder.fromUri(confService.getAuthorizationEndpoint())
|
||||||
return UriBuilder.fromUri(confService.getAuthorizationEndpoint())
|
.queryParam("scope", confService.getScope())
|
||||||
.queryParam("scope", confService.getScope())
|
.queryParam("response_type", confService.getResponseType().toString())
|
||||||
.queryParam("response_type", IMPLICIT_TOKEN_PARAMETER_NAME)
|
.queryParam("client_id", confService.getClientID())
|
||||||
.queryParam("client_id", confService.getClientID())
|
.queryParam("redirect_uri", confService.getRedirectURI());
|
||||||
.queryParam("redirect_uri", confService.getRedirectURI())
|
|
||||||
.queryParam("nonce", nonceService.generate(confService.getMaxNonceValidity() * 60000L))
|
|
||||||
.build();
|
|
||||||
} else {
|
|
||||||
UriBuilder builder = UriBuilder.fromUri(confService.getAuthorizationEndpoint())
|
|
||||||
.queryParam("scope", confService.getScope())
|
|
||||||
.queryParam("response_type", CODE_TOKEN_PARAMETER_NAME)
|
|
||||||
.queryParam("client_id", confService.getClientID())
|
|
||||||
.queryParam("redirect_uri", confService.getRedirectURI());
|
|
||||||
|
|
||||||
|
if (confService.isImplicitFlow()) {
|
||||||
|
builder.queryParam("nonce", nonceService.generate(confService.getMaxNonceValidity() * 60000L));
|
||||||
|
} else {
|
||||||
if (confService.isPKCERequired()) {
|
if (confService.isPKCERequired()) {
|
||||||
String codeVerifier = PKCEUtil.generateCodeVerifier();
|
String codeVerifier = PKCEUtil.generateCodeVerifier();
|
||||||
String codeChallenge;
|
String codeChallenge;
|
||||||
@@ -230,9 +225,9 @@ public class AuthenticationProviderService implements SSOAuthenticationProviderS
|
|||||||
.queryParam("code_challenge_method", "S256")
|
.queryParam("code_challenge_method", "S256")
|
||||||
.queryParam(AUTH_SESSION_QUERY_PARAM, identifier);
|
.queryParam(AUTH_SESSION_QUERY_PARAM, identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
return builder.build();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
+2
@@ -21,8 +21,10 @@ package org.apache.guacamole.auth.openid;
|
|||||||
|
|
||||||
import com.google.inject.AbstractModule;
|
import com.google.inject.AbstractModule;
|
||||||
import com.google.inject.Scopes;
|
import com.google.inject.Scopes;
|
||||||
|
import com.google.inject.Singleton;
|
||||||
import org.apache.guacamole.auth.openid.conf.ConfigurationService;
|
import org.apache.guacamole.auth.openid.conf.ConfigurationService;
|
||||||
import org.apache.guacamole.auth.openid.conf.OpenIDEnvironment;
|
import org.apache.guacamole.auth.openid.conf.OpenIDEnvironment;
|
||||||
|
import org.apache.guacamole.auth.openid.conf.OpenIDWellKnown;
|
||||||
import org.apache.guacamole.auth.openid.OpenIDAuthenticationSessionManager;
|
import org.apache.guacamole.auth.openid.OpenIDAuthenticationSessionManager;
|
||||||
import org.apache.guacamole.auth.sso.NonceService;
|
import org.apache.guacamole.auth.sso.NonceService;
|
||||||
import org.apache.guacamole.auth.openid.token.TokenValidationService;
|
import org.apache.guacamole.auth.openid.token.TokenValidationService;
|
||||||
|
|||||||
+77
-18
@@ -20,13 +20,17 @@
|
|||||||
package org.apache.guacamole.auth.openid.conf;
|
package org.apache.guacamole.auth.openid.conf;
|
||||||
|
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import org.apache.guacamole.auth.openid.conf.OpenIDResponseType;
|
||||||
|
import org.apache.guacamole.auth.openid.conf.OpenIDWellKnown;
|
||||||
import org.apache.guacamole.GuacamoleException;
|
import org.apache.guacamole.GuacamoleException;
|
||||||
import org.apache.guacamole.environment.Environment;
|
import org.apache.guacamole.environment.Environment;
|
||||||
import org.apache.guacamole.properties.BooleanGuacamoleProperty;
|
import org.apache.guacamole.properties.BooleanGuacamoleProperty;
|
||||||
|
import org.apache.guacamole.properties.EnumGuacamoleProperty;
|
||||||
import org.apache.guacamole.properties.IntegerGuacamoleProperty;
|
import org.apache.guacamole.properties.IntegerGuacamoleProperty;
|
||||||
import org.apache.guacamole.properties.StringGuacamoleProperty;
|
import org.apache.guacamole.properties.StringGuacamoleProperty;
|
||||||
import org.apache.guacamole.properties.URIGuacamoleProperty;
|
import org.apache.guacamole.properties.URIGuacamoleProperty;
|
||||||
@@ -38,9 +42,9 @@ import org.apache.guacamole.properties.URIGuacamoleProperty;
|
|||||||
public class ConfigurationService {
|
public class ConfigurationService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default OICD flow type
|
* The default OICD reponse type
|
||||||
*/
|
*/
|
||||||
private static final String DEFAULT_FLOW_TYPE = "implicit";
|
private static final OpenIDResponseType DEFAULT_RESPONSE_TYPE = OpenIDResponseType.ID_TOKEN;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default claim type to use to retrieve an authenticated user's
|
* The default claim type to use to retrieve an authenticated user's
|
||||||
@@ -128,13 +132,13 @@ public class ConfigurationService {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The flow type of the OpenID service.
|
* The reponse type of the OpenID service.
|
||||||
*/
|
*/
|
||||||
private static final StringGuacamoleProperty OPENID_FLOW_TYPE =
|
private static final EnumGuacamoleProperty<OpenIDResponseType> OPENID_RESPONSE_TYPE =
|
||||||
new StringGuacamoleProperty() {
|
new EnumGuacamoleProperty<OpenIDResponseType>(OpenIDResponseType.class) {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() { return "openid-flow-type"; }
|
public String getName() { return "openid-response-type"; }
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -316,6 +320,12 @@ public class ConfigurationService {
|
|||||||
*/
|
*/
|
||||||
@Inject
|
@Inject
|
||||||
private Environment environment;
|
private Environment environment;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service for retrieving OpenID well-known data.
|
||||||
|
*/
|
||||||
|
@Inject
|
||||||
|
private OpenIDWellKnown confWellKnown;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the authorization endpoint (URI) of the OpenID service as
|
* Returns the authorization endpoint (URI) of the OpenID service as
|
||||||
@@ -326,11 +336,15 @@ public class ConfigurationService {
|
|||||||
* guacamole.properties.
|
* guacamole.properties.
|
||||||
*
|
*
|
||||||
* @throws GuacamoleException
|
* @throws GuacamoleException
|
||||||
* If guacamole.properties cannot be parsed, or if the authorization
|
|
||||||
* endpoint property is missing.
|
* endpoint property is missing.
|
||||||
*/
|
*/
|
||||||
public URI getAuthorizationEndpoint() throws GuacamoleException {
|
public URI getAuthorizationEndpoint() throws GuacamoleException {
|
||||||
return environment.getRequiredProperty(OPENID_AUTHORIZATION_ENDPOINT);
|
URI authorization_endpoint = environment.getProperty(OPENID_AUTHORIZATION_ENDPOINT);
|
||||||
|
authorization_endpoint = authorization_endpoint == null ? confWellKnown.getAuthorizationEndpoint() : authorization_endpoint;
|
||||||
|
if (authorization_endpoint == null) {
|
||||||
|
throw new GuacamoleException("Property openid-authorization-endpoint or openid-well-known-endpoint is required");
|
||||||
|
}
|
||||||
|
return authorization_endpoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -382,7 +396,12 @@ public class ConfigurationService {
|
|||||||
* is missing.
|
* is missing.
|
||||||
*/
|
*/
|
||||||
public String getIssuer() throws GuacamoleException {
|
public String getIssuer() throws GuacamoleException {
|
||||||
return environment.getRequiredProperty(OPENID_ISSUER);
|
String issuer = environment.getProperty(OPENID_ISSUER);
|
||||||
|
issuer = issuer == null ? confWellKnown.getIssuer() : issuer;
|
||||||
|
if (issuer == null) {
|
||||||
|
throw new GuacamoleException("Property openid-issuer or openid-well-known-endpoint is required");
|
||||||
|
}
|
||||||
|
return issuer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -400,7 +419,12 @@ public class ConfigurationService {
|
|||||||
* property is missing.
|
* property is missing.
|
||||||
*/
|
*/
|
||||||
public URI getJWKSEndpoint() throws GuacamoleException {
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -412,25 +436,60 @@ public class ConfigurationService {
|
|||||||
* guacamole.properties.
|
* guacamole.properties.
|
||||||
*
|
*
|
||||||
* @throws GuacamoleException
|
* @throws GuacamoleException
|
||||||
* If guacamole.properties cannot be parsed, or if the authorization
|
* If guacamole.properties cannot be parsed, or if the token
|
||||||
* endpoint property is missing.
|
* endpoint property is missing.
|
||||||
*/
|
*/
|
||||||
public URI getTokenEndpoint() throws GuacamoleException {
|
public URI getTokenEndpoint() throws GuacamoleException {
|
||||||
return environment.getRequiredProperty(OPENID_TOKEN_ENDPOINT);
|
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 flow type of the OpenID service as configured with guacamole.properties.
|
* Returns the well-known endpoint (URI) of the OIDC service as
|
||||||
|
* configured with guacamole.properties.
|
||||||
*
|
*
|
||||||
* @return
|
* @return
|
||||||
* The flow type of the OpenID service, as configured with guacamole.properties. Can
|
* The well-known endpoint of the OIDC service, as configured with
|
||||||
* be either 'implicit' or 'code'.
|
* 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the reponse type of the OpenID service as configured with guacamole.properties.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* The reponse type of the OpenID service, as configured with guacamole.properties. Can
|
||||||
|
* be either 'id_token', 'token' or 'code'.
|
||||||
*
|
*
|
||||||
* @throws GuacamoleException
|
* @throws GuacamoleException
|
||||||
* If guacamole.properties cannot be parsed.
|
* If guacamole.properties cannot be parsed.
|
||||||
*/
|
*/
|
||||||
public String getFlowType() throws GuacamoleException {
|
public OpenIDResponseType getResponseType() throws GuacamoleException {
|
||||||
return environment.getProperty(OPENID_FLOW_TYPE, DEFAULT_FLOW_TYPE);
|
return environment.getProperty(OPENID_RESPONSE_TYPE, DEFAULT_RESPONSE_TYPE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the response type defines an implict flow
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* The whether implicit flow is used or not, as configured with guacamole.properties.
|
||||||
|
*
|
||||||
|
* @throws GuacamoleException
|
||||||
|
* If guacamole.properties cannot be parsed.
|
||||||
|
*/
|
||||||
|
public boolean isImplicitFlow() throws GuacamoleException {
|
||||||
|
OpenIDResponseType response_type = environment.getProperty(OPENID_RESPONSE_TYPE, DEFAULT_RESPONSE_TYPE);
|
||||||
|
return response_type != OpenIDResponseType.CODE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+67
@@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
* 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 org.apache.guacamole.properties.EnumGuacamoleProperty.PropertyValue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This enum represents the valid OIDC reponse types that can be used
|
||||||
|
*/
|
||||||
|
public enum OpenIDResponseType {
|
||||||
|
/*
|
||||||
|
* Response type "id_token" used for implciit flow as specified in the OpenID standard
|
||||||
|
*/
|
||||||
|
@PropertyValue("id_token")
|
||||||
|
ID_TOKEN("id_token"),
|
||||||
|
|
||||||
|
/* Response type "token" used for implicit flow by certain Identity Providers, notably
|
||||||
|
* AWS Cognito. This corresponds to the official OIDC response_type "id_token token"
|
||||||
|
* that returns both the "id_token" and "access_token" parameters
|
||||||
|
*/
|
||||||
|
@PropertyValue("token")
|
||||||
|
TOKEN("token"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response type "code" used for code flow authentication
|
||||||
|
*/
|
||||||
|
@PropertyValue("code")
|
||||||
|
CODE("code");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The string value of the response type used
|
||||||
|
*/
|
||||||
|
public final String STRING_VALUE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the response type such that it is associated with the
|
||||||
|
* given string value.
|
||||||
|
*
|
||||||
|
* @param value
|
||||||
|
* The string value that will be associated with the enum value.
|
||||||
|
*/
|
||||||
|
private OpenIDResponseType(String value) {
|
||||||
|
this.STRING_VALUE = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return STRING_VALUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
+214
@@ -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(), "");
|
||||||
|
issuer = (String) json.get("issuer");
|
||||||
|
authorization_endpoint = UriBuilder.fromUri((String) json.get("authorization_endpoint")).build();
|
||||||
|
token_endpoint = UriBuilder.fromUri((String) json.get("token_endpoint")).build();
|
||||||
|
jwks_uri = UriBuilder.fromUri((String) json.get("jwks_uri")).build();
|
||||||
|
|
||||||
|
logger.debug("OIDC well-known\n" +
|
||||||
|
" issuer : {}\n" +
|
||||||
|
" authorization_endpoint : {}\n" +
|
||||||
|
" token_endpoint : {}\n" +
|
||||||
|
" jwks_uri : {}\n",
|
||||||
|
issuer, authorization_endpoint, token_endpoint, jwks_uri);
|
||||||
|
|
||||||
|
scheduler.shutdown();
|
||||||
|
return;
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.debug("Rejecting well-known endpoint : {}", e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attempts >= MAX_ATTEMPTS) {
|
||||||
|
logger.info("Timeout on well-known on endpoint");
|
||||||
|
scheduler.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
scheduler.scheduleAtFixedRate(task, 0, DELAY_SECONDS, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
}
|
||||||
+4
-45
@@ -20,11 +20,6 @@
|
|||||||
package org.apache.guacamole.auth.openid.token;
|
package org.apache.guacamole.auth.openid.token;
|
||||||
|
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.net.HttpURLConnection;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
@@ -39,6 +34,7 @@ import java.util.Set;
|
|||||||
import javax.ws.rs.core.UriBuilder;
|
import javax.ws.rs.core.UriBuilder;
|
||||||
import org.apache.guacamole.GuacamoleException;
|
import org.apache.guacamole.GuacamoleException;
|
||||||
import org.apache.guacamole.auth.openid.conf.ConfigurationService;
|
import org.apache.guacamole.auth.openid.conf.ConfigurationService;
|
||||||
|
import org.apache.guacamole.auth.openid.util.JsonUrlReader;
|
||||||
import org.apache.guacamole.auth.sso.NonceService;
|
import org.apache.guacamole.auth.sso.NonceService;
|
||||||
import org.apache.guacamole.token.TokenName;
|
import org.apache.guacamole.token.TokenName;
|
||||||
import org.jose4j.json.JsonUtil;
|
import org.jose4j.json.JsonUtil;
|
||||||
@@ -243,46 +239,9 @@ public class TokenValidationService {
|
|||||||
bodyBuilder.append("&").append(urlencode("code_verifier", verifier));
|
bodyBuilder.append("&").append(urlencode("code_verifier", verifier));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build the final URI and convert to a URL
|
Map<String,Object> json =
|
||||||
URL url = confService.getTokenEndpoint().toURL();
|
JsonUrlReader.fetch("POST", confService.getTokenEndpoint().toURL(),
|
||||||
|
bodyBuilder.toString());
|
||||||
// Open connection, using HttpURLConnection
|
|
||||||
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
|
||||||
|
|
||||||
conn.setRequestMethod("POST");
|
|
||||||
conn.setDoOutput(true);
|
|
||||||
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8"
|
|
||||||
);
|
|
||||||
|
|
||||||
try (OutputStream out = conn.getOutputStream()) {
|
|
||||||
byte [] body = bodyBuilder.toString().getBytes(StandardCharsets.UTF_8);
|
|
||||||
out.write(body, 0, body.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read response
|
|
||||||
int status = conn.getResponseCode();
|
|
||||||
|
|
||||||
BufferedReader reader = new BufferedReader(
|
|
||||||
new InputStreamReader(
|
|
||||||
status >= 200 && status < 300
|
|
||||||
? conn.getInputStream()
|
|
||||||
: conn.getErrorStream(),
|
|
||||||
StandardCharsets.UTF_8
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
StringBuilder responseBody = new StringBuilder();
|
|
||||||
String line;
|
|
||||||
while ((line = reader.readLine()) != null) {
|
|
||||||
responseBody.append(line);
|
|
||||||
}
|
|
||||||
reader.close();
|
|
||||||
|
|
||||||
Map<String,Object> json = JsonUtil.parseJson(responseBody.toString());
|
|
||||||
|
|
||||||
if (status < 200 || status >= 300) {
|
|
||||||
throw new GuacamoleException("Token endpoint error (" + status + "): " + json.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
return (String) json.get("id_token");
|
return (String) json.get("id_token");
|
||||||
|
|
||||||
|
|||||||
+98
@@ -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")) {
|
||||||
|
conn.setDoOutput(true);
|
||||||
|
try (OutputStream out = conn.getOutputStream()) {
|
||||||
|
byte [] bodyBytes = body.getBytes(StandardCharsets.UTF_8);
|
||||||
|
out.write(bodyBytes, 0, bodyBytes.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read response
|
||||||
|
int status = conn.getResponseCode();
|
||||||
|
|
||||||
|
BufferedReader reader = new BufferedReader(
|
||||||
|
new InputStreamReader(
|
||||||
|
status >= 200 && status < 300
|
||||||
|
? conn.getInputStream()
|
||||||
|
: conn.getErrorStream(),
|
||||||
|
StandardCharsets.UTF_8
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
StringBuilder responseBody = new StringBuilder();
|
||||||
|
String line;
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
responseBody.append(line);
|
||||||
|
}
|
||||||
|
reader.close();
|
||||||
|
|
||||||
|
logger.debug("Response body : {}", responseBody.toString());
|
||||||
|
|
||||||
|
Map<String,Object> json = JsonUtil.parseJson(responseBody.toString());
|
||||||
|
|
||||||
|
if (status < 200 || status >= 300) {
|
||||||
|
throw new GuacamoleException("(status: " + status + "): " + json.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return json;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new GuacamoleException("JsonUrlreader error : " + method + " : " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user