blob: fee43575d9dcc142f862f1b1aa39fcc415f7a125 [file] [log] [blame]
/*
* 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.radius;
import com.google.common.io.BaseEncoding;
import com.google.inject.Inject;
import com.google.inject.Provider;
import java.util.Arrays;
import javax.servlet.http.HttpServletRequest;
import org.apache.guacamole.auth.radius.user.AuthenticatedUser;
import org.apache.guacamole.auth.radius.form.GuacamoleRadiusChallenge;
import org.apache.guacamole.auth.radius.form.RadiusStateField;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.form.Field;
import org.apache.guacamole.net.auth.Credentials;
import org.apache.guacamole.net.auth.credentials.CredentialsInfo;
import org.apache.guacamole.net.auth.credentials.GuacamoleInvalidCredentialsException;
import org.apache.guacamole.net.auth.credentials.GuacamoleInsufficientCredentialsException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.jradius.dictionary.Attr_State;
import net.jradius.dictionary.Attr_ReplyMessage;
import net.jradius.packet.RadiusPacket;
import net.jradius.packet.AccessAccept;
import net.jradius.packet.AccessChallenge;
import net.jradius.packet.AccessReject;
import net.jradius.packet.attribute.RadiusAttribute;
import org.apache.guacamole.form.PasswordField;
/**
* Service providing convenience functions for the RADIUS AuthenticationProvider
* implementation.
*/
public class AuthenticationProviderService {
/**
* Logger for this class.
*/
private final Logger logger = LoggerFactory.getLogger(AuthenticationProviderService.class);
/**
* The name of the password field where the user will enter a response to
* the RADIUS challenge.
*/
private static final String CHALLENGE_RESPONSE_PARAM = "radiusChallenge";
/**
* Service for creating and managing connections to RADIUS servers.
*/
@Inject
private RadiusConnectionService radiusService;
/**
* Provider for AuthenticatedUser objects.
*/
@Inject
private Provider<AuthenticatedUser> authenticatedUserProvider;
/**
* Returns an object containing the challenge message and the expected
* credentials from a RADIUS challenge, or null if either state or reply
* attributes are missing from the challenge.
*
* @param challengePacket
* The AccessChallenge RadiusPacket received from the RADIUS
* server.
*
* @return
* A GuacamoleRadiusChallenge object that contains the challenge message
* sent by the RADIUS server and the expected credentials that should
* be requested of the user in order to continue authentication. One
* of the expected credentials *must* be the RADIUS state. If either
* state or the reply are missing from the challenge this method will
* return null.
*/
private GuacamoleRadiusChallenge getRadiusChallenge(RadiusPacket challengePacket) {
// Try to get the state attribute - if it's not there, we have a problem
RadiusAttribute stateAttr = challengePacket.findAttribute(Attr_State.TYPE);
if (stateAttr == null) {
logger.error("Something went wrong, state attribute not present.");
logger.debug("State Attribute turned up null, which shouldn't happen in AccessChallenge.");
return null;
}
// We need to get the reply message so we know what to ask the user
RadiusAttribute replyAttr = challengePacket.findAttribute(Attr_ReplyMessage.TYPE);
if (replyAttr == null) {
logger.error("No reply message received from the server.");
logger.debug("Expecting a Attr_ReplyMessage attribute on this packet, and did not get one.");
return null;
}
// We have the required attributes - convert to strings and then generate the additional login box/field
String replyMsg = replyAttr.getValue().toString();
String radiusState = BaseEncoding.base16().encode(stateAttr.getValue().getBytes());
Field radiusResponseField = new PasswordField(CHALLENGE_RESPONSE_PARAM);
Field radiusStateField = new RadiusStateField(radiusState);
// Return the GuacamoleRadiusChallenge object that has the state
// and the expected response.
return new GuacamoleRadiusChallenge(replyMsg,
new CredentialsInfo(Arrays.asList(radiusResponseField,
radiusStateField)));
}
/**
* Returns an AuthenticatedUser representing the user authenticated by the
* given credentials.
*
* @param credentials
* The credentials to use for authentication.
*
* @return
* An AuthenticatedUser representing the user authenticated by the
* given credentials.
*
* @throws GuacamoleException
* If an error occurs while authenticating the user, or if access is
* denied.
*/
public AuthenticatedUser authenticateUser(Credentials credentials)
throws GuacamoleException {
// Ignore anonymous users
if (credentials.getUsername() == null || credentials.getUsername().isEmpty())
return null;
// Password is required
if (credentials.getPassword() == null || credentials.getPassword().isEmpty())
return null;
// Grab HTTP request object and a response to a challenge.
HttpServletRequest request = credentials.getRequest();
String challengeResponse = request.getParameter(CHALLENGE_RESPONSE_PARAM);
// RadiusPacket object to store response from server.
RadiusPacket radPack;
// No challenge response, proceed with username/password authentication.
if (challengeResponse == null) {
try {
radPack = radiusService.authenticate(credentials.getUsername(),
credentials.getPassword(), null);
}
catch (GuacamoleException e) {
logger.error("Cannot configure RADIUS server: {}", e.getMessage());
logger.debug("Error configuring RADIUS server.", e);
throw new GuacamoleInvalidCredentialsException("Authentication error.", CredentialsInfo.USERNAME_PASSWORD);
}
}
// This is a response to a previous challenge, authenticate with that.
else {
try {
String stateString = request.getParameter(RadiusStateField.PARAMETER_NAME);
if (stateString == null) {
logger.warn("Expected state parameter was not present in challenge/response.");
throw new GuacamoleInvalidCredentialsException("Authentication error.", CredentialsInfo.USERNAME_PASSWORD);
}
byte[] stateBytes = BaseEncoding.base16().decode(stateString);
radPack = radiusService.sendChallengeResponse(credentials.getUsername(),
challengeResponse,
stateBytes);
}
catch (IllegalArgumentException e) {
logger.warn("Illegal hexadecimal value while parsing RADIUS state string: {}", e.getMessage());
logger.debug("Encountered exception while attempting to parse the hexidecimal state value.", e);
throw new GuacamoleInvalidCredentialsException("Authentication error.", CredentialsInfo.USERNAME_PASSWORD);
}
catch (GuacamoleException e) {
logger.error("Cannot configure RADIUS server: {}", e.getMessage());
logger.debug("Error configuring RADIUS server.", e);
throw new GuacamoleInvalidCredentialsException("Authentication error.", CredentialsInfo.USERNAME_PASSWORD);
}
}
// No RadiusPacket is returned, we've encountered an error.
if (radPack == null) {
logger.debug("Nothing in the RADIUS packet.");
throw new GuacamoleInvalidCredentialsException("Authentication error.", CredentialsInfo.USERNAME_PASSWORD);
}
// Received AccessReject packet, login is denied.
else if (radPack instanceof AccessReject) {
logger.debug("Login has been rejected by RADIUS server.");
throw new GuacamoleInvalidCredentialsException("Authentication failed.", CredentialsInfo.USERNAME_PASSWORD);
}
// Received AccessAccept, authentication has succeeded
else if (radPack instanceof AccessAccept) {
AuthenticatedUser authenticatedUser = authenticatedUserProvider.get();
authenticatedUser.init(credentials);
return authenticatedUser;
}
// Received AccessChallenge packet, more credentials required to complete authentication
else if (radPack instanceof AccessChallenge) {
GuacamoleRadiusChallenge challenge = getRadiusChallenge(radPack);
if (challenge == null)
throw new GuacamoleInvalidCredentialsException("Authentication error.", CredentialsInfo.USERNAME_PASSWORD);
throw new GuacamoleInsufficientCredentialsException(
challenge.getChallengeText(),
challenge.getExpectedCredentials());
}
// Something unanticipated happened, so panic and go back to login.
else {
logger.error("Unexpected failure authenticating with RADIUS server.");
throw new GuacamoleInvalidCredentialsException("Unknown error trying to authenticate.", CredentialsInfo.USERNAME_PASSWORD);
}
}
}