Skip to content

Commit

Permalink
support RSA Securid through PAP protocol
Browse files Browse the repository at this point in the history
- handle properly AccessChallenge in JRadius implementation
- distinguish token change with custom TokenChangeException
- provide tokenChange variable in webflow context to distinguish info
from error
- add handling of authentication errors in case of error
  • Loading branch information
dodok1 committed Apr 10, 2017
1 parent 99f2173 commit 29012f5
Show file tree
Hide file tree
Showing 8 changed files with 125 additions and 33 deletions.
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ gradleVersion=3.2.1
sourceCompatibility=1.8
targetCompatibility=1.8
group=org.apereo.cas
version=5.0.4
version=5.0.4-securid

aspectjVersion=1.8.9
junitVersion=4.12
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@


import net.jradius.client.RadiusClient;
import net.jradius.client.auth.PAPAuthenticator;
import net.jradius.client.auth.RadiusAuthenticator;
import net.jradius.dictionary.Attr_NASIPAddress;
import net.jradius.dictionary.Attr_NASIPv6Address;
import net.jradius.dictionary.Attr_NASIdentifier;
Expand All @@ -11,7 +13,10 @@
import net.jradius.dictionary.Attr_UserName;
import net.jradius.dictionary.Attr_UserPassword;
import net.jradius.dictionary.vsa_redback.Attr_NASRealPort;
import net.jradius.exception.RadiusException;
import net.jradius.exception.UnknownAttributeException;
import net.jradius.packet.AccessAccept;
import net.jradius.packet.AccessChallenge;
import net.jradius.packet.AccessRequest;
import net.jradius.packet.RadiusPacket;
import net.jradius.packet.attribute.AttributeFactory;
Expand All @@ -21,6 +26,8 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.security.NoSuchAlgorithmException;

/**
* Implementation of a RadiusServer that utilizes the JRadius packages available
* at <a href="http://jradius.sf.net">http://jradius.sf.net</a>.
Expand Down Expand Up @@ -105,7 +112,7 @@ public RadiusResponse authenticate(final String username, final String password)
if (this.nasPortId != -1) {
attributeList.add(new Attr_NASPortId(this.nasPortId));
}
if (StringUtils.isNotBlank(this.nasIdentifier)) {
if (StringUtils.isNotBlank(this.nasIdentifier)) {
attributeList.add(new Attr_NASIdentifier(this.nasIdentifier));
}
if (this.nasRealPort != -1) {
Expand All @@ -119,21 +126,26 @@ public RadiusResponse authenticate(final String username, final String password)
try {
client = this.radiusClientFactory.newInstance();
final AccessRequest request = new AccessRequest(client, attributeList);
final RadiusPacket response = client.authenticate(
// FIXME this code should be called only in specific case of PAP protocol with RSA SecureId server
final RadiusPacket response = authenticate(client,
request,
RadiusClient.getAuthProtocol(this.protocol.getName()),
this.retries);

LOGGER.debug("RADIUS response from {}: {}",
client.getRemoteInetAddress().getCanonicalHostName(),
response.getClass().getName());
response.toString(true, true));

if (response instanceof AccessAccept) {
final AccessAccept acceptedResponse = (AccessAccept) response;

return new RadiusResponse(acceptedResponse.getCode(),
acceptedResponse.getIdentifier(),
acceptedResponse.getAttributes().getAttributeList());
} else if (response instanceof AccessChallenge) {
final AccessChallenge challenge = (AccessChallenge) response;
return new RadiusResponse(challenge.getCode(), challenge.getIdentifier(),
challenge.getAttributes().getAttributeList());
}
} finally {
if (client != null) {
Expand All @@ -143,7 +155,21 @@ public RadiusResponse authenticate(final String username, final String password)
return null;
}


/**
* Local implementation of RADIUS authentication that accepts AccessChallenge as valid response.
*
*/
private net.jradius.packet.RadiusResponse authenticate(RadiusClient client, AccessRequest p, RadiusAuthenticator auth, int retries)
throws RadiusException, NoSuchAlgorithmException
{
if (auth == null) auth = new PAPAuthenticator();

auth.setupRequest(client, p);
auth.processRequest(p);

return client.sendReceive(p, retries);
}

