Skip to content

Commit

Permalink
[UNDERTOW-466] Split out the generic notification handling side of th…
Browse files Browse the repository at this point in the history
…e SecurityContext implementation into an abstract base class so future SecurityContext implementations can focus just on the actual authentication.
  • Loading branch information
darranl committed Jun 4, 2015
1 parent 309e28b commit fe2b72e
Show file tree
Hide file tree
Showing 2 changed files with 166 additions and 115 deletions.
@@ -0,0 +1,157 @@
/*
* JBoss, Home of Professional Open Source.
* Copyright 2015 Red Hat, Inc., and individual contributors
* as indicated by the @author tags.
*
* Licensed 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 io.undertow.security.impl;

import static io.undertow.UndertowMessages.MESSAGES;
import io.undertow.security.api.NotificationReceiver;
import io.undertow.security.api.SecurityContext;
import io.undertow.security.api.SecurityNotification;
import io.undertow.security.api.SecurityNotification.EventType;
import io.undertow.security.idm.Account;
import io.undertow.server.HttpServerExchange;

/**
* A base class for {@link SecurityContext} implementations predominantly focusing on the notification handling allowing the
* specific implementation for focus on authentication.
*
* @author <a href="mailto:darran.lofthouse@jboss.com">Darran Lofthouse</a>
*/
public abstract class AbstractSecurityContext implements SecurityContext {

private boolean authenticationRequired;
protected final HttpServerExchange exchange;

private Node<NotificationReceiver> notificationReceivers = null;

private Account account;
private String mechanismName;

protected AbstractSecurityContext(final HttpServerExchange exchange) {
this.exchange = exchange;
}

@Override
public void setAuthenticationRequired() {
authenticationRequired = true;
}

@Override
public boolean isAuthenticationRequired() {
return authenticationRequired;
}

@Override
public boolean isAuthenticated() {
return account != null;
}

@Override
public Account getAuthenticatedAccount() {
return account;
}

/**
* @return The name of the mechanism used to authenticate the request.
*/
@Override
public String getMechanismName() {
return mechanismName;
}

@Override
public void authenticationComplete(Account account, String mechanism, final boolean cachingRequired) {
authenticationComplete(account, mechanism, false, cachingRequired);
}

protected void authenticationComplete(Account account, String mechanism, boolean programatic, final boolean cachingRequired) {
this.account = account;
this.mechanismName = mechanism;

sendNoticiation(new SecurityNotification(exchange, EventType.AUTHENTICATED, account, mechanism, programatic,
MESSAGES.userAuthenticated(account.getPrincipal().getName()), cachingRequired));
}

@Override
public void authenticationFailed(String message, String mechanism) {
sendNoticiation(new SecurityNotification(exchange, EventType.FAILED_AUTHENTICATION, null, mechanism, false, message, true));
}

@Override
public void registerNotificationReceiver(NotificationReceiver receiver) {
if(notificationReceivers == null) {
notificationReceivers = new Node<>(receiver);
} else {
Node<NotificationReceiver> cur = notificationReceivers;
while (cur.next != null) {
cur = cur.next;
}
cur.next = new Node<>(receiver);
}
}

@Override
public void removeNotificationReceiver(NotificationReceiver receiver) {
Node<NotificationReceiver> cur = notificationReceivers;
if(receiver.equals(cur.item)) {
notificationReceivers = cur.next;
} else {
Node<NotificationReceiver> old = cur;
while (cur.next != null) {
cur = cur.next;
if(receiver.equals(cur.item)) {
old.next = cur.next;
}
old = cur;
}
}
}

private void sendNoticiation(final SecurityNotification notification) {
Node<NotificationReceiver> cur = notificationReceivers;
while (cur != null) {
cur.item.handleNotification(notification);
cur = cur.next;
}
}

@Override
public void logout() {
if (!isAuthenticated()) {
return;
}
sendNoticiation(new SecurityNotification(exchange, SecurityNotification.EventType.LOGGED_OUT, account, mechanismName, true,
MESSAGES.userLoggedOut(account.getPrincipal().getName()), true));

this.account = null;
this.mechanismName = null;
}

/**
* To reduce allocations we use a custom linked list data structure
* @param <T>
*/
protected static final class Node<T> {
final T item;
Node<T> next;

private Node(T item) {
this.item = item;
}
}

}
124 changes: 9 additions & 115 deletions core/src/main/java/io/undertow/security/impl/SecurityContextImpl.java
Expand Up @@ -17,16 +17,12 @@
*/ */
package io.undertow.security.impl; package io.undertow.security.impl;


