diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/RemoteAuthenticatedUser.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/RemoteAuthenticatedUser.java index a936e4ecc0..626ea174ef 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/RemoteAuthenticatedUser.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/RemoteAuthenticatedUser.java @@ -51,6 +51,12 @@ public abstract class RemoteAuthenticatedUser implements AuthenticatedUser { */ private final Set effectiveGroups; + /** + * The URI that was originally used to the first call to the authentication + * providers authenticateUser method. + */ + private String originalUri; + /** * Creates a new RemoteAuthenticatedUser, deriving the associated remote * host from the given credentials. @@ -103,4 +109,14 @@ public void invalidate() { // Nothing to invalidate } + @Override + public void setOriginalUri(String originalUri) { + this.originalUri = originalUri; + } + + @Override + public String getOriginalUri() { + return this.originalUri; + } + } diff --git a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-base/src/main/java/org/apache/guacamole/auth/sso/session/SSOAuthenticationSession.java b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-base/src/main/java/org/apache/guacamole/auth/sso/session/SSOAuthenticationSession.java new file mode 100644 index 0000000000..df2d170c3f --- /dev/null +++ b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-base/src/main/java/org/apache/guacamole/auth/sso/session/SSOAuthenticationSession.java @@ -0,0 +1,129 @@ +/* + * 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.sso.session; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.Map; +import org.apache.guacamole.net.auth.AuthenticationSession; +import org.apache.guacamole.net.auth.Credentials; + +/** + * Representation of an in-progress OpenID authentication attempt. + */ +public class SSOAuthenticationSession extends AuthenticationSession { + /** + * The key value used to store the redirection URI + */ + private static String REDIRECTION = "redirection"; + + /** + * THe key value of the redirection URI in the credential parameers + */ + private static String REQUEST_HREF = "href"; + + /** + * A Map of Arbitrary session data + */ + private final Map session; + + /** + * Creates a new AuthenticationSession representing an in-progress + * authentication attempt. + * + * @param session + * A Map of the session data to be stored + * + * @param expires + * The number of milliseconds that may elapse before this session must + * be considered invalid. + */ + public SSOAuthenticationSession(Map session, long expires) { + super(expires); + this.session = session; + } + + /** + * Creates a new AuthenticationSession representing an in-progress + * authentication attempt. + * + * @param expires + * The number of milliseconds that may elapse before this session must + * be considered invalid. + */ + public SSOAuthenticationSession(long expires) { + this(new ConcurrentHashMap<>(), expires); + } + + /** + * Returns the stored session data + * + * @return + * The session data, can be null + */ + public Map getSession() { + return session; + } + + /** + * Returns an Object stored in the session data + * + * @return + * The object in the session, can be null + */ + public Object get(String key) { + return session.get(key); + } + + /** + * Returns an Object stored in the session data + * + * @return + * The object in the session, can be null + */ + public void put(String key, Object value) { + session.put(key, value); + } + + /** + * Special case for redirection from credentials to + * simplify he authentication providers + * + * @return + * The redirection stored in teh session + */ + public String getRedirection() { + Object obj = session.get(REDIRECTION); + return obj == null ? null : obj.toString(); + } + + /** + * Special case for redirection from credentials to + * simplify he authentication providers + * + * @param credentials + * The credentials from which to extract the redirection. + */ + public void setRedirection(Credentials credentials) { + String redirection = credentials.getParameter(REQUEST_HREF); + put(REDIRECTION, redirection); + } +} + + diff --git a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-base/src/main/java/org/apache/guacamole/auth/sso/session/SSOAuthenticationSessionManager.java b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-base/src/main/java/org/apache/guacamole/auth/sso/session/SSOAuthenticationSessionManager.java new file mode 100644 index 0000000000..60116ddfe0 --- /dev/null +++ b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-base/src/main/java/org/apache/guacamole/auth/sso/session/SSOAuthenticationSessionManager.java @@ -0,0 +1,48 @@ +/* + * 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.sso.session; + +import java.util.Map; +import com.google.inject.Singleton; +import org.apache.guacamole.net.auth.AuthenticationSessionManager; + +/** + * Manager service that temporarily stores authentication attempts while + * the authentication flow is underway. + */ +@Singleton +public class SSOAuthenticationSessionManager + extends AuthenticationSessionManager { + + /** + * Returns the stored session data used with the identity provider + * + * @param identifier + * The unique string returned by the call to defer(). For convenience, + * this value may safely be null. + * + * @return + * The session data + */ + public SSOAuthenticationSession resume(String identifier) { + return super.resume(identifier); + } +} + diff --git a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/AuthenticationProviderService.java b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/AuthenticationProviderService.java index dc559c6b72..00035a55e5 100644 --- a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/AuthenticationProviderService.java +++ b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/AuthenticationProviderService.java @@ -33,6 +33,8 @@ import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.auth.sso.NonceService; import org.apache.guacamole.auth.sso.SSOAuthenticationProviderService; +import org.apache.guacamole.auth.sso.session.SSOAuthenticationSession; +import org.apache.guacamole.auth.sso.session.SSOAuthenticationSessionManager; import org.apache.guacamole.auth.sso.user.SSOAuthenticatedUser; import org.apache.guacamole.form.Field; import org.apache.guacamole.form.RedirectField; @@ -66,6 +68,12 @@ public class AuthenticationProviderService implements SSOAuthenticationProviderS @Inject private NonceService nonceService; + /** + * Manager of active OpenID authentication attempts. Tracks redirection + */ + @Inject + private SSOAuthenticationSessionManager sessionManager; + /** * Service for validating received ID tokens. */ @@ -78,6 +86,25 @@ public class AuthenticationProviderService implements SSOAuthenticationProviderS @Inject private Provider authenticatedUserProvider; + /** + * Return the value of the session identifier associated with the given + * credentials, or null if no session identifier is found in the + * credentials. + * + * @param credentials + * The credentials from which to extract the session identifier. + * + * @return + * The session identifier associated with the given credentials, or + * null if no identifier is found. + */ + private static String getSessionIdentifier(Credentials credentials) { + + // Return the session identifier from the request params, if set, or + // null otherwise + return credentials != null ? credentials.getParameter("state") : null; + } + @Override public SSOAuthenticatedUser authenticateUser(Credentials credentials) throws GuacamoleException { @@ -86,6 +113,10 @@ public SSOAuthenticatedUser authenticateUser(Credentials credentials) Set groups = null; Map tokens = Collections.emptyMap(); + // Recover session + String identifier = getSessionIdentifier(credentials); + SSOAuthenticationSession session = sessionManager.resume(identifier); + // Validate OpenID token in request, if present, and derive username String token = credentials.getParameter(TOKEN_PARAMETER_NAME); if (token != null) { @@ -104,6 +135,9 @@ public SSOAuthenticatedUser authenticateUser(Credentials credentials) // Create corresponding authenticated user SSOAuthenticatedUser authenticatedUser = authenticatedUserProvider.get(); authenticatedUser.init(username, credentials, groups, tokens); + if (session != null) { + authenticatedUser.setOriginalUri(session.getRedirection()); + } return authenticatedUser; } @@ -112,7 +146,7 @@ public SSOAuthenticatedUser authenticateUser(Credentials credentials) // OpenID authorization page via JavaScript) throw new GuacamoleInvalidCredentialsException("Invalid login.", new CredentialsInfo(Arrays.asList(new Field[] { - new RedirectField(TOKEN_PARAMETER_NAME, getLoginURI(), + new RedirectField(TOKEN_PARAMETER_NAME, getLoginURI(credentials), new TranslatableMessage("LOGIN.INFO_IDP_REDIRECT_PENDING")) })) ); @@ -121,12 +155,24 @@ public SSOAuthenticatedUser authenticateUser(Credentials credentials) @Override public URI getLoginURI() throws GuacamoleException { + return getLoginURI(null); + } + + private URI getLoginURI(Credentials credentials) throws GuacamoleException { + // Create a session to store redirection, validity 10 miinutes + SSOAuthenticationSession session = new SSOAuthenticationSession(600000L); + if (credentials != null) { + session.setRedirection(credentials); + } + String identifier = sessionManager.defer(session); + return UriBuilder.fromUri(confService.getAuthorizationEndpoint()) .queryParam("scope", confService.getScope()) .queryParam("response_type", "id_token") .queryParam("client_id", confService.getClientID()) .queryParam("redirect_uri", confService.getRedirectURI()) .queryParam("nonce", nonceService.generate(confService.getMaxNonceValidity() * 60000L)) + .queryParam("state", identifier) .build(); } diff --git a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/OpenIDAuthenticationProviderModule.java b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/OpenIDAuthenticationProviderModule.java index 6dc45f7f97..74a0cc65ab 100644 --- a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/OpenIDAuthenticationProviderModule.java +++ b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/OpenIDAuthenticationProviderModule.java @@ -24,6 +24,7 @@ import org.apache.guacamole.auth.openid.conf.ConfigurationService; import org.apache.guacamole.auth.openid.conf.OpenIDEnvironment; import org.apache.guacamole.auth.sso.NonceService; +import org.apache.guacamole.auth.sso.session.SSOAuthenticationSessionManager; import org.apache.guacamole.auth.openid.token.TokenValidationService; import org.apache.guacamole.environment.Environment; @@ -42,6 +43,7 @@ protected void configure() { bind(ConfigurationService.class); bind(NonceService.class).in(Scopes.SINGLETON); bind(TokenValidationService.class); + bind(SSOAuthenticationSessionManager.class).in(Scopes.SINGLETON); bind(Environment.class).toInstance(environment); } diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/RequestDetails.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/RequestDetails.java index eecebbf512..54c8378ba8 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/net/RequestDetails.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/RequestDetails.java @@ -78,6 +78,11 @@ public class RequestDetails { */ private final List cookies; + /** + * The request URI of the associated request + */ + private final String requestURI; + /** * Returns an unmodifiable Map of all HTTP headers within the given request. * If there are no such headers, the returned Map will be empty. As there @@ -193,6 +198,7 @@ public RequestDetails(HttpServletRequest request) { this.parameters = getParameters(request); this.remoteAddress = request.getRemoteAddr(); this.remoteHostname = request.getRemoteHost(); + this.requestURI = request.getRequestURI(); this.session = request.getSession(false); } @@ -209,6 +215,7 @@ public RequestDetails(RequestDetails requestDetails) { this.parameters = requestDetails.getParameters(); this.remoteAddress = requestDetails.getRemoteAddress(); this.remoteHostname = requestDetails.getRemoteHostname(); + this.requestURI = requestDetails.getRequestURI(); this.session = requestDetails.getSession(); } @@ -412,4 +419,14 @@ public String getRemoteHostname() { return remoteHostname; } + /** + * Returns the request URI used by the client that sent the associated request + * + * @return + * The original request URI + */ + public String getRequestURI() { + return requestURI; + } + } diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/AbstractAuthenticatedUser.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/AbstractAuthenticatedUser.java index 10107c2f5f..51a4e29a57 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/AbstractAuthenticatedUser.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/AbstractAuthenticatedUser.java @@ -31,6 +31,12 @@ public abstract class AbstractAuthenticatedUser extends AbstractIdentifiable implements AuthenticatedUser { + /** + * The URI that was originally used to the first call to the authentication + * providers authenticateUser method. + */ + private String originalUri; + /** * Creates a new AbstractAuthenticatedUser that considers usernames to be * case-sensitive or case-insensitive based on the provided case sensitivity @@ -80,4 +86,14 @@ public void invalidate() { // Nothing to invalidate } + @Override + public void setOriginalUri(String originalUri) { + this.originalUri = originalUri; + } + + @Override + public String getOriginalUri() { + return this.originalUri; + } + } diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/AuthenticatedUser.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/AuthenticatedUser.java index a799937e85..6c3c629fc1 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/AuthenticatedUser.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/AuthenticatedUser.java @@ -75,4 +75,14 @@ public interface AuthenticatedUser extends Identifiable { */ void invalidate(); + /** + * A function to set the originalUri value + */ + public void setOriginalUri(String originalUri); + + /** + * A function to get the originalUri value + */ + public String getOriginalUri(); + } diff --git a/guacamole/src/main/frontend/src/app/auth/service/authenticationService.js b/guacamole/src/main/frontend/src/app/auth/service/authenticationService.js index af5a9ab6c3..27d6c28137 100644 --- a/guacamole/src/main/frontend/src/app/auth/service/authenticationService.js +++ b/guacamole/src/main/frontend/src/app/auth/service/authenticationService.js @@ -72,6 +72,7 @@ angular.module('auth').factory('authenticationService', ['$injector', var $rootScope = $injector.get('$rootScope'); var localStorageService = $injector.get('localStorageService'); var requestService = $injector.get('requestService'); + var $window = $injector.get('$window'); var service = {}; @@ -203,6 +204,10 @@ angular.module('auth').factory('authenticationService', ['$injector', // the stack when fed to $.param(). requestParams = _.omitBy(requestParams, (value, key) => key.startsWith('$')); + // Add a parameter "href" so that the java authentication provider + // knows where we came from + requestParams.href = $window.location.href; + return requestService({ method: 'POST', url: 'api/tokens', @@ -229,13 +234,14 @@ angular.module('auth').factory('authenticationService', ['$injector', // Notify of login and new token setAuthenticationResult(new AuthenticationResult(data)); $rootScope.$broadcast('guacLogin', data.authToken); - } // Update cached authentication result, even if the token remains // the same - else + else { + delete data.redirection setAuthenticationResult(new AuthenticationResult(data)); + } // Authentication was successful return data; diff --git a/guacamole/src/main/frontend/src/app/index/config/indexRouteConfig.js b/guacamole/src/main/frontend/src/app/index/config/indexRouteConfig.js index d24df9179c..40ea315211 100644 --- a/guacamole/src/main/frontend/src/app/index/config/indexRouteConfig.js +++ b/guacamole/src/main/frontend/src/app/index/config/indexRouteConfig.js @@ -79,10 +79,10 @@ angular.module('index').config(['$routeProvider', '$locationProvider', // Re-authenticate including any parameters in URL $injector.invoke(updateCurrentToken) - .then(function tokenUpdateComplete() { + .then(function tokenUpdateComplete(data) { // Redirect to home page - userPageService.getHomePage() + userPageService.getHomePage(data.redirection) .then(function homePageRetrieved(homePage) { // If home page is the requested location, allow through diff --git a/guacamole/src/main/frontend/src/app/index/controllers/indexController.js b/guacamole/src/main/frontend/src/app/index/controllers/indexController.js index fb30052d4d..3df61edd24 100644 --- a/guacamole/src/main/frontend/src/app/index/controllers/indexController.js +++ b/guacamole/src/main/frontend/src/app/index/controllers/indexController.js @@ -328,13 +328,7 @@ angular.module('index').controller('indexController', ['$scope', '$injector', $scope.reAuthenticate = function reAuthenticate() { $scope.reAuthenticating = true; - - // Clear out URL state to conveniently bring user back to home screen - // upon relogin - if ($location.path() !== '/') - $location.url('/'); - else - $route.reload(); + $route.reload(); }; diff --git a/guacamole/src/main/frontend/src/app/navigation/services/userPageService.js b/guacamole/src/main/frontend/src/app/navigation/services/userPageService.js index 9a9f693f6d..3314ab1214 100644 --- a/guacamole/src/main/frontend/src/app/navigation/services/userPageService.js +++ b/guacamole/src/main/frontend/src/app/navigation/services/userPageService.js @@ -61,14 +61,31 @@ angular.module('navigation').factory('userPageService', ['$injector', * @param {Object.} permissions * A map of all permissions granted to the current user, where each * key is the identifier of the corresponding data source. + * + * @param {String} redirection + * A url of a proposed redirection supplied by the server * * @returns {PageDefinition} * The user's home page. */ - var generateHomePage = function generateHomePage(rootGroups, permissions) { + var generateHomePage = function generateHomePage(rootGroups, permissions, redirection = null) { var settingsPages = generateSettingsPages(permissions); + // On initial login allow redirection back to requested page + // If the user doesn't have the permission for this page they + // will be directed back to SYSTEM_HOME_PAGE or a missing + // connection error thrown + var redirectionPage = null; + if (redirection) { + const url = new URL(redirection); + const p = url.hash.split('?')[0].substring(1); + // Only respect the hash path without the search parameters of + // the redirection to avoid spoofing attacks + if (SYSTEM_HOME_PAGE.url != p) + redirectionPage = new PageDefinition({name : 'USER_MENU.ACTION_NAVIGATE_HOME', url: p}); + } + // If user has access to settings pages, return home page and skip // evaluation for automatic connections. The Preferences page is // a Settings page and is always visible, and the Session management @@ -76,12 +93,13 @@ angular.module('navigation').factory('userPageService', ['$injector', // own session. We look for more than those two pages to determine // if we should go to the home page. if (settingsPages.length > 2) - return SYSTEM_HOME_PAGE; + return redirectionPage ? redirectionPage : SYSTEM_HOME_PAGE; // If exactly one connection or balancing group is available, use // that as the home page var clientPages = service.getClientPages(rootGroups); - return (clientPages.length === 1) ? clientPages[0] : SYSTEM_HOME_PAGE; + return (clientPages.length === 1) ? clientPages[0] : + redirectionPage ? redirectionPage : SYSTEM_HOME_PAGE; }; @@ -165,11 +183,14 @@ angular.module('navigation').factory('userPageService', ['$injector', /** * Returns a promise which resolves with an appropriate home page for the * current user. The promise will not be rejected. + * + * @param redirection + * A url of a proposed redirection supplied by the server * * @returns {Promise.} * A promise which resolves with the user's default home page. */ - service.getHomePage = function getHomePage() { + service.getHomePage = function getHomePage(redirection = null) { var deferred = $q.defer(); @@ -190,7 +211,7 @@ angular.module('navigation').factory('userPageService', ['$injector', permissionsSets : getPermissionSets }) .then(function rootConnectionGroupsPermissionsRetrieved(data) { - deferred.resolve(generateHomePage(data.rootGroups,data.permissionsSets)); + deferred.resolve(generateHomePage(data.rootGroups,data.permissionsSets, redirection)); }, requestService.DIE); return deferred.promise; diff --git a/guacamole/src/main/java/org/apache/guacamole/GuacamoleSession.java b/guacamole/src/main/java/org/apache/guacamole/GuacamoleSession.java index 50ee2a13dd..d0c4a662a8 100644 --- a/guacamole/src/main/java/org/apache/guacamole/GuacamoleSession.java +++ b/guacamole/src/main/java/org/apache/guacamole/GuacamoleSession.java @@ -66,6 +66,11 @@ public class GuacamoleSession { */ private final ListenerService listenerService; + /** + * If the session has just authenticated, allow a redirection to be applied + */ + private String redirection; + /** * The last time this session was accessed. */ @@ -96,6 +101,7 @@ public GuacamoleSession(ListenerService listenerService, this.listenerService = listenerService; this.authenticatedUser = authenticatedUser; this.userContexts = userContexts; + this.redirection = null; } /** @@ -123,6 +129,32 @@ public void setAuthenticatedUser(AuthenticatedUser authenticatedUser) { this.authenticatedUser = authenticatedUser; } + /** + * Returns a redirection path for newly authenticated users, allowing + * redirection to where they left off when an authentication was + * requested. + * + * @return + * A String with the redirection path + */ + public String getRedirection() { + this.access(); + return this.redirection; + } + + /** + * Returns a redirection path for newly authenticated users, allowing + * redirection to where they left off when an authentication was + * requested. + * + * @return + * A String with the redirection path + */ + public void setRedirection(String redirection) { + this.access(); + this.redirection = redirection; + } + /** * Returns a list of all UserContexts associated with this session. Each * AuthenticationProvider currently loaded by Guacamole may provide its own 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..92683f977e 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,11 @@ public class APIAuthenticationResult { */ private final List availableDataSources; + /** + * If the session has just authenticated, allow a redirection to be applied + */ + private final String redirection; + /** * Returns the unique authentication token which identifies the current * session. @@ -98,6 +103,18 @@ public List getAvailableDataSources() { return availableDataSources; } + /** + * Returns a redirection path for newly authenticated users, allowing + * redirection to where they left off when an authentication was + * requested. + * + * @return + * A String with the redirection path + */ + public String getRedirection() { + return redirection; + } + /** * Create a new APIAuthenticationResult object containing the given data. * @@ -115,13 +132,18 @@ public List getAvailableDataSources() { * @param availableDataSources * The unique identifier of all AuthenticationProviders to which the * user now has access. + * + * @param redirection + * A redirection proposed for the newly created sessions */ public APIAuthenticationResult(String authToken, String username, - String dataSource, List availableDataSources) { + String dataSource, List availableDataSources, + String redirection) { this.authToken = authToken; this.username = username; this.dataSource = dataSource; this.availableDataSources = Collections.unmodifiableList(availableDataSources); + this.redirection = redirection; } } diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/auth/AuthenticationService.java b/guacamole/src/main/java/org/apache/guacamole/rest/auth/AuthenticationService.java index 7037bb7894..de31f4448e 100644 --- a/guacamole/src/main/java/org/apache/guacamole/rest/auth/AuthenticationService.java +++ b/guacamole/src/main/java/org/apache/guacamole/rest/auth/AuthenticationService.java @@ -401,7 +401,18 @@ public String authenticate(Credentials credentials, String token) // If no existing session, generate a new token/session pair else { authToken = authTokenGenerator.getToken(); - tokenSessionMap.put(authToken, new GuacamoleSession(listenerService, authenticatedUser, userContexts)); + GuacamoleSession newSession = new GuacamoleSession(listenerService, authenticatedUser, userContexts); + + // Get the redirection if authenication provider updated it + String redirection = authenticatedUser.getOriginalUri(); + if (redirection == null || redirection.isEmpty()) { + // The authentication provider didn't update the redirection + redirection = credentials.getParameter("href"); + } + if (redirection != null && ! redirection.isEmpty()) { + newSession.setRedirection(redirection); + } + tokenSessionMap.put(authToken, newSession); } // Report authentication success 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..5e4d00281f 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 @@ -190,7 +190,8 @@ public APIAuthenticationResult createToken(@FormParam("username") String usernam token, authenticatedUser.getIdentifier(), authenticatedUser.getAuthenticationProvider().getIdentifier(), - authProviderIdentifiers + authProviderIdentifiers, + session.getRedirection() ); }