diff --git a/extensions/guacamole-auth-radius/pom.xml b/extensions/guacamole-auth-radius/pom.xml index e9d5b5ca7..9aaa515f5 100644 --- a/extensions/guacamole-auth-radius/pom.xml +++ b/extensions/guacamole-auth-radius/pom.xml @@ -258,6 +258,18 @@ 1.1.5 + + commons-lang + commons-lang + 2.6 + + + commons-codec + commons-codec + 1.10 + + + diff --git a/extensions/guacamole-auth-radius/src/main/java/org/apache/guacamole/auth/radius/AuthenticationProviderService.java b/extensions/guacamole-auth-radius/src/main/java/org/apache/guacamole/auth/radius/AuthenticationProviderService.java index c6650bc8b..91bb0985b 100644 --- a/extensions/guacamole-auth-radius/src/main/java/org/apache/guacamole/auth/radius/AuthenticationProviderService.java +++ b/extensions/guacamole-auth-radius/src/main/java/org/apache/guacamole/auth/radius/AuthenticationProviderService.java @@ -21,9 +21,11 @@ package org.apache.guacamole.auth.radius; import com.google.inject.Inject; import com.google.inject.Provider; -import java.util.Collections; +import java.util.Arrays; +import javax.servlet.http.HttpServletRequest; import org.apache.guacamole.auth.radius.user.AuthenticatedUser; import org.apache.guacamole.auth.radius.form.RadiusChallengeResponseField; +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; @@ -32,6 +34,7 @@ import org.apache.guacamole.net.auth.credentials.GuacamoleInvalidCredentialsExce 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.exception.UnknownAttributeException; import net.jradius.packet.RadiusPacket; import net.jradius.packet.AccessAccept; @@ -90,69 +93,124 @@ public class AuthenticationProviderService { public AuthenticatedUser authenticateUser(Credentials credentials) throws GuacamoleException { - // Initialize Radius Packet and try to authenticate + // Grab the HTTP Request from the credentials object + HttpServletRequest request = credentials.getRequest(); + + // Set up RadiusPacket object RadiusPacket radPack; - try { - radPack = radiusService.authenticate(credentials.getUsername(), + + // Ignore anonymous users + if (credentials.getUsername() == null || credentials.getUsername().isEmpty()) + return null; + + // Password is required + if (credentials.getPassword() == null || credentials.getPassword().isEmpty()) + return null; + + String challengeResponse = request.getParameter(RadiusChallengeResponseField.PARAMETER_NAME); + String radiusState = request.getParameter(RadiusStateField.PARAMETER_NAME); + + // We do not have a challenge response, so we proceed normally + if (challengeResponse == null || challengeResponse.isEmpty()) { + + // Initialize Radius Packet and try to authenticate + try { + radPack = radiusService.authenticate(credentials.getUsername(), credentials.getPassword()); - } - catch (GuacamoleException e) { - logger.error("Cannot configure RADIUS server: {}", e.getMessage()); - logger.debug("Error configuring RADIUS server.", e); - radPack = null; - } - - // If configure fails, permission to login is denied - if (radPack == null) { - logger.debug("Nothing in the RADIUS packet."); - throw new GuacamoleInvalidCredentialsException("Permission denied.", CredentialsInfo.USERNAME_PASSWORD); - } - - // If we get back an AccessReject packet, login is denied. - else if (radPack instanceof AccessReject) { - logger.debug("Login has been rejected by RADIUS server."); - throw new GuacamoleInvalidCredentialsException("Permission denied.", CredentialsInfo.USERNAME_PASSWORD); - } - - /** - * If we receive an AccessChallenge package, the server needs more information - - * We create a new form/field with the challenge message. - */ - else if (radPack instanceof AccessChallenge) { - try { - String replyMsg = radPack.getAttributeValue("Reply-Message").toString(); - String radState = radPack.getAttributeValue("State").toString(); - logger.debug("RADIUS sent challenge: {}", replyMsg); - logger.debug("RADIUS sent state: {}", radState); - Field radiusResponseField = new RadiusChallengeResponseField(credentials.getUsername(), replyMsg, radState); - CredentialsInfo expectedCredentials = new CredentialsInfo(Collections.singletonList(radiusResponseField)); - throw new GuacamoleInsufficientCredentialsException("LOGIN.INFO_RADIUS_ADDL_REQUIRED", expectedCredentials); } - catch(UnknownAttributeException e) { - logger.error("Error in talks with RADIUS server."); - logger.debug("RADIUS challenged by didn't provide right attributes."); - 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); + radPack = null; } + + // If configure fails, permission to login is denied + if (radPack == null) { + logger.debug("Nothing in the RADIUS packet."); + throw new GuacamoleInvalidCredentialsException("Permission denied.", CredentialsInfo.USERNAME_PASSWORD); + } + + // If we get back an AccessReject packet, login is denied. + else if (radPack instanceof AccessReject) { + logger.debug("Login has been rejected by RADIUS server."); + throw new GuacamoleInvalidCredentialsException("Permission denied.", CredentialsInfo.USERNAME_PASSWORD); + } + + /** + * If we receive an AccessChallenge package, the server needs more information - + * We create a new form/field with the challenge message. + */ + else if (radPack instanceof AccessChallenge) { + try { + RadiusAttribute stateAttr = radPack.findAttribute(Attr_State.TYPE); + // We should have a state attribute at this point, if not, we need to quit. + 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."); + throw new GuacamoleInvalidCredentialsException("Authentication error.", CredentialsInfo.USERNAME_PASSWORD); + } + String replyMsg = radPack.getAttributeValue("Reply-Message").toString(); + radiusState = new String(stateAttr.getValue().getBytes()); + Field radiusResponseField = new RadiusChallengeResponseField(replyMsg); + Field radiusStateField = new RadiusStateField(radiusState); + CredentialsInfo expectedCredentials = new CredentialsInfo(Arrays.asList(radiusResponseField,radiusStateField)); + throw new GuacamoleInsufficientCredentialsException("LOGIN.INFO_RADIUS_ADDL_REQUIRED", expectedCredentials); + } + catch (UnknownAttributeException e) { + logger.error("Error in talks with RADIUS server."); + logger.debug("RADIUS challenged by didn't provide right attributes."); + throw new GuacamoleInvalidCredentialsException("Authentication error.", CredentialsInfo.USERNAME_PASSWORD); + } + } + + // If we receive AccessAccept, authentication has succeeded + else if (radPack instanceof AccessAccept) { + try { + + // Return AuthenticatedUser if bind succeeds + AuthenticatedUser authenticatedUser = authenticatedUserProvider.get(); + authenticatedUser.init(credentials); + return authenticatedUser; + } + finally { + radiusService.disconnect(); + } + } + + // Something unanticipated happened, so we panic + else + throw new GuacamoleInvalidCredentialsException("Unknown error trying to authenticate.", CredentialsInfo.USERNAME_PASSWORD); } - // If we receive AccessAccept, authentication has succeeded - else if (radPack instanceof AccessAccept) { + // We did receive a challenge response, so we're going to send that back to the server + else { + // Initialize Radius Packet and try to authenticate try { - - // Return AuthenticatedUser if bind succeeds - AuthenticatedUser authenticatedUser = authenticatedUserProvider.get(); - authenticatedUser.init(credentials); - return authenticatedUser; + radPack = radiusService.authenticate(credentials.getUsername(), + radiusState, + challengeResponse); + } + catch (GuacamoleException e) { + logger.error("Cannot configure RADIUS server: {}", e.getMessage()); + logger.debug("Error configuring RADIUS server.", e); + radPack = null; } finally { radiusService.disconnect(); } + + if (radPack instanceof AccessAccept) { + AuthenticatedUser authenticatedUser = authenticatedUserProvider.get(); + authenticatedUser.init(credentials); + return authenticatedUser; + } + + else { + logger.warn("RADIUS Challenge/Response authentication failed."); + logger.debug("Did not receive a RADIUS AccessAccept packet back from server."); + throw new GuacamoleInvalidCredentialsException("Failed to authenticate to RADIUS.", CredentialsInfo.USERNAME_PASSWORD); + } } - - // Something else we haven't thought of has happened, so we throw an error - else - throw new GuacamoleInvalidCredentialsException("Unknown error trying to authenticate.", CredentialsInfo.USERNAME_PASSWORD); - } } diff --git a/extensions/guacamole-auth-radius/src/main/java/org/apache/guacamole/auth/radius/RadiusConnectionService.java b/extensions/guacamole-auth-radius/src/main/java/org/apache/guacamole/auth/radius/RadiusConnectionService.java index 02ad9869c..51eef07a7 100644 --- a/extensions/guacamole-auth-radius/src/main/java/org/apache/guacamole/auth/radius/RadiusConnectionService.java +++ b/extensions/guacamole-auth-radius/src/main/java/org/apache/guacamole/auth/radius/RadiusConnectionService.java @@ -178,7 +178,6 @@ public class RadiusConnectionService { radAttrs.add(new Attr_UserName(username)); radAttrs.add(new Attr_UserPassword(password)); AccessRequest radAcc = new AccessRequest(radiusClient, radAttrs); - logger.debug("Sending authentication request to radius server for user {}.", username); radAuth.setupRequest(radiusClient, radAcc); radAuth.processRequest(radAcc); return radiusClient.sendReceive(radAcc, confService.getRadiusRetries()); @@ -256,7 +255,6 @@ public class RadiusConnectionService { radAttrs.add(new Attr_State(state)); radAttrs.add(new Attr_UserPassword(response)); AccessRequest radAcc = new AccessRequest(radiusClient, radAttrs); - logger.debug("Sending authentication response to radius server for user {}.", username); radAuth.setupRequest(radiusClient, radAcc); radAuth.processRequest(radAcc); return radiusClient.sendReceive(radAcc, confService.getRadiusRetries()); diff --git a/extensions/guacamole-auth-radius/src/main/java/org/apache/guacamole/auth/radius/form/RadiusChallengeResponseField.java b/extensions/guacamole-auth-radius/src/main/java/org/apache/guacamole/auth/radius/form/RadiusChallengeResponseField.java index 111c07dee..f76c7bd7a 100644 --- a/extensions/guacamole-auth-radius/src/main/java/org/apache/guacamole/auth/radius/form/RadiusChallengeResponseField.java +++ b/extensions/guacamole-auth-radius/src/main/java/org/apache/guacamole/auth/radius/form/RadiusChallengeResponseField.java @@ -34,23 +34,13 @@ public class RadiusChallengeResponseField extends Field { /** * The field returned by the RADIUS challenge/response. */ - private static final String RADIUS_FIELD_NAME = "guac-radius-challenge-response"; + public static final String PARAMETER_NAME = "guac-radius-challenge-response"; /** * The type of field to initialize for the challenge/response. */ private static final String RADIUS_FIELD_TYPE = "GUAC_RADIUS_CHALLENGE_RESPONSE"; - /** - * The username used for the RADIUS authentication attempt. - */ - private final String username; - - /** - * The state of the connection passed by the previous RADIUS attempt. - */ - private final String radiusState; - /** * The message the RADIUS server sent back in the challenge. */ @@ -59,24 +49,14 @@ public class RadiusChallengeResponseField extends Field { /** * Initialize the field with the reply message and the state. */ - public RadiusChallengeResponseField(String username, String replyMsg, String radiusState) { - super(RADIUS_FIELD_NAME, RADIUS_FIELD_TYPE); + public RadiusChallengeResponseField(String replyMsg) { + super(PARAMETER_NAME, RADIUS_FIELD_TYPE); logger.debug("Initializing the RADIUS challenge/response field: {}", replyMsg); - this.username = username; this.replyMsg = replyMsg; - this.radiusState = radiusState; } - public String getUsername() { - return username; - } - - public String getRadiusState() { - return radiusState; - } - public String getReplyMsg() { return replyMsg; } diff --git a/extensions/guacamole-auth-radius/src/main/java/org/apache/guacamole/auth/radius/form/RadiusStateField.java b/extensions/guacamole-auth-radius/src/main/java/org/apache/guacamole/auth/radius/form/RadiusStateField.java new file mode 100644 index 000000000..97a0e447a --- /dev/null +++ b/extensions/guacamole-auth-radius/src/main/java/org/apache/guacamole/auth/radius/form/RadiusStateField.java @@ -0,0 +1,64 @@ +/* + * 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.form; + +import org.apache.guacamole.form.Field; +import org.codehaus.jackson.annotate.JsonProperty; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class RadiusStateField extends Field { + + /** + * Logger for this class. + */ + private final Logger logger = LoggerFactory.getLogger(RadiusStateField.class); + + /** + * The parameter returned by the RADIUS state. + */ + public static final String PARAMETER_NAME = "guac-radius-state"; + + /** + * The type of field to initialize for the state. + */ + private static final String RADIUS_FIELD_TYPE = "GUAC_RADIUS_STATE"; + + /** + * The state of the connection passed by the previous RADIUS attempt. + */ + private final String radiusState; + + /** + * Initialize the field with the reply message and the state. + */ + public RadiusStateField(String radiusState) { + super(PARAMETER_NAME, RADIUS_FIELD_TYPE); + logger.debug("Initializing the RADIUS state field: {}", radiusState); + + this.radiusState = radiusState; + + } + + public String getRadiusState() { + return radiusState; + } + +} diff --git a/extensions/guacamole-auth-radius/src/main/resources/config/radiusConfig.js b/extensions/guacamole-auth-radius/src/main/resources/config/radiusConfig.js index bc14e63c3..cff025c57 100644 --- a/extensions/guacamole-auth-radius/src/main/resources/config/radiusConfig.js +++ b/extensions/guacamole-auth-radius/src/main/resources/config/radiusConfig.js @@ -27,8 +27,13 @@ angular.module('guacRadius').config(['formServiceProvider', // Define field for the challenge from the RADIUS service formServiceProvider.registerFieldType('GUAC_RADIUS_CHALLENGE_RESPONSE', { module : 'guacRadius', - controller : 'guacRadiusController', - templateUrl : 'app/ext/radius/templates/radiusChallengeResponseField.html' + controller : 'radiusResponseController', + templateUrl : 'app/ext/radius/templates/radiusResponseField.html' + }); + formServiceProvider.registerFieldType('GUAC_RADIUS_STATE', { + module : 'guacRadius', + controller : 'radiusStateController', + template : '' }); }]); diff --git a/extensions/guacamole-auth-radius/src/main/resources/controllers/guacRadiusController.js b/extensions/guacamole-auth-radius/src/main/resources/controllers/radiusResponseController.js similarity index 70% rename from extensions/guacamole-auth-radius/src/main/resources/controllers/guacRadiusController.js rename to extensions/guacamole-auth-radius/src/main/resources/controllers/radiusResponseController.js index beb544d88..16d7ce56a 100644 --- a/extensions/guacamole-auth-radius/src/main/resources/controllers/guacRadiusController.js +++ b/extensions/guacamole-auth-radius/src/main/resources/controllers/radiusResponseController.js @@ -22,22 +22,15 @@ * API to prompt the user for additional credentials, ultimately receiving a * signed response from the Duo service. */ -angular.module('guacRadius').controller('guacRadiusController', ['$scope', '$element', - function guacRadiusController($scope, $element) { - console.log("In guacRadiusController() method."); +angular.module('guacRadius').controller('radiusResponseController', ['$scope', '$element', + function radiusResponseController($scope, $element) { + console.log("In radiusResponseController() method."); // Find the area to display the challenge message var radiusChallenge = $element.find(document.querySelector('#radius-challenge-text')); - // Find the hidden input to put the state in - var radiusState = $element.find(document.querySelector('#radius-state')); - // Populate the reply message field console.log("RADIUS Reply Message: " + $scope.field.replyMsg); - radiusChellenge.html($scope.field.replyMsg); - - // Populate the input area for the connection state - console.log("RADIUS State: " + scope.field.radiusState); - radiusState.value = $scope.field.radiusState; + radiusChallenge.html($scope.field.replyMsg); }]); diff --git a/extensions/guacamole-auth-radius/src/main/resources/controllers/radiusStateController.js b/extensions/guacamole-auth-radius/src/main/resources/controllers/radiusStateController.js new file mode 100644 index 000000000..8b51933f8 --- /dev/null +++ b/extensions/guacamole-auth-radius/src/main/resources/controllers/radiusStateController.js @@ -0,0 +1,33 @@ +/* + * 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. + */ + +/** + * Controller for the "GUAC_RADIUS_CHALLENGE_RESPONSE" field which uses the DuoWeb + * API to prompt the user for additional credentials, ultimately receiving a + * signed response from the Duo service. + */ +angular.module('guacRadius').controller('radiusStateController', ['$scope', '$element', + function radiusStateController($scope, $element) { + console.log("In radiusStateController() method."); + + // Populate the input area for the connection state + console.log("RADIUS State: " + $scope.field.radiusState); + $scope.model = $scope.field.radiusState; + +}]); diff --git a/extensions/guacamole-auth-radius/src/main/resources/templates/radiusChallengeResponseField.html b/extensions/guacamole-auth-radius/src/main/resources/templates/radiusChallengeResponseField.html deleted file mode 100644 index 6dedcda7b..000000000 --- a/extensions/guacamole-auth-radius/src/main/resources/templates/radiusChallengeResponseField.html +++ /dev/null @@ -1,9 +0,0 @@ -
-
-
- -
-
- - -
diff --git a/extensions/guacamole-auth-radius/src/main/resources/templates/radiusResponseField.html b/extensions/guacamole-auth-radius/src/main/resources/templates/radiusResponseField.html new file mode 100644 index 000000000..a75f66dae --- /dev/null +++ b/extensions/guacamole-auth-radius/src/main/resources/templates/radiusResponseField.html @@ -0,0 +1,5 @@ +
+
+ +
+