Merge patch branch changes to main.

This commit is contained in:
Virtually Nick
2025-04-14 22:21:07 -04:00
17 changed files with 697 additions and 213 deletions

View File

@@ -21,7 +21,6 @@ package org.apache.guacamole.auth.duo;
import com.google.inject.Guice; import com.google.inject.Guice;
import com.google.inject.Injector; import com.google.inject.Injector;
import javax.servlet.http.HttpServletRequest;
import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.net.auth.AbstractAuthenticationProvider; import org.apache.guacamole.net.auth.AbstractAuthenticationProvider;
import org.apache.guacamole.net.auth.AuthenticatedUser; import org.apache.guacamole.net.auth.AuthenticatedUser;
@@ -86,8 +85,7 @@ public class DuoAuthenticationProvider extends AbstractAuthenticationProvider {
// Ignore requests with no corresponding authentication session ID, as // Ignore requests with no corresponding authentication session ID, as
// there are no credentials to reconstitute if the user has not yet // there are no credentials to reconstitute if the user has not yet
// attempted to authenticate // attempted to authenticate
HttpServletRequest request = credentials.getRequest(); String duoState = credentials.getParameter(UserVerificationService.DUO_STATE_PARAMETER_NAME);
String duoState = request.getParameter(UserVerificationService.DUO_STATE_PARAMETER_NAME);
if (duoState == null) if (duoState == null)
return credentials; return credentials;
@@ -99,7 +97,7 @@ public class DuoAuthenticationProvider extends AbstractAuthenticationProvider {
// Reconstitute the originally-provided credentials from the users // Reconstitute the originally-provided credentials from the users
// authentication attempt prior to being redirected to Duo // authentication attempt prior to being redirected to Duo
Credentials previousCredentials = session.getCredentials(); Credentials previousCredentials = session.getCredentials();
previousCredentials.setRequest(request); previousCredentials.setRequestDetails(credentials.getRequestDetails());
return previousCredentials; return previousCredentials;
} }

View File

