diff --git a/guacamole/pom.xml b/guacamole/pom.xml index ebad2b8892..f7600337ae 100644 --- a/guacamole/pom.xml +++ b/guacamole/pom.xml @@ -378,7 +378,5 @@ junit test - - diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/auth/APIAuthenticationResult.java b/guacamole/src/main/java/org/apache/guacamole/rest/auth/APIAuthenticationResult.java index 2f50823424..c9e9dba405 100644 --- a/guacamole/src/main/java/org/apache/guacamole/rest/auth/APIAuthenticationResult.java +++ b/guacamole/src/main/java/org/apache/guacamole/rest/auth/APIAuthenticationResult.java @@ -52,6 +52,23 @@ public class APIAuthenticationResult { */ private final List availableDataSources; + /** + * The baseUrl injected by the requester to be returned for load balancing purposes. + */ + private final String baseUrl; + + + /** + * Returns the baseUrl that should be used by the client for subsequent requests, + * if any. This is typically used for load balancing or redirection. + * + * @return + * The baseUrl to be used, or null if no specific baseUrl is required. + */ + public String getBaseUrl() { + return baseUrl; + } + /** * Returns the unique authentication token which identifies the current * session. @@ -118,10 +135,39 @@ public List getAvailableDataSources() { */ public APIAuthenticationResult(String authToken, String username, String dataSource, List availableDataSources) { + // Chain to the new constructor with null for baseUrl + this(authToken, username, dataSource, availableDataSources, null); + } + + /** + * Create a new APIAuthenticationResult object containing the given data. + * + * @param authToken + * The unique token generated for the user that authenticated, to be + * used for the duration of their session. + * + * @param username + * The username of the user owning the given token. + * + * @param dataSource + * The unique identifier of the AuthenticationProvider which + * authenticated the user. + * + * @param availableDataSources + * The unique identifier of all AuthenticationProviders to which the + * user now has access. + * + * @param baseUrl + * The base URL that the client should use for subsequent requests, + * or null if the standard URL should be used. + */ + public APIAuthenticationResult(String authToken, String username, + String dataSource, List availableDataSources, String baseUrl) { this.authToken = authToken; this.username = username; this.dataSource = dataSource; this.availableDataSources = Collections.unmodifiableList(availableDataSources); + this.baseUrl = baseUrl; } } diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/auth/TokenRESTService.java b/guacamole/src/main/java/org/apache/guacamole/rest/auth/TokenRESTService.java index 427af7f399..d0627afd5d 100644 --- a/guacamole/src/main/java/org/apache/guacamole/rest/auth/TokenRESTService.java +++ b/guacamole/src/main/java/org/apache/guacamole/rest/auth/TokenRESTService.java @@ -36,9 +36,11 @@ import javax.ws.rs.core.MultivaluedMap; import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.GuacamoleResourceNotFoundException; +import org.apache.guacamole.environment.Environment; import org.apache.guacamole.net.auth.AuthenticatedUser; import org.apache.guacamole.net.auth.Credentials; import org.apache.guacamole.net.auth.UserContext; +import org.apache.guacamole.properties.StringGuacamoleProperty; import org.apache.guacamole.GuacamoleSession; import org.apache.guacamole.rest.APIRequest; import org.slf4j.Logger; @@ -62,6 +64,25 @@ public class TokenRESTService { @Inject private AuthenticationService authenticationService; + /** + * The Guacamole server environment. + */ + @Inject + private Environment environment; + + /** + * The property within guacamole.properties which defines the HTTP header + * that contains the client base URL (for service discovery/load balancing). + */ + private static final StringGuacamoleProperty AUTH_BASE_URL_HEADER = new StringGuacamoleProperty() { + + @Override + public String getName() { + return "auth-base-url-header"; + } + + }; + /** * Returns the credentials associated with the given request, using the * provided username and password. @@ -173,6 +194,19 @@ public APIAuthenticationResult createToken(@FormParam("username") String usernam // Create/update session producing possibly-new token token = authenticationService.authenticate(credentials, token); + // Determine if a custom Base URL should be injected + String baseUrl = null; + + // Use the injected environment instead of static access + String headerName = environment.getProperty(AUTH_BASE_URL_HEADER); + + if (headerName != null) { + String headerValue = request.getHeader(headerName); + if (headerValue != null) { + baseUrl = headerValue.replace("{TOKEN}", token); + } + } + // Pull corresponding session GuacamoleSession session = authenticationService.getGuacamoleSession(token); if (session == null) @@ -190,7 +224,8 @@ public APIAuthenticationResult createToken(@FormParam("username") String usernam token, authenticatedUser.getIdentifier(), authenticatedUser.getAuthenticationProvider().getIdentifier(), - authProviderIdentifiers + authProviderIdentifiers, + baseUrl ); } diff --git a/guacamole/src/test/java/org/apache/guacamole/rest/auth/APIAuthenticationResultTest.java b/guacamole/src/test/java/org/apache/guacamole/rest/auth/APIAuthenticationResultTest.java new file mode 100644 index 0000000000..702312a275 --- /dev/null +++ b/guacamole/src/test/java/org/apache/guacamole/rest/auth/APIAuthenticationResultTest.java @@ -0,0 +1,61 @@ +/* + * 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.rest.auth; + +import java.util.Collections; +import org.junit.Test; +import static org.junit.Assert.*; + +public class APIAuthenticationResultTest { + + /** + * Verifies that the baseUrl is correctly stored and retrieved when + * using the new constructor. + */ + @Test + public void testBaseUrlInjection() { + String expectedUrl = "https://lb.example.com"; + APIAuthenticationResult result = new APIAuthenticationResult( + "token", + "user", + "source", + Collections.emptyList(), + expectedUrl + ); + + assertEquals(expectedUrl, result.getBaseUrl()); + } + + /** + * Verifies that the legacy constructor still works and results in a + * null baseUrl (backward compatibility). + */ + @Test + public void testLegacyConstructor() { + APIAuthenticationResult result = new APIAuthenticationResult( + "token", + "user", + "source", + Collections.emptyList() + ); + + assertNull(result.getBaseUrl()); + } +}