diff --git a/extensions/guacamole-auth-openid/src/main/java/org/apache/guacamole/auth/oauth/AuthenticationProviderService.java b/extensions/guacamole-auth-openid/src/main/java/org/apache/guacamole/auth/oauth/AuthenticationProviderService.java index a1b9c462d..5783faa5d 100644 --- a/extensions/guacamole-auth-openid/src/main/java/org/apache/guacamole/auth/oauth/AuthenticationProviderService.java +++ b/extensions/guacamole-auth-openid/src/main/java/org/apache/guacamole/auth/oauth/AuthenticationProviderService.java @@ -26,6 +26,8 @@ import javax.servlet.http.HttpServletRequest; import org.apache.guacamole.auth.oauth.user.AuthenticatedUser; import org.apache.guacamole.auth.oauth.conf.ConfigurationService; import org.apache.guacamole.auth.oauth.form.OAuthCodeField; +import org.apache.guacamole.auth.oauth.token.TokenResponse; +import org.apache.guacamole.auth.oauth.token.TokenService; import org.glyptodon.guacamole.GuacamoleException; import org.glyptodon.guacamole.form.Field; import org.glyptodon.guacamole.net.auth.Credentials; @@ -51,6 +53,12 @@ public class AuthenticationProviderService { @Inject private ConfigurationService confService; + /** + * Service for producing authentication tokens from OAuth codes. + */ + @Inject + private TokenService tokenService; + /** * Provider for AuthenticatedUser objects. */ @@ -84,9 +92,16 @@ public class AuthenticationProviderService { // TODO: Actually complete authentication using received code if (code != null) { + + // POST code and client information to OAuth token endpoint + TokenResponse response = tokenService.getTokenFromCode(code); + logger.debug("RESPONSE: {}", response); + + // Create corresponding authenticated user AuthenticatedUser authenticatedUser = authenticatedUserProvider.get(); authenticatedUser.init("STUB", credentials); return authenticatedUser; + } // Request auth code diff --git a/extensions/guacamole-auth-openid/src/main/java/org/apache/guacamole/auth/oauth/OAuthAuthenticationProviderModule.java b/extensions/guacamole-auth-openid/src/main/java/org/apache/guacamole/auth/oauth/OAuthAuthenticationProviderModule.java index e31c9457f..a5cef6da5 100644 --- a/extensions/guacamole-auth-openid/src/main/java/org/apache/guacamole/auth/oauth/OAuthAuthenticationProviderModule.java +++ b/extensions/guacamole-auth-openid/src/main/java/org/apache/guacamole/auth/oauth/OAuthAuthenticationProviderModule.java @@ -20,7 +20,13 @@ package org.apache.guacamole.auth.oauth; import com.google.inject.AbstractModule; +import com.sun.jersey.api.client.Client; +import com.sun.jersey.api.client.config.ClientConfig; +import com.sun.jersey.api.client.config.DefaultClientConfig; import org.apache.guacamole.auth.oauth.conf.ConfigurationService; +import org.apache.guacamole.auth.oauth.token.TokenService; +import org.codehaus.jackson.jaxrs.JacksonJaxbJsonProvider; +import org.codehaus.jackson.map.DeserializationConfig; import org.glyptodon.guacamole.GuacamoleException; import org.glyptodon.guacamole.environment.Environment; import org.glyptodon.guacamole.environment.LocalEnvironment; @@ -42,6 +48,12 @@ public class OAuthAuthenticationProviderModule extends AbstractModule { */ private final AuthenticationProvider authProvider; + /** + * A reference to the shared HTTP client to be used when making calls to + * the OAuth service. + */ + private final Client client; + /** * Creates a new OAuth authentication provider module which configures * injection for the OAuthAuthenticationProvider. @@ -62,6 +74,15 @@ public class OAuthAuthenticationProviderModule extends AbstractModule { // Store associated auth provider this.authProvider = authProvider; + // Set up configuration for HTTP client + ClientConfig clientConfig = new DefaultClientConfig(); + clientConfig.getSingletons().add(new JacksonJaxbJsonProvider() + .configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false) + ); + + // Store pre-configured HTTP client + this.client = Client.create(clientConfig); + } @Override @@ -73,6 +94,10 @@ public class OAuthAuthenticationProviderModule extends AbstractModule { // Bind OAuth-specific services bind(ConfigurationService.class); + bind(TokenService.class); + + // Bind HTTP client + bind(Client.class).toInstance(client); } diff --git a/extensions/guacamole-auth-openid/src/main/java/org/apache/guacamole/auth/oauth/TokenResponse.java b/extensions/guacamole-auth-openid/src/main/java/org/apache/guacamole/auth/oauth/TokenResponse.java deleted file mode 100644 index 329fb1626..000000000 --- a/extensions/guacamole-auth-openid/src/main/java/org/apache/guacamole/auth/oauth/TokenResponse.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * 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.oauth; - -import com.google.inject.Inject; -import com.google.inject.Provider; -import java.util.Arrays; -import javax.servlet.http.HttpServletRequest; -import org.glyptodon.guacamole.GuacamoleException; -import org.apache.guacamole.auth.oauth.conf.ConfigurationService; -import org.apache.guacamole.auth.oauth.form.OAuthCodeField; -import org.apache.guacamole.auth.oauth.user.AuthenticatedUser; -import org.glyptodon.guacamole.form.Field; -import org.glyptodon.guacamole.net.auth.Credentials; -import org.glyptodon.guacamole.net.auth.credentials.CredentialsInfo; -import org.glyptodon.guacamole.net.auth.credentials.GuacamoleInvalidCredentialsException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Service providing convenience functions for the OAuth AuthenticationProvider - * implementation. - */ -public class AuthenticationProviderService { - - /** - * Logger for this class. - */ - private final Logger logger = LoggerFactory.getLogger(AuthenticationProviderService.class); - - /** - * Service for retrieving OAuth configuration information. - */ - @Inject - private ConfigurationService confService; - - /** - * Provider for AuthenticatedUser objects. - */ - @Inject - private Provider authenticatedUserProvider; - - /** - * Returns an AuthenticatedUser representing the user authenticated by the - * given credentials. - * - * @param credentials - * The credentials to use for authentication. - * - * @return - * An AuthenticatedUser representing the user authenticated by the - * given credentials. - * - * @throws GuacamoleException - * If an error occurs while authenticating the user, or if access is - * denied. - */ - public AuthenticatedUser authenticateUser(Credentials credentials) - throws GuacamoleException { - - String code = null; - - // Pull OAuth code from request if present - HttpServletRequest request = credentials.getRequest(); - if (request != null) - code = request.getParameter(OAuthCodeField.PARAMETER_NAME); - - // TODO: Actually complete authentication using received code - if (code != null) { - AuthenticatedUser authenticatedUser = authenticatedUserProvider.get(); - authenticatedUser.init("STUB", credentials); - return authenticatedUser; - } - - // Request auth code - throw new GuacamoleInvalidCredentialsException("Invalid login.", - new CredentialsInfo(Arrays.asList(new Field[] { - - // Normal username/password fields - CredentialsInfo.USERNAME, - CredentialsInfo.PASSWORD, - - // OAuth-specific code (will be rendered as an appropriate - // "Log in with..." button - new OAuthCodeField( - confService.getAuthorizationEndpoint(), - confService.getClientID(), - confService.getRedirectURI() - ) - - })) - ); - - } - -} diff --git a/extensions/guacamole-auth-openid/src/main/java/org/apache/guacamole/auth/oauth/token/TokenResponse.java b/extensions/guacamole-auth-openid/src/main/java/org/apache/guacamole/auth/oauth/token/TokenResponse.java new file mode 100644 index 000000000..513683041 --- /dev/null +++ b/extensions/guacamole-auth-openid/src/main/java/org/apache/guacamole/auth/oauth/token/TokenResponse.java @@ -0,0 +1,153 @@ +/* + * 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.oauth.token; + +import org.codehaus.jackson.annotate.JsonProperty; + +/** + * The response produced from a successful request to the token endpoint of an + * OAuth service. + */ +public class TokenResponse { + + /** + * An arbitrary access token which can be used for future requests against + * the API associated with the OAuth service. + */ + private String accessToken; + + /** + * The type of token present. This will always be "Bearer". + */ + private String tokenType; + + /** + * The number of seconds the access token will remain valid. + */ + private int expiresIn; + + /** + * A JWT (JSON Web Token) which containing identity information which has + * been cryptographically signed. + */ + private String idToken; + + /** + * Returns an arbitrary access token which can be used for future requests + * against the API associated with the OAuth service. + * + * @return + * An arbitrary access token provided by the OAuth service. + */ + @JsonProperty("access_token") + public String getAccessToken() { + return accessToken; + } + + /** + * Sets the arbitrary access token which can be used for future requests + * against the API associated with the OAuth service. + * + * @param accessToken + * The arbitrary access token provided by the OAuth service. + */ + @JsonProperty("access_token") + public void setAccessToken(String accessToken) { + this.accessToken = accessToken; + } + + /** + * Returns the type of token present in this response. This should always + * be "Bearer". + * + * @return + * The type of token present in this response. + */ + @JsonProperty("token_type") + public String getTokenType() { + return tokenType; + } + + /** + * Sets the type of token present in this response. This should always be + * "Bearer". + * + * @param tokenType + * The type of token present in this response, which should be + * "Bearer". + */ + @JsonProperty("token_type") + public void setTokenType(String tokenType) { + this.tokenType = tokenType; + } + + /** + * Returns the number of seconds the access token within this response will + * remain valid. + * + * @return + * The number of seconds the access token within this response will + * remain valid. + */ + @JsonProperty("expires_in") + public int getExpiresIn() { + return expiresIn; + } + + /** + * Sets the number of seconds the access token within this response will + * remain valid. + * + * @param expiresIn + * The number of seconds the access token within this response will + * remain valid. + */ + @JsonProperty("expires_in") + public void setExpiresIn(int expiresIn) { + this.expiresIn = expiresIn; + } + + /** + * Returns a JWT (JSON Web Token) containing identity information which has + * been cryptographically signed by the OAuth service. + * + * @return + * A JWT (JSON Web Token) containing identity information which has + * been cryptographically signed by the OAuth service. + */ + @JsonProperty("id_token") + public String getIdToken() { + return idToken; + } + + /** + * Sets the JWT (JSON Web Token) containing identity information which has + * been cryptographically signed by the OAuth service. + * + * @param idToken + * A JWT (JSON Web Token) containing identity information which has + * been cryptographically signed by the OAuth service. + */ + @JsonProperty("id_token") + public void setIdToken(String idToken) { + this.idToken = idToken; + } + +} diff --git a/extensions/guacamole-auth-openid/src/main/java/org/apache/guacamole/auth/oauth/token/TokenService.java b/extensions/guacamole-auth-openid/src/main/java/org/apache/guacamole/auth/oauth/token/TokenService.java new file mode 100644 index 000000000..a328bde4d --- /dev/null +++ b/extensions/guacamole-auth-openid/src/main/java/org/apache/guacamole/auth/oauth/token/TokenService.java @@ -0,0 +1,101 @@ +/* + * 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.oauth.token; + +import com.google.inject.Inject; +import com.sun.jersey.api.client.Client; +import com.sun.jersey.api.client.UniformInterfaceException; +import com.sun.jersey.api.representation.Form; +import javax.ws.rs.core.MediaType; +import org.apache.guacamole.auth.oauth.AuthenticationProviderService; +import org.apache.guacamole.auth.oauth.conf.ConfigurationService; +import org.glyptodon.guacamole.GuacamoleException; +import org.glyptodon.guacamole.GuacamoleServerException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Provides relatively abstract means of producing authentication tokens from + * the codes received from OAuth services. + */ +public class TokenService { + + /** + * Logger for this class. + */ + private final Logger logger = LoggerFactory.getLogger(AuthenticationProviderService.class); + + /** + * Service for retrieving OAuth configuration information. + */ + @Inject + private ConfigurationService confService; + + /** + * Jersey HTTP client. + */ + @Inject + private Client client; + + /** + * Given an authorization code previously received from the OAuth service + * via the "code" parameter provided to the redirect URL, retrieves and + * returns an authentication token. + * + * @param code + * The value of the "code" parameter received from the OAuth service. + * + * @return + * The authentication roken response received from the OAuth service. + * + * @throws GuacamoleException + * If required properties within guacamole.properties cannot be read, + * or if an error occurs while contacting the OAuth service. + */ + public TokenResponse getTokenFromCode(String code) + throws GuacamoleException { + + try { + + // Generate POST data + Form form = new Form(); + form.add("code", code); + form.add("client_id", confService.getClientID()); + form.add("client_secret", confService.getClientSecret()); + form.add("redirect_uri", confService.getRedirectURI()); + form.add("grant_type", "authorization_code"); + + // POST code and client information to OAuth token endpoint + return client.resource(confService.getTokenEndpoint()) + .type(MediaType.APPLICATION_FORM_URLENCODED_TYPE) + .accept(MediaType.APPLICATION_JSON_TYPE) + .post(TokenResponse.class, form); + + } + + // Log any failure reaching the OAuth service + catch (UniformInterfaceException e) { + logger.debug("POST to token endpoint failed.", e); + throw new GuacamoleServerException("Unable to POST to token endpoint.", e); + } + + } + +}