import static io.undertow.UndertowMessages.MESSAGES;
import io.undertow.UndertowMessages; import io.undertow.UndertowMessages;
import io.undertow.security.api.AuthenticationMechanism; import io.undertow.security.api.AuthenticationMechanism;
import io.undertow.security.api.AuthenticationMechanism.AuthenticationMechanismOutcome; import io.undertow.security.api.AuthenticationMechanism.AuthenticationMechanismOutcome;
import io.undertow.security.api.AuthenticationMechanism.ChallengeResult; import io.undertow.security.api.AuthenticationMechanism.ChallengeResult;
import io.undertow.security.api.AuthenticationMechanismContext; import io.undertow.security.api.AuthenticationMechanismContext;
import io.undertow.security.api.AuthenticationMode; import io.undertow.security.api.AuthenticationMode;
import io.undertow.security.api.NotificationReceiver;
import io.undertow.security.api.SecurityNotification;
import io.undertow.security.api.SecurityNotification.EventType;
import io.undertow.security.idm.Account; import io.undertow.security.idm.Account;
import io.undertow.security.idm.IdentityManager; import io.undertow.security.idm.IdentityManager;
import io.undertow.security.idm.PasswordCredential; import io.undertow.security.idm.PasswordCredential;
Expand All @@ -45,43 +41,30 @@
* @author <a href="mailto:darran.lofthouse@jboss.com">Darran Lofthouse</a> * @author <a href="mailto:darran.lofthouse@jboss.com">Darran Lofthouse</a>
* @author Stuart Douglas * @author Stuart Douglas
*/ */
public class SecurityContextImpl implements AuthenticationMechanismContext { public class SecurityContextImpl extends AbstractSecurityContext implements AuthenticationMechanismContext {


private static final RuntimePermission PERMISSION = new RuntimePermission("MODIFY_UNDERTOW_SECURITY_CONTEXT"); private static final RuntimePermission PERMISSION = new RuntimePermission("MODIFY_UNDERTOW_SECURITY_CONTEXT");


private AuthenticationState authenticationState = AuthenticationState.NOT_ATTEMPTED;
private final AuthenticationMode authenticationMode; private final AuthenticationMode authenticationMode;
private boolean authenticationRequired;
private String programaticMechName = "Programatic"; private String programaticMechName = "Programatic";
private AuthenticationState authenticationState = AuthenticationState.NOT_ATTEMPTED;
private final HttpServerExchange exchange;
/** /**
* the authentication mechanisms. Note that in order to reduce the allocation of list and iterator structures * the authentication mechanisms. Note that in order to reduce the allocation of list and iterator structures
* we use a custom linked list structure. * we use a custom linked list structure.
*/ */
private Node<AuthenticationMechanism> authMechanisms = null; private Node<AuthenticationMechanism> authMechanisms = null;
private final IdentityManager identityManager; private final IdentityManager identityManager;
private Node<NotificationReceiver> notificationReceivers = null;


// Maybe this will need to be a custom mechanism that doesn't exchange tokens with the client but will then
// be configured to either associate with the connection, the session or some other arbitrary whatever.
//
// Do we want multiple to be supported or just one? Maybe extend the AuthenticationMechanism to allow
// it to be identified and called.

private String mechanismName;
private Account account;

// TODO - Why two constructors? Maybe the first can do.


public SecurityContextImpl(final HttpServerExchange exchange, final IdentityManager identityManager) { public SecurityContextImpl(final HttpServerExchange exchange, final IdentityManager identityManager) {
this(exchange, AuthenticationMode.PRO_ACTIVE, identityManager); this(exchange, AuthenticationMode.PRO_ACTIVE, identityManager);
} }


public SecurityContextImpl(final HttpServerExchange exchange, final AuthenticationMode authenticationMode, final IdentityManager identityManager) { public SecurityContextImpl(final HttpServerExchange exchange, final AuthenticationMode authenticationMode, final IdentityManager identityManager) {
super(exchange);
this.authenticationMode = authenticationMode; this.authenticationMode = authenticationMode;
this.identityManager = identityManager; this.identityManager = identityManager;
this.exchange = exchange;
if (System.getSecurityManager() != null) { if (System.getSecurityManager() != null) {
System.getSecurityManager().checkPermission(PERMISSION); System.getSecurityManager().checkPermission(PERMISSION);
} }
Expand Down Expand Up @@ -147,33 +130,18 @@ private boolean authTransitionRequired() {
case NOT_ATTEMPTED: case NOT_ATTEMPTED:
// There has been no attempt to authenticate the current request so do so either if required or if we are set to // There has been no attempt to authenticate the current request so do so either if required or if we are set to
// be pro-active. // be pro-active.
return authenticationRequired || authenticationMode == AuthenticationMode.PRO_ACTIVE; return isAuthenticationRequired() || authenticationMode == AuthenticationMode.PRO_ACTIVE;
case ATTEMPTED: case ATTEMPTED:
// To be ATTEMPTED we know it was not AUTHENTICATED so if it is required we need to transition to send the // To be ATTEMPTED we know it was not AUTHENTICATED so if it is required we need to transition to send the
// challenges. // challenges.
return authenticationRequired; return isAuthenticationRequired();
default: default:
// At this point the state would either be AUTHENTICATED or CHALLENGE_SENT - either of which mean no further // At this point the state would either be AUTHENTICATED or CHALLENGE_SENT - either of which mean no further
// transitions applicable for this request. // transitions applicable for this request.
return false; return false;
} }
} }


@Override
public void setAuthenticationRequired() {
authenticationRequired = true;
}

@Override
public boolean isAuthenticationRequired() {
return authenticationRequired;
}

@Override
public boolean isAuthenticated() {
return authenticationState == AuthenticationState.AUTHENTICATED;
}

/** /**
* Set the name of the mechanism used for authentication to be reported if authentication was handled programatically. * Set the name of the mechanism used for authentication to be reported if authentication was handled programatically.
* *
Expand All @@ -183,14 +151,6 @@ public void setProgramaticMechName(final String programaticMechName) {
this.programaticMechName = programaticMechName; this.programaticMechName = programaticMechName;
} }


/**
* @return The name of the mechanism used to authenticate the request.
*/
@Override
public String getMechanismName() {
return mechanismName;
}

@Override @Override
public void addAuthenticationMechanism(final AuthenticationMechanism handler) { public void addAuthenticationMechanism(final AuthenticationMechanism handler) {
// TODO - Do we want to change this so we can ensure the mechanisms are not modifiable mid request? // TODO - Do we want to change this so we can ensure the mechanisms are not modifiable mid request?
Expand Down Expand Up @@ -218,11 +178,7 @@ public List<AuthenticationMechanism> getAuthenticationMechanisms() {
} }


@Override @Override
public Account getAuthenticatedAccount() { @Deprecated
return account;
}

@Override
public IdentityManager getIdentityManager() { public IdentityManager getIdentityManager() {
return identityManager; return identityManager;
} }
Expand Down Expand Up @@ -255,72 +211,10 @@ public Account run() {


@Override @Override
public void logout() { public void logout() {
if (!isAuthenticated()) { super.logout();
return;
}
sendNoticiation(new SecurityNotification(exchange, SecurityNotification.EventType.LOGGED_OUT, account, mechanismName, true,
MESSAGES.userLoggedOut(account.getPrincipal().getName()), true));

this.account = null;
this.mechanismName = null;
this.authenticationState = AuthenticationState.NOT_ATTEMPTED; this.authenticationState = AuthenticationState.NOT_ATTEMPTED;
} }


@Override
public void authenticationComplete(Account account, String mechanism, final boolean cachingRequired) {
authenticationComplete(account, mechanism, false, cachingRequired);
}

protected void authenticationComplete(Account account, String mechanism, boolean programatic, final boolean cachingRequired) {
this.account = account;
this.mechanismName = mechanism;

sendNoticiation(new SecurityNotification(exchange, EventType.AUTHENTICATED, account, mechanism, programatic,
MESSAGES.userAuthenticated(account.getPrincipal().getName()), cachingRequired));
}

@Override
public void authenticationFailed(String message, String mechanism) {
sendNoticiation(new SecurityNotification(exchange, EventType.FAILED_AUTHENTICATION, null, mechanism, false, message, true));
}

private void sendNoticiation(final SecurityNotification notification) {
Node<NotificationReceiver> cur = notificationReceivers;
while (cur != null) {
cur.item.handleNotification(notification);
cur = cur.next;
}
}

@Override
public void registerNotificationReceiver(NotificationReceiver receiver) {
if(notificationReceivers == null) {
notificationReceivers = new Node<>(receiver);
} else {
Node<NotificationReceiver> cur = notificationReceivers;
while (cur.next != null) {
cur = cur.next;
}
cur.next = new Node<>(receiver);
}
}

@Override
public void removeNotificationReceiver(NotificationReceiver receiver) {
Node<NotificationReceiver> cur = notificationReceivers;
if(receiver.equals(cur.item)) {
notificationReceivers = cur.next;
} else {
Node<NotificationReceiver> old = cur;
while (cur.next != null) {
cur = cur.next;
if(receiver.equals(cur.item)) {
old.next = cur.next;
}
old = cur;
}
}
}


private class AuthAttempter { private class AuthAttempter {


Expand Down

0 comments on commit fe2b72e

Please sign in to comment.