/**
* Sets the nas ip address.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ private RadiusUtils() {}
* @param servers the servers
* @param failoverOnAuthenticationFailure the failover on authentication failure
* @param failoverOnException the failover on exception
* @return the pair
* @return the pair indicating completed login in
* @throws Exception the exception
*/
public static Pair<Boolean, Optional<Map<String, Object>>> authenticate(final String username, final String password,
Expand All @@ -48,7 +48,7 @@ public static Pair<Boolean, Optional<Map<String, Object>>> authenticate(final St
for (final RadiusAttribute attribute : response.getAttributes()) {
attributes.put(attribute.getAttributeName(), attribute.getValue().toString());
}
return new Pair<>(Boolean.TRUE, Optional.of(attributes));
return new Pair<>(response.getCode() == 2, Optional.of(attributes));
}

if (!failoverOnAuthenticationFailure) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.apereo.cas.adaptors.radius;

import javax.security.auth.login.LoginException;

/**
* Exception indicating that provided login credentials are not complete and should provided again with changed token.
*/
public class TokenChangeException extends LoginException {

private static final long serialVersionUID = 0;

/**
* Constructs a {@link TokenChangeException} with the specified detail message.
* A detail message is a String that describes change required from authentication backend.
*
* <p>
*
* @param msg instruction for complete authentication.
*/
public TokenChangeException(String msg) {
super(msg);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.apereo.cas.adaptors.radius.authentication;

import net.jradius.exception.TimeoutException;
import org.apereo.cas.adaptors.radius.TokenChangeException;
import org.apereo.cas.adaptors.radius.RadiusServer;
import org.apereo.cas.adaptors.radius.RadiusUtils;
import org.apereo.cas.authentication.Credential;
Expand Down Expand Up @@ -47,24 +48,27 @@ public boolean supports(final Credential credential) {

@Override
protected HandlerResult doAuthentication(final Credential credential) throws GeneralSecurityException, PreventedException {
final RadiusTokenCredential radiusCredential = (RadiusTokenCredential) credential;
final String password = radiusCredential.getToken();

final RequestContext context = RequestContextHolder.getRequestContext();
final String username = WebUtils.getAuthentication(context).getPrincipal().getId();

final Pair<Boolean, Optional<Map<String, Object>>> result;
try {
final RadiusTokenCredential radiusCredential = (RadiusTokenCredential) credential;
final String password = radiusCredential.getToken();

final RequestContext context = RequestContextHolder.getRequestContext();
final String username = WebUtils.getAuthentication(context).getPrincipal().getId();

final Pair<Boolean, Optional<Map<String, Object>>> result =
RadiusUtils.authenticate(username, password, this.servers,
this.failoverOnAuthenticationFailure, this.failoverOnException);
if (result.getFirst()) {
return createHandlerResult(credential, this.principalFactory.createPrincipal(username, result.getSecond().get()),
new ArrayList<>());
}
throw new FailedLoginException("Radius authentication failed for user " + username);
result = RadiusUtils.authenticate(username, password, this.servers,
this.failoverOnAuthenticationFailure, this.failoverOnException);
} catch (final Exception e) {
throw new FailedLoginException("Radius authentication failed " + e.getMessage());
}
if (result.getFirst()) {
return createHandlerResult(credential, this.principalFactory.createPrincipal(username, result.getSecond().get()),
new ArrayList<>());
}
else if (result.getSecond().isPresent()){
throw new TokenChangeException((String) result.getSecond().get().getOrDefault("Reply-Message", null));
}
throw new FailedLoginException("Radius authentication failed for user " + username);
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
package org.apereo.cas.adaptors.radius.web.flow;

import com.google.common.collect.ImmutableSet;
import org.apereo.cas.adaptors.radius.TokenChangeException;
import org.apereo.cas.adaptors.radius.authentication.RadiusTokenAuthenticationHandler;
import org.apereo.cas.authentication.AuthenticationException;
import org.apereo.cas.authentication.AuthenticationResultBuilder;
import org.apereo.cas.authentication.Credential;
import org.apereo.cas.authentication.principal.Service;
import org.apereo.cas.web.flow.resolver.impl.AbstractCasWebflowEventResolver;
import org.apereo.cas.web.support.WebUtils;
import org.springframework.webflow.execution.Event;
import org.springframework.webflow.execution.RequestContext;

Expand All @@ -16,7 +24,29 @@
public class RadiusAuthenticationWebflowEventResolver extends AbstractCasWebflowEventResolver {
@Override
protected Set<Event> resolveInternal(final RequestContext context) {
return handleAuthenticationTransactionAndGrantTicketGrantingTicket(context);
try {
final Credential credential = getCredentialFromContext(context);
AuthenticationResultBuilder builder = WebUtils.getAuthenticationResultBuilder(context);

logger.debug("Handling authentication transaction for credential {}", credential);
builder = this.authenticationSystemSupport.handleAuthenticationTransaction(builder, credential);
final Service service = WebUtils.getService(context);

logger.debug("Issuing ticket-granting tickets for service {}", service);
return ImmutableSet.of(grantTicketGrantingTicketToAuthenticationResult(context, builder, service));
} catch (final AuthenticationException e) {
Class<? extends Exception> error = e.getHandlerErrors().get(RadiusTokenAuthenticationHandler.class.getSimpleName());
boolean tokenChange = error != null && error == TokenChangeException.class;
context.getFlowScope().put("tokenChange", tokenChange);
if (tokenChange) {
return ImmutableSet.of(newEvent("tokenChange"));
}
else {
return ImmutableSet.of(newEvent("error", e));
}
} catch (final Exception e) {
return ImmutableSet.of(newEvent("error", e));
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,13 @@
<action-state id="realSubmitRadius">
<evaluate expression="radiusAuthenticationWebflowAction" />
<transition on="success" to="success" />
<transition on="error" to="initializeLoginForm" />
<transition on="error" to="handleError" />
<transition on="tokenChange" to="viewLoginFormRadius" />
</action-state>

<action-state id="handleError">
<evaluate expression="authenticationExceptionHandler.handle(currentEvent.attributes.error, messageContext)"/>
<transition to="initializeLoginForm"/>
</action-state>

<end-state id="success" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.apereo.cas.adaptors.radius.authentication.handler.support;

import org.apereo.cas.adaptors.radius.TokenChangeException;
import org.apereo.cas.authentication.HandlerResult;
import org.apereo.cas.authentication.PreventedException;
import org.apereo.cas.authentication.handler.support.AbstractUsernamePasswordAuthenticationHandler;
Expand Down Expand Up @@ -50,19 +51,21 @@ public RadiusAuthenticationHandler() {
protected HandlerResult authenticateUsernamePasswordInternal(final UsernamePasswordCredential credential)
throws GeneralSecurityException, PreventedException {

final String username = credential.getUsername();
final Pair<Boolean, Optional<Map<String, Object>>> result;
try {
final String username = credential.getUsername();
final Pair<Boolean, Optional<Map<String, Object>>> result =
RadiusUtils.authenticate(username, credential.getPassword(), this.servers,
this.failoverOnAuthenticationFailure, this.failoverOnException);
if (result.getFirst()) {
return createHandlerResult(credential, this.principalFactory.createPrincipal(username, result.getSecond().get()),
new ArrayList<>());
}
throw new FailedLoginException("Radius authentication failed for user " + username);
} catch (final Exception e) {
result = RadiusUtils.authenticate(username, credential.getPassword(), this.servers,
this.failoverOnAuthenticationFailure, this.failoverOnException);
} catch (Exception e) {
throw new FailedLoginException("Radius authentication failed " + e.getMessage());
}
if (result.getFirst()) {
return createHandlerResult(credential, this.principalFactory.createPrincipal(username, result.getSecond().get()),
new ArrayList<>());
} else if (result.getSecond().isPresent()){
throw new TokenChangeException((String) result.getSecond().get().getOrDefault("Reply-Message", null));
}
throw new FailedLoginException("Radius authentication failed for user " + username);
}

/**
Expand Down

0 comments on commit 29012f5

Please sign in to comment.