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());
+ }
+}