@@ -30,7 +30,6 @@ import java.net.URISyntaxException;
import java.util.Collections; import java.util.Collections;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.List; import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleServerException; import org.apache.guacamole.GuacamoleServerException;
import org.apache.guacamole.auth.duo.conf.ConfigurationService; import org.apache.guacamole.auth.duo.conf.ConfigurationService;
@@ -113,8 +112,7 @@ public class UserVerificationService {
// Pull the original HTTP request used to authenticate // Pull the original HTTP request used to authenticate
Credentials credentials = authenticatedUser.getCredentials(); Credentials credentials = authenticatedUser.getCredentials();
HttpServletRequest request = credentials.getRequest(); IPAddress clientAddr = new IPAddressString(credentials.getRemoteAddress()).getAddress();
IPAddress clientAddr = new IPAddressString(request.getRemoteAddr()).getAddress();
// Ignore anonymous users // Ignore anonymous users
String username = authenticatedUser.getIdentifier(); String username = authenticatedUser.getIdentifier();
@@ -176,8 +174,8 @@ public class UserVerificationService {
// Retrieve signed Duo authentication code and session state from the // Retrieve signed Duo authentication code and session state from the
// request (these will be absent if this is an initial authentication // request (these will be absent if this is an initial authentication
// attempt and not a redirect back from Duo) // attempt and not a redirect back from Duo)
String duoCode = request.getParameter(DUO_CODE_PARAMETER_NAME); String duoCode = credentials.getParameter(DUO_CODE_PARAMETER_NAME);
String duoState = request.getParameter(DUO_STATE_PARAMETER_NAME); String duoState = credentials.getParameter(DUO_STATE_PARAMETER_NAME);
// Redirect to Duo to obtain an authentication code if that redirect // Redirect to Duo to obtain an authentication code if that redirect
// has not yet occurred // has not yet occurred

View File

@@ -21,13 +21,11 @@ package org.apache.guacamole.auth.header;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.Provider; import com.google.inject.Provider;
import javax.servlet.http.HttpServletRequest;
import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.net.auth.Credentials; import org.apache.guacamole.net.auth.Credentials;
import org.apache.guacamole.net.auth.credentials.CredentialsInfo; import org.apache.guacamole.net.auth.credentials.CredentialsInfo;
import org.apache.guacamole.net.auth.credentials.GuacamoleInvalidCredentialsException; import org.apache.guacamole.net.auth.credentials.GuacamoleInvalidCredentialsException;
import org.apache.guacamole.auth.header.user.AuthenticatedUser; import org.apache.guacamole.auth.header.user.AuthenticatedUser;
import java.security.Principal;
/** /**
* Service providing convenience functions for the HTTP Header * Service providing convenience functions for the HTTP Header
@@ -65,19 +63,12 @@ public class AuthenticationProviderService {
public AuthenticatedUser authenticateUser(Credentials credentials) public AuthenticatedUser authenticateUser(Credentials credentials)
throws GuacamoleException { throws GuacamoleException {
// Pull HTTP header from request if present // Get the username from the header configured in guacamole.properties
HttpServletRequest request = credentials.getRequest(); String username = credentials.getHeader(confService.getHttpAuthHeader());
if (request != null) { if (username != null) {
AuthenticatedUser authenticatedUser = authenticatedUserProvider.get();
// Get the username from the header configured in guacamole.properties authenticatedUser.init(username, credentials);
String username = request.getHeader(confService.getHttpAuthHeader()); return authenticatedUser;
if (username != null) {
AuthenticatedUser authenticatedUser = authenticatedUserProvider.get();
authenticatedUser.init(username, credentials);
return authenticatedUser;
}
} }
// Authentication not provided via header, yet, so we request it. // Authentication not provided via header, yet, so we request it.

View File

@@ -22,7 +22,6 @@ package org.apache.guacamole.auth.jdbc.sharing;
import com.google.inject.Inject; import com.google.inject.Inject;
import java.util.Collections; import java.util.Collections;
import java.util.Map; import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.apache.guacamole.auth.jdbc.user.ModeledAuthenticatedUser; import org.apache.guacamole.auth.jdbc.user.ModeledAuthenticatedUser;
import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleSecurityException; import org.apache.guacamole.GuacamoleSecurityException;
@@ -175,13 +174,8 @@ public class ConnectionSharingService {
*/ */
public String getShareKey(Credentials credentials) { public String getShareKey(Credentials credentials) {
// Pull associated HTTP request
HttpServletRequest request = credentials.getRequest();
if (request == null)
return null;
// Retrieve the share key from the request // Retrieve the share key from the request
return request.getParameter(SHARE_KEY_NAME); return credentials.getParameter(SHARE_KEY_NAME);
} }

View File

@@ -26,7 +26,6 @@ import java.util.Arrays;
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 javax.servlet.http.HttpServletRequest;
import org.apache.guacamole.GuacamoleClientException; import org.apache.guacamole.GuacamoleClientException;
import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleUnsupportedException; import org.apache.guacamole.GuacamoleUnsupportedException;
@@ -522,9 +521,8 @@ public class UserService extends ModeledDirectoryObjectService<ModeledUser, User
String username = user.getIdentifier(); String username = user.getIdentifier();
// Pull new password from HTTP request // Pull new password from HTTP request
HttpServletRequest request = credentials.getRequest(); String newPassword = credentials.getParameter(NEW_PASSWORD_PARAMETER);
String newPassword = request.getParameter(NEW_PASSWORD_PARAMETER); String confirmNewPassword = credentials.getParameter(CONFIRM_NEW_PASSWORD_PARAMETER);
String confirmNewPassword = request.getParameter(CONFIRM_NEW_PASSWORD_PARAMETER);
// Require new password if account is expired // Require new password if account is expired
if (newPassword == null || confirmNewPassword == null) { if (newPassword == null || confirmNewPassword == null) {

View File

@@ -21,10 +21,9 @@ package org.apache.guacamole.auth.json;
import com.google.inject.Inject; import com.google.inject.Inject;
import inet.ipaddr.IPAddressString; import inet.ipaddr.IPAddressString;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import javax.servlet.http.HttpServletRequest;
import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.net.auth.Credentials;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -59,17 +58,17 @@ public class RequestValidationService {
} }
/** /**
* Returns whether the given request can be used for authentication, taking * Returns whether the given credentials can be used for authentication,
* into account restrictions specified within guacamole.properties. * taking into account restrictions specified within guacamole.properties.
* *
* @param request * @param credentials
* The HTTP request to test. * The credentials of the authentication request to test.
* *
* @return * @return
* true if the given request comes from a trusted source and can be * true if the given credentials come from a trusted source and can be
* used for authentication, false otherwise. * used for authentication, false otherwise.
*/ */
public boolean isAuthenticationAllowed(HttpServletRequest request) { public boolean isAuthenticationAllowed(Credentials credentials) {
// Pull list of all trusted networks // Pull list of all trusted networks
Collection<String> trustedNetworks; Collection<String> trustedNetworks;
@@ -79,14 +78,14 @@ public class RequestValidationService {
// Deny all requests if restrictions cannot be parsed // Deny all requests if restrictions cannot be parsed
catch (GuacamoleException e) { catch (GuacamoleException e) {
logger.warn("Authentication request from \"{}\" is DENIED due to parse error: {}", request.getRemoteAddr(), e.getMessage()); logger.warn("Authentication request from \"{}\" is DENIED due to parse error: {}", credentials.getRemoteAddress(), e.getMessage());
logger.debug("Error parsing authentication request restrictions from guacamole.properties.", e); logger.debug("Error parsing authentication request restrictions from guacamole.properties.", e);
return false; return false;
} }
// All requests are allowed if no restrictions are defined // All requests are allowed if no restrictions are defined
if (trustedNetworks.isEmpty()) { if (trustedNetworks.isEmpty()) {
logger.debug("Authentication request from \"{}\" is ALLOWED (no restrictions).", request.getRemoteAddr()); logger.debug("Authentication request from \"{}\" is ALLOWED (no restrictions).", credentials.getRemoteAddress());
return true; return true;
} }
@@ -94,15 +93,15 @@ public class RequestValidationService {
for (String network : trustedNetworks) { for (String network : trustedNetworks) {
// Request is allowed if any subnet matches // Request is allowed if any subnet matches
if (new IPAddressString(network).contains(new IPAddressString(request.getRemoteAddr()))) { if (new IPAddressString(network).contains(new IPAddressString(credentials.getRemoteAddress()))) {
logger.debug("Authentication request from \"{}\" is ALLOWED (matched subnet).", request.getRemoteAddr()); logger.debug("Authentication request from \"{}\" is ALLOWED (matched subnet).", credentials.getRemoteAddress());
return true; return true;
} }
} }
// Otherwise request is denied - no subnets matched // Otherwise request is denied - no subnets matched
logger.debug("Authentication request from \"{}\" is DENIED (did not match subnet).", request.getRemoteAddr()); logger.debug("Authentication request from \"{}\" is DENIED (did not match subnet).", credentials.getRemoteAddress());
return false; return false;
} }

View File

@@ -31,7 +31,6 @@ import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.auth.json.ConfigurationService; import org.apache.guacamole.auth.json.ConfigurationService;
import org.apache.guacamole.auth.json.CryptoService; import org.apache.guacamole.auth.json.CryptoService;
@@ -121,18 +120,13 @@ public class UserDataService {
String json; String json;
byte[] correctSignature; byte[] correctSignature;
// Pull HTTP request, if available
HttpServletRequest request = credentials.getRequest();
if (request == null)
return null;
// Abort if the request itself is not allowed // Abort if the request itself is not allowed
if (!requestService.isAuthenticationAllowed(request)) if (!requestService.isAuthenticationAllowed(credentials))
return null; return null;
// Pull base64-encoded, encrypted JSON data from HTTP request, if any // Pull base64-encoded, encrypted JSON data from HTTP request, if any
// such data is present // such data is present
String base64 = request.getParameter(ENCRYPTED_DATA_PARAMETER); String base64 = credentials.getParameter(ENCRYPTED_DATA_PARAMETER);
if (base64 == null) if (base64 == null)
return null; return null;

View File

@@ -33,6 +33,7 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSession;
import javax.servlet.ServletInputStream; import javax.servlet.ServletInputStream;
import javax.servlet.RequestDispatcher; import javax.servlet.RequestDispatcher;
import org.apache.guacamole.net.auth.Credentials;
import org.junit.Test; import org.junit.Test;
import static org.junit.Assert.*; import static org.junit.Assert.*;
@@ -375,13 +376,13 @@ public class RequestValidationServiceTest {
requestService = new RequestValidationService(new MockConfigurationService(null)); requestService = new RequestValidationService(new MockConfigurationService(null));
try { try {
assertTrue(requestService.isAuthenticationAllowed(mockHttpServletRequest("1.1.1.1"))); assertTrue(requestService.isAuthenticationAllowed(new Credentials(null, null, mockHttpServletRequest("1.1.1.1"))));
assertTrue(requestService.isAuthenticationAllowed(mockHttpServletRequest("10.10.10.10"))); assertTrue(requestService.isAuthenticationAllowed(new Credentials(null, null, mockHttpServletRequest("10.10.10.10"))));
assertTrue(requestService.isAuthenticationAllowed(mockHttpServletRequest("100.100.100.100"))); assertTrue(requestService.isAuthenticationAllowed(new Credentials(null, null, mockHttpServletRequest("100.100.100.100"))));
assertTrue(requestService.isAuthenticationAllowed(mockHttpServletRequest("1:1:1:1:1:1:1:1"))); assertTrue(requestService.isAuthenticationAllowed(new Credentials(null, null, mockHttpServletRequest("1:1:1:1:1:1:1:1"))));
assertTrue(requestService.isAuthenticationAllowed(mockHttpServletRequest("10:10:10:10:10:10:10:10"))); assertTrue(requestService.isAuthenticationAllowed(new Credentials(null, null, mockHttpServletRequest("10:10:10:10:10:10:10:10"))));
assertTrue(requestService.isAuthenticationAllowed(mockHttpServletRequest("100:100:100:100:100:100:100:100"))); assertTrue(requestService.isAuthenticationAllowed(new Credentials(null, null, mockHttpServletRequest("100:100:100:100:100:100:100:100"))));
assertTrue(requestService.isAuthenticationAllowed(mockHttpServletRequest("1000:1000:1000:1000:1000:1000:1000:1000"))); assertTrue(requestService.isAuthenticationAllowed(new Credentials(null, null, mockHttpServletRequest("1000:1000:1000:1000:1000:1000:1000:1000"))));
} }
catch (AssertionError e) { catch (AssertionError e) {
fail("A network was denied to authenticate even though no trusted networks were specified."); fail("A network was denied to authenticate even though no trusted networks were specified.");
@@ -399,18 +400,18 @@ public class RequestValidationServiceTest {
requestService = new RequestValidationService(new MockConfigurationService("10.0.0.0/8,127.0.0.0/8,172.16.0.0/12,192.168.0.0/16,1.2.3.4/32,::1/128,fc00::/7")); requestService = new RequestValidationService(new MockConfigurationService("10.0.0.0/8,127.0.0.0/8,172.16.0.0/12,192.168.0.0/16,1.2.3.4/32,::1/128,fc00::/7"));
try { try {
assertTrue(requestService.isAuthenticationAllowed(mockHttpServletRequest("10.0.0.0"))); assertTrue(requestService.isAuthenticationAllowed(new Credentials(null, null, mockHttpServletRequest("10.0.0.0"))));
assertTrue(requestService.isAuthenticationAllowed(mockHttpServletRequest("10.255.255.255"))); assertTrue(requestService.isAuthenticationAllowed(new Credentials(null, null, mockHttpServletRequest("10.255.255.255"))));
assertTrue(requestService.isAuthenticationAllowed(mockHttpServletRequest("127.0.0.0"))); assertTrue(requestService.isAuthenticationAllowed(new Credentials(null, null, mockHttpServletRequest("127.0.0.0"))));
assertTrue(requestService.isAuthenticationAllowed(mockHttpServletRequest("127.255.255.255"))); assertTrue(requestService.isAuthenticationAllowed(new Credentials(null, null, mockHttpServletRequest("127.255.255.255"))));
assertTrue(requestService.isAuthenticationAllowed(mockHttpServletRequest("172.16.0.0"))); assertTrue(requestService.isAuthenticationAllowed(new Credentials(null, null, mockHttpServletRequest("172.16.0.0"))));
assertTrue(requestService.isAuthenticationAllowed(mockHttpServletRequest("172.31.255.255"))); assertTrue(requestService.isAuthenticationAllowed(new Credentials(null, null, mockHttpServletRequest("172.31.255.255"))));
assertTrue(requestService.isAuthenticationAllowed(mockHttpServletRequest("192.168.0.0"))); assertTrue(requestService.isAuthenticationAllowed(new Credentials(null, null, mockHttpServletRequest("192.168.0.0"))));
assertTrue(requestService.isAuthenticationAllowed(mockHttpServletRequest("192.168.255.255"))); assertTrue(requestService.isAuthenticationAllowed(new Credentials(null, null, mockHttpServletRequest("192.168.255.255"))));
assertTrue(requestService.isAuthenticationAllowed(mockHttpServletRequest("1.2.3.4"))); assertTrue(requestService.isAuthenticationAllowed(new Credentials(null, null, mockHttpServletRequest("1.2.3.4"))));
assertTrue(requestService.isAuthenticationAllowed(mockHttpServletRequest("::1"))); assertTrue(requestService.isAuthenticationAllowed(new Credentials(null, null, mockHttpServletRequest("::1"))));
assertTrue(requestService.isAuthenticationAllowed(mockHttpServletRequest("fc00::"))); assertTrue(requestService.isAuthenticationAllowed(new Credentials(null, null, mockHttpServletRequest("fc00::"))));
assertTrue(requestService.isAuthenticationAllowed(mockHttpServletRequest("fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"))); assertTrue(requestService.isAuthenticationAllowed(new Credentials(null, null, mockHttpServletRequest("fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"))));
} }
catch (AssertionError e) { catch (AssertionError e) {
fail("A trusted network was denied to authenticate."); fail("A trusted network was denied to authenticate.");
@@ -428,20 +429,20 @@ public class RequestValidationServiceTest {
requestService = new RequestValidationService(new MockConfigurationService("10.0.0.0/8,127.0.0.0/8,172.16.0.0/12,192.168.0.0/16,1.2.3.4/32,::1/128,fc00::/7")); requestService = new RequestValidationService(new MockConfigurationService("10.0.0.0/8,127.0.0.0/8,172.16.0.0/12,192.168.0.0/16,1.2.3.4/32,::1/128,fc00::/7"));
try { try {
assertFalse(requestService.isAuthenticationAllowed(mockHttpServletRequest("9.255.255.255"))); assertFalse(requestService.isAuthenticationAllowed(new Credentials(null, null, mockHttpServletRequest("9.255.255.255"))));
assertFalse(requestService.isAuthenticationAllowed(mockHttpServletRequest("11.0.0.0"))); assertFalse(requestService.isAuthenticationAllowed(new Credentials(null, null, mockHttpServletRequest("11.0.0.0"))));
assertFalse(requestService.isAuthenticationAllowed(mockHttpServletRequest("126.255.255.255"))); assertFalse(requestService.isAuthenticationAllowed(new Credentials(null, null, mockHttpServletRequest("126.255.255.255"))));
assertFalse(requestService.isAuthenticationAllowed(mockHttpServletRequest("128.0.0.0"))); assertFalse(requestService.isAuthenticationAllowed(new Credentials(null, null, mockHttpServletRequest("128.0.0.0"))));
assertFalse(requestService.isAuthenticationAllowed(mockHttpServletRequest("172.15.255.255"))); assertFalse(requestService.isAuthenticationAllowed(new Credentials(null, null, mockHttpServletRequest("172.15.255.255"))));
assertFalse(requestService.isAuthenticationAllowed(mockHttpServletRequest("172.32.0.0"))); assertFalse(requestService.isAuthenticationAllowed(new Credentials(null, null, mockHttpServletRequest("172.32.0.0"))));
assertFalse(requestService.isAuthenticationAllowed(mockHttpServletRequest("192.167.255.255"))); assertFalse(requestService.isAuthenticationAllowed(new Credentials(null, null, mockHttpServletRequest("192.167.255.255"))));
assertFalse(requestService.isAuthenticationAllowed(mockHttpServletRequest("192.169.0.0"))); assertFalse(requestService.isAuthenticationAllowed(new Credentials(null, null, mockHttpServletRequest("192.169.0.0"))));
assertFalse(requestService.isAuthenticationAllowed(mockHttpServletRequest("1.2.3.3"))); assertFalse(requestService.isAuthenticationAllowed(new Credentials(null, null, mockHttpServletRequest("1.2.3.3"))));
assertFalse(requestService.isAuthenticationAllowed(mockHttpServletRequest("1.2.3.5"))); assertFalse(requestService.isAuthenticationAllowed(new Credentials(null, null, mockHttpServletRequest("1.2.3.5"))));
assertFalse(requestService.isAuthenticationAllowed(mockHttpServletRequest("::0"))); assertFalse(requestService.isAuthenticationAllowed(new Credentials(null, null, mockHttpServletRequest("::0"))));
assertFalse(requestService.isAuthenticationAllowed(mockHttpServletRequest("::2"))); assertFalse(requestService.isAuthenticationAllowed(new Credentials(null, null, mockHttpServletRequest("::2"))));
assertFalse(requestService.isAuthenticationAllowed(mockHttpServletRequest("fbff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"))); assertFalse(requestService.isAuthenticationAllowed(new Credentials(null, null, mockHttpServletRequest("fbff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"))));
assertFalse(requestService.isAuthenticationAllowed(mockHttpServletRequest("fe00::"))); assertFalse(requestService.isAuthenticationAllowed(new Credentials(null, null, mockHttpServletRequest("fe00::"))));
} }
catch (AssertionError e) { catch (AssertionError e) {
fail("An untrusted network was allowed to authenticate."); fail("An untrusted network was allowed to authenticate.");

View File

@@ -23,7 +23,6 @@ import com.google.common.io.BaseEncoding;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.Provider; import com.google.inject.Provider;
import java.util.Arrays; import java.util.Arrays;
import javax.servlet.http.HttpServletRequest;
import org.apache.guacamole.auth.radius.user.AuthenticatedUser; import org.apache.guacamole.auth.radius.user.AuthenticatedUser;
import org.apache.guacamole.auth.radius.form.GuacamoleRadiusChallenge; import org.apache.guacamole.auth.radius.form.GuacamoleRadiusChallenge;
import org.apache.guacamole.auth.radius.form.RadiusStateField; import org.apache.guacamole.auth.radius.form.RadiusStateField;
@@ -148,8 +147,7 @@ public class AuthenticationProviderService {
return null; return null;
// Grab HTTP request object and a response to a challenge. // Grab HTTP request object and a response to a challenge.
HttpServletRequest request = credentials.getRequest(); String challengeResponse = credentials.getParameter(CHALLENGE_RESPONSE_PARAM);
String challengeResponse = request.getParameter(CHALLENGE_RESPONSE_PARAM);
// RadiusPacket object to store response from server. // RadiusPacket object to store response from server.
RadiusPacket radPack; RadiusPacket radPack;
@@ -173,7 +171,7 @@ public class AuthenticationProviderService {
// This is a response to a previous challenge, authenticate with that. // This is a response to a previous challenge, authenticate with that.
else { else {
try { try {
String stateString = request.getParameter(RadiusStateField.PARAMETER_NAME); String stateString = credentials.getParameter(RadiusStateField.PARAMETER_NAME);
if (stateString == null) { if (stateString == null) {
logger.warn("Expected state parameter was not present in challenge/response."); logger.warn("Expected state parameter was not present in challenge/response.");
throw new GuacamoleInvalidCredentialsException("Authentication error.", CredentialsInfo.USERNAME_PASSWORD); throw new GuacamoleInvalidCredentialsException("Authentication error.", CredentialsInfo.USERNAME_PASSWORD);

View File

@@ -23,7 +23,6 @@ import com.google.inject.Inject;
import com.google.inject.Singleton; import com.google.inject.Singleton;
import java.net.URI; import java.net.URI;
import java.util.Arrays; import java.util.Arrays;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.core.UriBuilder; import javax.ws.rs.core.UriBuilder;
import org.apache.guacamole.form.Field; import org.apache.guacamole.form.Field;
import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.GuacamoleException;
@@ -70,12 +69,9 @@ public class AuthenticationProviderService implements SSOAuthenticationProviderS
throws GuacamoleException { throws GuacamoleException {
// Pull CAS ticket from request if present // Pull CAS ticket from request if present
HttpServletRequest request = credentials.getRequest(); String ticket = credentials.getParameter(TICKET_PARAMETER_NAME);
if (request != null) { if (ticket != null) {
String ticket = request.getParameter(TICKET_PARAMETER_NAME); return ticketService.validateTicket(ticket, credentials);
if (ticket != null) {
return ticketService.validateTicket(ticket, credentials);
}
} }
// Request CAS ticket (will automatically redirect the user to the // Request CAS ticket (will automatically redirect the user to the

View File

@@ -27,7 +27,6 @@ import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.core.UriBuilder; import javax.ws.rs.core.UriBuilder;
import org.apache.guacamole.auth.openid.conf.ConfigurationService; import org.apache.guacamole.auth.openid.conf.ConfigurationService;
import org.apache.guacamole.auth.openid.token.TokenValidationService; import org.apache.guacamole.auth.openid.token.TokenValidationService;
@@ -88,16 +87,13 @@ public class AuthenticationProviderService implements SSOAuthenticationProviderS
Map<String,String> tokens = Collections.emptyMap(); Map<String,String> tokens = Collections.emptyMap();
// Validate OpenID token in request, if present, and derive username // Validate OpenID token in request, if present, and derive username
HttpServletRequest request = credentials.getRequest(); String token = credentials.getParameter(TOKEN_PARAMETER_NAME);
if (request != null) { if (token != null) {
String token = request.getParameter(TOKEN_PARAMETER_NAME); JwtClaims claims = tokenService.validateToken(token);
if (token != null) { if (claims != null) {
JwtClaims claims = tokenService.validateToken(token); username = tokenService.processUsername(claims);
if (claims != null) { groups = tokenService.processGroups(claims);
username = tokenService.processUsername(claims); tokens = tokenService.processAttributes(claims);
groups = tokenService.processGroups(claims);
tokens = tokenService.processAttributes(claims);
}
} }
} }

View File

@@ -24,7 +24,6 @@ import com.google.inject.Provider;
import com.google.inject.Singleton; import com.google.inject.Singleton;
import java.net.URI; import java.net.URI;
import java.util.Arrays; import java.util.Arrays;
import javax.servlet.http.HttpServletRequest;
import org.apache.guacamole.auth.saml.user.SAMLAuthenticatedUser; import org.apache.guacamole.auth.saml.user.SAMLAuthenticatedUser;
import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.auth.saml.acs.AssertedIdentity; import org.apache.guacamole.auth.saml.acs.AssertedIdentity;
@@ -85,21 +84,13 @@ public class AuthenticationProviderService implements SSOAuthenticationProviderS
// Return the session identifier from the request params, if set, or // Return the session identifier from the request params, if set, or
// null otherwise // null otherwise
return credentials != null && credentials.getRequest() != null return credentials != null ? credentials.getParameter(AUTH_SESSION_QUERY_PARAM) : null;
? credentials.getRequest().getParameter(AUTH_SESSION_QUERY_PARAM)
: null;
} }
@Override @Override
public SAMLAuthenticatedUser authenticateUser(Credentials credentials) public SAMLAuthenticatedUser authenticateUser(Credentials credentials)
throws GuacamoleException { throws GuacamoleException {
// No authentication can be attempted without a corresponding HTTP
// request
HttpServletRequest request = credentials.getRequest();
if (request == null)
return null;
// Use established SAML identity if already provided by the SAML IdP // Use established SAML identity if already provided by the SAML IdP
AssertedIdentity identity = sessionManager.getIdentity( AssertedIdentity identity = sessionManager.getIdentity(
getSessionIdentifier(credentials)); getSessionIdentifier(credentials));

View File

@@ -25,7 +25,6 @@ import com.google.inject.Singleton;
import java.net.URI; import java.net.URI;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import javax.servlet.http.HttpServletRequest;
import org.apache.guacamole.auth.ssl.conf.ConfigurationService; import org.apache.guacamole.auth.ssl.conf.ConfigurationService;
import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleResourceNotFoundException; import org.apache.guacamole.GuacamoleResourceNotFoundException;
@@ -89,9 +88,7 @@ public class AuthenticationProviderService implements SSOAuthenticationProviderS
// Return the session identifier from the request params, if set, or // Return the session identifier from the request params, if set, or
// null otherwise // null otherwise
return credentials != null && credentials.getRequest() != null return credentials != null ? credentials.getParameter(AUTH_SESSION_PARAMETER_NAME) : null;
? credentials.getRequest().getParameter(AUTH_SESSION_PARAMETER_NAME)
: null;
} }
/** /**
@@ -154,15 +151,9 @@ public class AuthenticationProviderService implements SSOAuthenticationProviderS
// was signed by the expected CA. // was signed by the expected CA.
// //
// We can't authenticate using SSL/TLS client auth unless there's an
// associated HTTP request
HttpServletRequest request = credentials.getRequest();
if (request == null)
return null;
// We MUST have the domain associated with the request to ensure we // We MUST have the domain associated with the request to ensure we
// always get fresh SSL sessions when validating client certificates // always get fresh SSL sessions when validating client certificates
String host = request.getHeader("Host"); String host = credentials.getHeader("Host");
if (host == null) if (host == null)
return null; return null;

View File

@@ -30,7 +30,6 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleSecurityException; import org.apache.guacamole.GuacamoleSecurityException;
import org.apache.guacamole.GuacamoleUnsupportedException; import org.apache.guacamole.GuacamoleUnsupportedException;
@@ -317,10 +316,9 @@ public class UserVerificationService {
// Pull the original HTTP request used to authenticate // Pull the original HTTP request used to authenticate
Credentials credentials = authenticatedUser.getCredentials(); Credentials credentials = authenticatedUser.getCredentials();
HttpServletRequest request = credentials.getRequest();
// Get the current client address // Get the current client address
IPAddress clientAddr = new IPAddressString(request.getRemoteAddr()).getAddress(); IPAddress clientAddr = new IPAddressString(credentials.getRemoteAddress()).getAddress();
// Ignore anonymous users // Ignore anonymous users
if (authenticatedUser.getIdentifier().equals(AuthenticatedUser.ANONYMOUS_IDENTIFIER)) if (authenticatedUser.getIdentifier().equals(AuthenticatedUser.ANONYMOUS_IDENTIFIER))
@@ -369,7 +367,7 @@ public class UserVerificationService {
return; return;
// Retrieve TOTP from request // Retrieve TOTP from request
String code = request.getParameter(AuthenticationCodeField.PARAMETER_NAME); String code = credentials.getParameter(AuthenticationCodeField.PARAMETER_NAME);
// If no TOTP provided, request one // If no TOTP provided, request one
if (code == null) { if (code == null) {

View File

@@ -0,0 +1,411 @@
/*
* 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.net;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
/**
* A copy of the details of an HTTP request. The values within this object can
* be accessed even when outside the scope of the specific request represented.
*/
public class RequestDetails {
/**
* The address of the client that sent the associated request.
*/
private final String remoteAddress;
/**
* The hostname or, if the hostname cannot be determined, the address of the
* client that sent the associated request.
*/
private final String remoteHostname;
/**
* The HttpSession associated with the request, if any.
*/
private final HttpSession session;
/**
* An unmodifiable Map of all HTTP headers included in the associated
* request. If there are no such headers, this Map will be empty. As there
* may be many values for each header, each value is stored as a separate
* entry in an unmodifiable List. The keys of this map (header names) are
* case-insensitive.
*/
private final Map<String, List<String>> headers;
/**
* An unmodifiable Map of all HTTP parameters included in the associated
* request. If there are no such parameters, this Map will be empty. As
* there may be many values for each parameter, each value is stored as a
* separate entry in an unmodifiable List. Unlike headers, the keys of this
* map (parameter names) are case-sensitive.
*/
private final Map<String, List<String>> parameters;
/**
* An unmodifiable list of all cookies associated with the request. If
* there are no cookies associated with the request, this List will be
* empty.
*/
private final List<Cookie> cookies;
/**
* 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
* may be many values for each header, each value is stored as a separate
* entry in an unmodifiable List. The keys of the returned map (header
* names) are case-insensitive.
*
* @param request
* The HTTP request to extract all headers from.
*
* @return
* An unmodifiable Map of all HTTP headers in the given request.
*/
private static Map<String, List<String>> getHeaders(HttpServletRequest request) {
@SuppressWarnings("unchecked") // getHeaderNames() is explicitly documented as returning a Enumeration<String>
Enumeration<String> names = (Enumeration<String>) request.getHeaderNames();
// Represent the total lack of headers as an empty map, not null
if (names == null)
return Collections.emptyMap();
// Headers are case-insensitive
Map<String, List<String>> headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
names.asIterator().forEachRemaining((String name) -> {
@SuppressWarnings("unchecked") // getHeaders() is explicitly documented as returning a Enumeration<String>
Enumeration<String> values = (Enumeration<String>) request.getHeaders(name);
if (values != null && values.hasMoreElements())
headers.put(name, Collections.unmodifiableList(Collections.list(values)));
});
return Collections.unmodifiableMap(headers);
}
/**
* Returns an unmodifiable Map of all HTTP parameters within the given
* request. If there are no such parameters, the returned Map will be empty.
* As there may be many values for each parameter, each value is stored as a
* separate entry in an unmodifiable List. Unlike headers, the keys of the
* returned map (parameter names) are case-sensitive.
*
* @param request
* The HTTP request to extract all parameters from.
*
* @return
* An unmodifiable Map of all HTTP parameters in the given request.
*/
private static Map<String, List<String>> getParameters(HttpServletRequest request) {
@SuppressWarnings("unchecked") // getParameterNames() is explicitly documented as returning a Enumeration<String>
Enumeration<String> names = (Enumeration<String>) request.getParameterNames();
// Represent the total lack of parameters as an empty map, not null
if (names == null)
return Collections.emptyMap();
// Unlike headers, parameters are case-sensitive
Map<String, List<String>> parameters = new HashMap<>();
names.asIterator().forEachRemaining((String name) -> {
String[] values = request.getParameterValues(name);
if (values != null && values.length != 0)
parameters.put(name, Collections.unmodifiableList(Arrays.asList(values)));
});
return Collections.unmodifiableMap(parameters);
}
/**
* Returns an unmodifiable List of all cookies in the given HTTP request.
*
* @param request
* The HTTP request to extract all cookies from.
*
* @return
* An unmodifiable List of all cookies in the given request.
*/
private static List<Cookie> getCookies(HttpServletRequest request) {
Cookie[] cookies = request.getCookies();
if (cookies == null || cookies.length == 0)
return Collections.emptyList();
return Collections.unmodifiableList(Arrays.asList(cookies));
}
/**
* Creates a new RequestDetails that copies the details of the given HTTP
* request. The provided request may safely go out of scope and be reused
* for future requests without rendering the content of this RequestDetails
* invalid or inaccessible.
* <p>
* Though an HttpSession will be retrieved from the given request if the
* HttpSession already exists on the request, no HttpSession will be created
* through invoking this constructor.
*
* @param request
* The HTTP request to copy the details of.
*/
public RequestDetails(HttpServletRequest request) {
this.cookies = getCookies(request);
this.headers = getHeaders(request);
this.parameters = getParameters(request);
this.remoteAddress = request.getRemoteAddr();
this.remoteHostname = request.getRemoteHost();
this.session = request.getSession(false);
}
/**
* Creates a new RequestDetails that copies the details of the given
* RequestDetails.
*
* @param requestDetails
* The RequestDetails to copy.
*/
public RequestDetails(RequestDetails requestDetails) {
this.cookies = requestDetails.getCookies();
this.headers = requestDetails.getHeaders();
this.parameters = requestDetails.getParameters();
this.remoteAddress = requestDetails.getRemoteAddress();
this.remoteHostname = requestDetails.getRemoteHostname();
this.session = requestDetails.getSession();
}
/**
* Returns the first value stored for the given key in the given Map. If no
* such key exists in the map, or no values are associated with the given
* key, null is returned.
*
* @param map
* A Map of keys to multiple values, where each set of multiple values
* is represented by a List.
*
* @param key
* The key to look up.
*
* @return
* The first value stored for the given key in the given Map, or null
* if there is no such value or no such key.
*/
private static String getFirstValue(Map<String, List<String>> map, String key) {
List<String> values = map.get(key);
if (values == null || values.isEmpty())
return null;
return values.get(0);
}
/**
* Returns the value of the HTTP header having the given name. Header names
* are case-insensitive. If no such header was present, null is returned. If
* the header had multiple values, the first value is returned.
*
* @param name
* The name of the header to retrieve. This name is case-insensitive.
*
* @return
* The first value of the HTTP header with the given name, or null if
* there is no such header.
*/
public String getHeader(String name) {
return getFirstValue(headers, name);
}
/**
* Returns an unmodifiable List of all values of the HTTP header having the
* given name. Header names are case-insensitive. If no such header was
* present, the returned List will be empty.
*
* @param name
* The name of the header to retrieve. This name is case-insensitive.
*
* @return
* An unmodifiable List of all values of the HTTP header with the
* given name, or an empty List if there is no such header.
*/
public List<String> getHeaders(String name) {
return headers.getOrDefault(name, Collections.emptyList());
}
/**
* Returns an unmodifiable Set of the names of all HTTP headers present on
* the request. If there are no headers, this set will be empty. Header
* names are case-insensitive, and the returned Set will perform lookups in
* a case-insensitive manner.
*
* @return
* An unmodifiable Set of the names of all HTTP headers present on the
* request.
*/
public Set<String> getHeaderNames() {
return headers.keySet();
}
/**
* Returns an unmodifiable Map of all values of all HTTP headers on the
* request, where each Map key is a header name. Each Map value is an
* unmodifiable List of all values provided for the associated header and
* will contain at least one value. Header names are case-insensitive, and
* the returned map will perform lookups in a case-insensitive manner.
*
* @return
* An unmodifiable Map of all values of all HTTP headers on the
* request.
*/
public Map<String, List<String>> getHeaders() {
return headers;
}
/**
* Returns the value of the HTTP parameter having the given name. Parameter
* names are case-sensitive. If no such parameter was present, null is
* returned. If the parameter had multiple values, the first value is
* returned.
*
* @param name
* The name of the parameter to retrieve. This name is case-sensitive.
*
* @return
* The first value of the HTTP parameter with the given name, or null
* if there is no such parameter.
*/
public String getParameter(String name) {
return getFirstValue(parameters, name);
}
/**
* Returns an unmodifiable List of all values of the HTTP parameter having
* the given name. Parameter names are case-sensitive. If no such parameter
* was present, the returned List will be empty.
*
* @param name
* The name of the parameter to retrieve. This name is case-sensitive.
*
* @return
* An unmodifiable List of all values of the HTTP parameter with the
* given name, or an empty List if there is no such parameter.
*/
public List<String> getParameters(String name) {
return parameters.getOrDefault(name, Collections.emptyList());
}
/**
* Returns an unmodifiable Set of the names of all HTTP parameters present
* on the request. If there are no parameters, this set will be empty.
* Parameter names are case-sensitive, and the returned Set will perform
* lookups in a case-sensitive manner.
*
* @return
* An unmodifiable Set of the names of all HTTP parameters present on
* the request.
*/
public Set<String> getParameterNames() {
return parameters.keySet();
}
/**
* Returns an unmodifiable Map of all values of all HTTP parameters on the
* request, where each Map key is a parameter name. Each Map value is an
* unmodifiable List of all values provided for the associated parameter and
* will contain at least one value. Parameter names are case-sensitive, and
* the returned map will perform lookups in a case-sensitive manner.
*
* @return
* An unmodifiable Map of all values of all HTTP parameters on the
* request.
*/
public Map<String, List<String>> getParameters() {
return parameters;
}
/**
* Returns an unmodifiable List of all cookies in the request. If no cookies
* are present, the returned List will be empty.
*
* @return
* An unmodifiable List of all cookies in the request, which may an
* empty List.
*/
public List<Cookie> getCookies() {
return cookies;
}
/**
* Returns the HttpSession associated with the request, if any.
* <p>
* <strong>NOTE: Guacamole itself does not use the HttpSession.</strong>
* The extension subsystem does not provide access to the session object
* used by Guacamole, which is considered internal. Access to an HttpSession
* is only of use if you have another application in place that
* <em>does</em> use HttpSession and needs to be considered.
*
* @return
* The HttpSession associated with the request, or null if there is
* no HttpSession.
*/
public HttpSession getSession() {
return session;
}
/**
* Returns the address of the client that sent the associated request.
*
* @return
* The address of the client that sent the request.
*/
public String getRemoteAddress() {
return remoteAddress;
}
/**
* Returns the hostname of the client that sent the associated request, if
* known. If the hostname of the client cannot be determined, the address
* will be returned instead.
*
* @return
* The hostname or address of the client that sent the request.
*/
public String getRemoteHostname() {
return remoteHostname;
}
}

View File

@@ -19,16 +19,15 @@
package org.apache.guacamole.net.auth; package org.apache.guacamole.net.auth;
import org.apache.guacamole.net.RequestDetails;
import java.io.Serializable; import java.io.Serializable;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSession;
/** /**
* Simple arbitrary set of credentials, including a username/password pair, * Simple arbitrary set of credentials, including a username/password pair and
* the HttpServletRequest associated with the request for authorization * a copy of the details of the HTTP request received for authentication.
* (if any) and the HttpSession associated with that request. * <p>
*
* This class is used along with AuthenticationProvider to provide arbitrary * This class is used along with AuthenticationProvider to provide arbitrary
* HTTP-based authentication for Guacamole. * HTTP-based authentication for Guacamole.
*/ */
@@ -50,17 +49,9 @@ public class Credentials implements Serializable {
private String password; private String password;
/** /**
* The address of the client end of the connection which provided these * The details of the HTTP request that provided these Credentials.
* credentials, if known.
*/ */
private String remoteAddress; private transient RequestDetails requestDetails;
/**
* The hostname or, if the hostname cannot be determined, the address of
* the client end of the connection which provided these credentials, if
* known.
*/
private String remoteHostname;
/** /**
* The HttpServletRequest carrying additional credentials, if any. * The HttpServletRequest carrying additional credentials, if any.
@@ -68,47 +59,51 @@ public class Credentials implements Serializable {
private transient HttpServletRequest request; private transient HttpServletRequest request;
/** /**
* The HttpSession carrying additional credentials, if any. * Creates a new Credentials object with the given username, password,
*/ * and HTTP request. The details of the request are copied for later
private transient HttpSession session; * reference and can be retrieved with {@link #getRequestDetails()}.
*
/**
* Construct a Credentials object with the given username, password,
* and HTTP request. The information is assigned to the various
* storage objects, and the remote hostname and address is parsed out
* of the request object.
*
* @param username * @param username
* The username that was provided for authentication. * The username that was provided for authentication.
* *
* @param password * @param password
* The password that was provided for authentication. * The password that was provided for authentication.
* *
* @param request * @param request
* The HTTP request associated with the authentication * The HTTP request associated with the authentication
* request. * request.
*/ */
public Credentials(String username, String password, HttpServletRequest request) { public Credentials(String username, String password, HttpServletRequest request) {
this(username, password, new RequestDetails(request));
this.request = request;
}
/**
* Creates a new Credentials object with the given username, password,
* and general HTTP request details.
*
* @param username
* The username that was provided for authentication.
*
* @param password
* The password that was provided for authentication.
*
* @param requestDetails
* The details of the HTTP request associated with the authentication
* request.
*/
public Credentials(String username, String password, RequestDetails requestDetails) {
this.username = username; this.username = username;
this.password = password; this.password = password;
this.request = request; this.requestDetails = requestDetails;
// Set the remote address
this.remoteAddress = request.getRemoteAddr();
// Get the remote hostname
this.remoteHostname = request.getRemoteHost();
// If session exists get it, but don't create a new one.
this.session = request.getSession(false);
} }
/** /**
* Returns the password associated with this set of credentials. * Returns the password associated with this set of credentials.
* *
* @return The password associated with this username/password pair, or * @return
* null if no password has been set. * The password associated with this username/password pair, or null
* if no password has been set.
*/ */
public String getPassword() { public String getPassword() {
return password; return password;
@@ -117,8 +112,8 @@ public class Credentials implements Serializable {
/** /**
* Sets the password associated with this set of credentials. * Sets the password associated with this set of credentials.
* *
* @param password The password to associate with this username/password * @param password
* pair. * The password to associate with this username/password pair.
*/ */
public void setPassword(String password) { public void setPassword(String password) {
this.password = password; this.password = password;
@@ -127,8 +122,9 @@ public class Credentials implements Serializable {
/** /**
* Returns the username associated with this set of credentials. * Returns the username associated with this set of credentials.
* *
* @return The username associated with this username/password pair, or * @return
* null if no username has been set. * The username associated with this username/password pair, or null
* if no username has been set.
*/ */
public String getUsername() { public String getUsername() {
return username; return username;
@@ -137,8 +133,8 @@ public class Credentials implements Serializable {
/** /**
* Sets the username associated with this set of credentials. * Sets the username associated with this set of credentials.
* *
* @param username The username to associate with this username/password * @param username
* pair. * The username to associate with this username/password pair.
*/ */
public void setUsername(String username) { public void setUsername(String username) {
this.username = username; this.username = username;
@@ -147,9 +143,16 @@ public class Credentials implements Serializable {
/** /**
* Returns the HttpServletRequest associated with this set of credentials. * Returns the HttpServletRequest associated with this set of credentials.
* *
* @return The HttpServletRequest associated with this set of credentials, * @deprecated
* or null if no such request exists. * It is not reliable to reference an HttpServletRequest outside the
* scope of the specific request that created it. Use
* {@link #getRequestDetails()} instead.
*
* @return
* The HttpServletRequest associated with this set of credentials,
* or null if no such request exists.
*/ */
@Deprecated
public HttpServletRequest getRequest() { public HttpServletRequest getRequest() {
return request; return request;
} }
@@ -157,55 +160,173 @@ public class Credentials implements Serializable {
/** /**
* Sets the HttpServletRequest associated with this set of credentials. * Sets the HttpServletRequest associated with this set of credentials.
* *
* @param request The HttpServletRequest to associated with this set of * @deprecated
* credentials. * It is not reliable to reference an HttpServletRequest outside the
* scope of the specific request that created it. Use
* {@link #setRequestDetails(org.apache.guacamole.net.RequestDetails)}
* instead.
*
* @param request
* The HttpServletRequest to associated with this set of credentials.
*/ */
@Deprecated
public void setRequest(HttpServletRequest request) { public void setRequest(HttpServletRequest request) {
setRequestDetails(new RequestDetails(request));
this.request = request; this.request = request;
} }
/** /**
* Returns the HttpSession associated with this set of credentials. * Returns the details of the HTTP request related to these Credentials.
* *
* @return The HttpSession associated with this set of credentials, or null * @return
* if no such request exists. * The details of the HTTP request related to these Credentials.
*/
public RequestDetails getRequestDetails() {
return requestDetails;
}
/**
* Replaces the current HTTP request details of these Credentials with the
* given details.
*
* @param requestDetails
* The details of the HTTP request that should replace the established
* details within these Credentials.
*/
public void setRequestDetails(RequestDetails requestDetails) {
this.requestDetails = requestDetails;
}
/**
* Returns the HttpSession associated with this set of credentials.
* <p>
* This is a convenience function that is equivalent to invoking
* {@link RequestDetails#getSession()} on the {@link RequestDetails}
* returned by {@link #getRequestDetails()}.
* <p>
* <strong>NOTE: Guacamole itself does not use the HttpSession.</strong>
* The extension subsystem does not provide access to the session object
* used by Guacamole, which is considered internal. Access to an HttpSession
* is only of use if you have another application in place that
* <em>does</em> use HttpSession and needs to be considered.
*
* @return
* The HttpSession associated with this set of credentials, or null if
* there is no HttpSession.
*/ */
public HttpSession getSession() { public HttpSession getSession() {
return session; return requestDetails.getSession();
} }
/** /**
* Sets the HttpSession associated with this set of credentials. * Sets the HttpSession associated with this set of credentials.
* *
* @param session The HttpSession to associated with this set of * @deprecated
* credentials. * Since 1.6.0, the HttpSession that may be associated with a
* Credentials object is tied to the RequestDetails. If the HttpSession
* is part of a Credentials and truly needs to be replaced by another
* HttpSession, use {@link #setRequestDetails(org.apache.guacamole.net.RequestDetails)}
* to override the underlying {@link RequestDetails} instead.
*
* @param session
* The HttpSession to associated with this set of credentials.
*/ */
@Deprecated
public void setSession(HttpSession session) { public void setSession(HttpSession session) {
this.session = session; setRequestDetails(new RequestDetails(getRequestDetails()) {
@Override
public HttpSession getSession() {
return session;
}
});
}
/**
* Returns the value of the HTTP header having the given name from the
* original details of the HTTP request that is related to these
* credentials. Header names are case-insensitive. If no such header was
* present, null is returned. If the header had multiple values, the first
* value is returned.
* <p>
* For access to all values of a header, as well as other details of the
* request, see {@link #getRequestDetails()}. This is a convenience
* function that is equivalent to invoking {@link RequestDetails#getHeader(java.lang.String)}.
*
* @param name
* The name of the header to retrieve. This name is case-insensitive.
*
* @return
* The first value of the HTTP header with the given name, or null if
* there is no such header.
*/
public String getHeader(String name) {
return getRequestDetails().getHeader(name);
}
/**
* Returns the value of the HTTP parameter having the given name from the
* original details of the HTTP request that is related to these
* credentials. Parameter names are case-sensitive. If no such parameter was
* present, null is returned. If the parameter had multiple values, the
* first value is returned.
* <p>
* For access to all values of a parameter, as well as other details of the
* request, see {@link #getRequestDetails()}. This is a convenience
* function that is equivalent to invoking {@link RequestDetails#getParameter(java.lang.String)}.
*
* @param name
* The name of the parameter to retrieve. This name is case-sensitive.
*
* @return
* The first value of the HTTP parameter with the given name, or null
* if there is no such parameter.
*/
public String getParameter(String name) {
return getRequestDetails().getParameter(name);
} }
/** /**
* Returns the address of the client end of the connection which provided * Returns the address of the client end of the connection which provided
* these credentials, if known. * these credentials, if known.
* <p>
* This is a convenience function that is equivalent to invoking
* {@link RequestDetails#getRemoteAddress()} on the
* {@link RequestDetails} returned by {@link #getRequestDetails()}.
* *
* @return * @return
* The address of the client end of the connection which provided these * The address of the client end of the connection which provided these
* credentials, or null if the address is not known. * credentials, or null if the address is not known.
*/ */
public String getRemoteAddress() { public String getRemoteAddress() {
return remoteAddress; return getRequestDetails().getRemoteAddress();
} }
/** /**
* Sets the address of the client end of the connection which provided * Sets the address of the client end of the connection which provided
* these credentials. * these credentials.
* *
* @deprecated
* Since 1.6.0, the address that may be associated with a Credentials
* object is tied to the RequestDetails. If the address truly needs to
* be replaced, use {@link #setRequestDetails(org.apache.guacamole.net.RequestDetails)}
* to override the underlying {@link RequestDetails} instead.
*
* @param remoteAddress * @param remoteAddress
* The address of the client end of the connection which provided these * The address of the client end of the connection which provided these
* credentials, or null if the address is not known. * credentials, or null if the address is not known.
*/ */
@Deprecated
public void setRemoteAddress(String remoteAddress) { public void setRemoteAddress(String remoteAddress) {
this.remoteAddress = remoteAddress; setRequestDetails(new RequestDetails(getRequestDetails()) {
@Override
public String getRemoteAddress() {
return remoteAddress;
}
});
} }
/** /**
@@ -213,13 +334,17 @@ public class Credentials implements Serializable {
* these credentials, if known. If the hostname of the client cannot be * these credentials, if known. If the hostname of the client cannot be
* determined, but the address is known, the address may be returned * determined, but the address is known, the address may be returned
* instead. * instead.
* <p>
* This is a convenience function that is equivalent to invoking
* {@link RequestDetails#getRemoteHostname()} on the
* {@link RequestDetails} returned by {@link #getRequestDetails()}.
* *
* @return * @return
* The hostname or address of the client end of the connection which * The hostname or address of the client end of the connection which
* provided these credentials, or null if the hostname is not known. * provided these credentials, or null if the hostname is not known.
*/ */
public String getRemoteHostname() { public String getRemoteHostname() {
return remoteHostname; return getRequestDetails().getRemoteHostname();
} }
/** /**
@@ -228,12 +353,26 @@ public class Credentials implements Serializable {
* determined, but the address is known, the address may be specified * determined, but the address is known, the address may be specified
* instead. * instead.
* *
* @deprecated
* Since 1.6.0, the hostname that may be associated with a Credentials
* object is tied to the RequestDetails. If the hostname truly needs to
* be replaced, use {@link #setRequestDetails(org.apache.guacamole.net.RequestDetails)}
* to override the underlying {@link RequestDetails} instead.
*
* @param remoteHostname * @param remoteHostname
* The hostname or address of the client end of the connection which * The hostname or address of the client end of the connection which
* provided these credentials, or null if the hostname is not known. * provided these credentials, or null if the hostname is not known.
*/ */
@Deprecated
public void setRemoteHostname(String remoteHostname) { public void setRemoteHostname(String remoteHostname) {
this.remoteHostname = remoteHostname; setRequestDetails(new RequestDetails(getRequestDetails()) {
@Override
public String getRemoteHostname() {
return remoteHostname;
}
});
} }
/** /**
@@ -256,15 +395,9 @@ public class Credentials implements Serializable {
if (getUsername() != null || getPassword() != null) if (getUsername() != null || getPassword() != null)
return false; return false;
// All further tests depend on HTTP request details
HttpServletRequest httpRequest = getRequest();
if (httpRequest == null)
return true;
// An authentication request is non-empty if it contains any HTTP // An authentication request is non-empty if it contains any HTTP
// parameters at all or contains an authentication token // parameters at all or contains an authentication token
return !httpRequest.getParameterNames().hasMoreElements() return getRequestDetails().getParameterNames().isEmpty() && getHeader("Guacamole-Token") == null;
&& httpRequest.getHeader("Guacamole-Token") == null;
} }

View File

@@ -20,7 +20,6 @@
package org.apache.guacamole.event; package org.apache.guacamole.event;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import javax.servlet.http.HttpServletRequest;
import org.apache.guacamole.net.auth.Credentials; import org.apache.guacamole.net.auth.Credentials;
/** /**
@@ -78,17 +77,15 @@ public class RemoteAddress implements LoggableDetail {
@Override @Override
public String toString() { public String toString() {
HttpServletRequest request = creds.getRequest(); String remoteAddress = creds.getRemoteAddress();
if (request == null)
return creds.getRemoteAddress();
// Log X-Forwarded-For, if present and valid // Log X-Forwarded-For, if present and valid
String header = request.getHeader("X-Forwarded-For"); String header = creds.getHeader("X-Forwarded-For");
if (header != null && X_FORWARDED_FOR.matcher(header).matches()) if (header != null && X_FORWARDED_FOR.matcher(header).matches())
return "[" + header + ", " + request.getRemoteAddr() + "]"; return "[" + header + ", " + remoteAddress + "]";
// If header absent or invalid, just use source IP // If header absent or invalid, just use source IP
return request.getRemoteAddr(); return remoteAddress;
} }