Skip to content

Commit

Permalink
Add authentication origin information to logged user context. (#310)
Browse files Browse the repository at this point in the history
  • Loading branch information
Luis-Cruz authored Nov 28, 2017
1 parent 4a96f93 commit 6554bc0
Show file tree
Hide file tree
Showing 8 changed files with 193 additions and 43 deletions.
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

package org.fenixedu.bennu.cas.client.api;

import java.io.UnsupportedEncodingException;
Expand Down Expand Up @@ -75,7 +76,7 @@ public Response returnFromCAS(@QueryParam("ticket") String ticket, @PathParam("c

String username = validator.validate(ticket, requestURL).getPrincipal().getName();
User user = getUser(username);
Authenticate.login(request, response, user);
Authenticate.login(request, response, user, "CAS Authentication");
logger.trace("Logged in user {}, redirecting to {}", username, actualCallback);
} catch (TicketValidationException | AuthorizationException e) {
logger.debug(e.getMessage(), e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public JsonElement login(@Context HttpServletRequest request, @Context HttpServl
if (CoreConfiguration.getConfiguration().localLoginEnabled()) {
User user = User.findByUsername(username);
if (user != null && (CoreConfiguration.getConfiguration().developmentMode() || user.matchesPassword(password))) {
Authenticate.login(request, response, user);
Authenticate.login(request, response, user, "Local Username/Password");
} else {
throw AuthorizationException.authenticationFailed();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package org.fenixedu.bennu.core.domain;

import java.io.Serializable;
import java.time.LocalDateTime;

public class AuthenticationContext implements Serializable {

public class AuthenticationMethodEvent implements Serializable {

private static final long serialVersionUID = 1L;

private final String authenticationMethod;
private final LocalDateTime instant;

private AuthenticationMethodEvent(final String authenticationMethod, final LocalDateTime instant) {
this.authenticationMethod = authenticationMethod;
this.instant = instant;
}

public String getAuthenticationMethod() {
return authenticationMethod;
}

public LocalDateTime getInstant() {
return instant;
}

}

private static final long serialVersionUID = 1L;

private final User user;
private AuthenticationMethodEvent[] authenticationMethodEvents;

public AuthenticationContext(final User user, final String authenticationMethod) {
this.user = user;
authenticationMethodEvents = new AuthenticationMethodEvent[] { new AuthenticationMethodEvent(authenticationMethod, LocalDateTime.now()) };
}

public User getUser() {
return user;
}

public AuthenticationMethodEvent[] getAuthenticationMethodEvents() {
return copyAuthenticationMethodEvents(authenticationMethodEvents.length);
}

private void addAuthenticationMethodEvent(String authenticationMethod, LocalDateTime instant) {
final int l = authenticationMethodEvents.length;
authenticationMethodEvents = copyAuthenticationMethodEvents(l + 1);
authenticationMethodEvents[l] = new AuthenticationMethodEvent(authenticationMethod, instant);
}

public void addAuthenticationMethodEvent(final String authenticationMethod) {
addAuthenticationMethodEvent(authenticationMethod, LocalDateTime.now());
}

public void removeAuthenticationMethodEvent(final AuthenticationMethodEvent authenticationMethodEvent) {
int r = -1;
final int l = authenticationMethodEvents.length;
for (int i = 0; i < l; i++) {
if (authenticationMethodEvents[i] == authenticationMethodEvent) {
r = i;
break;
}
}
if (r >= 0) {
final AuthenticationMethodEvent[] newArray = new AuthenticationMethodEvent[l - 1];
for (int i = 0, j = 0; i < l; i++) {
if (i != r) {
newArray[j++] = authenticationMethodEvents[i];
}
}
authenticationMethodEvents = newArray;
}
}

private AuthenticationMethodEvent[] copyAuthenticationMethodEvents(final int size) {
final AuthenticationMethodEvent[] result = new AuthenticationMethodEvent[size];
System.arraycopy( authenticationMethodEvents, 0, result, 0, authenticationMethodEvents.length );
return result;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.fenixedu.bennu.core.domain.AuthenticationContext;
import org.fenixedu.bennu.core.domain.AuthenticationContext.AuthenticationMethodEvent;
import org.fenixedu.bennu.core.domain.User;
import org.fenixedu.bennu.core.domain.exceptions.AuthorizationException;
import org.fenixedu.bennu.core.i18n.I18NFilter;
Expand All @@ -35,9 +37,12 @@
public class Authenticate {
private static final Logger logger = LoggerFactory.getLogger(Authenticate.class);

@Deprecated
private static final String LOGGED_USER_ATTRIBUTE = "LOGGED_USER_ATTRIBUTE";

private static final InheritableThreadLocal<User> loggedUser = new InheritableThreadLocal<>();
private static final String LOGGED_USER_AUTHENTICATION_CONTEXT_ATTRIBUTE = "LOGGED_USER_AUTHENTICATION_CONTEXT_ATTRIBUTE";

private static final InheritableThreadLocal<AuthenticationContext> loggedUserContext = new InheritableThreadLocal<>();

private static final Collection<UserAuthenticationListener> userAuthenticationListeners = new ConcurrentLinkedQueue<>();

Expand All @@ -50,26 +55,37 @@ public class Authenticate {
* The response associated with the request that triggered the login
* @param user
* The user to log in
* @param authenticationMethod
* The authentication method used to login the user
* @return
* The logged in user
* @throws AuthorizationException
* If the provided user is {@code null} or if the user has its login expired
*/
public static User login(HttpServletRequest request, HttpServletResponse response, User user) {
public static User login(final HttpServletRequest request, final HttpServletResponse response, final User user, final String authenticationMethod) {
if (user == null || user.isLoginExpired()) {
throw AuthorizationException.authenticationFailed();
}

HttpSession session = request.getSession();
loggedUser.set(user);
session.setAttribute(LOGGED_USER_ATTRIBUTE, user);
final Locale preferredLocale = user.getProfile().getPreferredLocale();
if (preferredLocale != null) {
I18NFilter.updateLocale(preferredLocale, request, response);
AuthenticationContext authenticationContext = loggedUserContext.get();
if (authenticationContext == null) {
authenticationContext = new AuthenticationContext(user, authenticationMethod);
loggedUserContext.set(authenticationContext);

storeInSession(request, authenticationContext);
final Locale preferredLocale = user.getProfile().getPreferredLocale();
if (preferredLocale != null) {
I18NFilter.updateLocale(preferredLocale, request, response);
}

fireLoginListeners(request, response, authenticationContext);
logger.debug("Logged in user: " + user.getUsername());
} else if (authenticationContext.getUser() != user) {
throw AuthorizationException.authenticationFailed();
} else {
authenticationContext.addAuthenticationMethodEvent(authenticationMethod);
}

fireLoginListeners(request, response, user);
logger.debug("Logged in user: " + user.getUsername());

return user;
}
Expand All @@ -84,64 +100,98 @@ public static User login(HttpServletRequest request, HttpServletResponse respons
* @param response
* The response associated with the requires that triggered the logout
*/
public static void logout(HttpServletRequest request, HttpServletResponse response) {
HttpSession session = request.getSession(false);
public static void logout(final HttpServletRequest request, final HttpServletResponse response) {
final HttpSession session = request.getSession(false);
if (session != null) {
User user = (User) session.getAttribute(LOGGED_USER_ATTRIBUTE);
if (user != null) {
fireLogoutListeners(request, response, user);
final AuthenticationContext authenticationContext = (AuthenticationContext) session.getAttribute(LOGGED_USER_AUTHENTICATION_CONTEXT_ATTRIBUTE);
if (authenticationContext != null) {
final User user = authenticationContext.getUser();
if (user != null && FenixFramework.isDomainObjectValid(user)) {
fireLogoutListeners(request, response, authenticationContext);
}
}
session.invalidate();
}
loggedUser.set(null);
clear();
}

public static void mock(User user) {
loggedUser.set(user);
public static void mock(final User user, final String authenticationMethod) {
final AuthenticationContext authenticationContext = loggedUserContext.get();
if (authenticationContext == null || authenticationContext.getUser() != user) {
loggedUserContext.set(new AuthenticationContext(user, authenticationMethod));
} else {
authenticationContext.addAuthenticationMethodEvent(authenticationMethod);
}
}

public static void unmock() {
loggedUser.set(null);
clear();
}

public static User getUser() {
return loggedUser.get();
final AuthenticationContext context = loggedUserContext.get();
return context == null ? null : context.getUser();
}

public static boolean isLogged() {
return loggedUser.get() != null;
final AuthenticationContext context = loggedUserContext.get();
return context != null && context.getUser() != null;
}

static void updateFromSession(HttpSession session) {
User user = (User) (session == null ? null : session.getAttribute(LOGGED_USER_ATTRIBUTE));
if (user != null && FenixFramework.isDomainObjectValid(user)) {
loggedUser.set(user);
} else {
loggedUser.set(null);
public static AuthenticationContext getAuthenticationContext() {
return loggedUserContext.get();
}

private static void storeInSession(final HttpServletRequest request, final AuthenticationContext authenticationContext) {
final HttpSession session = request.getSession();
session.setAttribute(LOGGED_USER_ATTRIBUTE, authenticationContext.getUser());
session.setAttribute(LOGGED_USER_AUTHENTICATION_CONTEXT_ATTRIBUTE, authenticationContext);
}

static void updateFromSession(final HttpSession session) {
if (session != null) {
final AuthenticationContext authenticationContext = (AuthenticationContext) session.getAttribute(LOGGED_USER_AUTHENTICATION_CONTEXT_ATTRIBUTE);
if (authenticationContext != null) {
final User user = authenticationContext.getUser();
if (user != null && FenixFramework.isDomainObjectValid(user)) {
loggedUserContext.set(authenticationContext);
} else {
clear();
}
}
}
}

static void clear() {
loggedUser.set(null);
final AuthenticationContext context = loggedUserContext.get();
if (context != null) {
final AuthenticationMethodEvent[] events = context.getAuthenticationMethodEvents();
final int length = events.length;
if (length > 0) {
context.removeAuthenticationMethodEvent(events[length - 1]);
}
loggedUserContext.set(null);
}
}

public static void addUserAuthenticationListener(UserAuthenticationListener listener) {
public static void addUserAuthenticationListener(final UserAuthenticationListener listener) {
userAuthenticationListeners.add(listener);
}

public static void removeUserAuthenticationListener(UserAuthenticationListener listener) {
public static void removeUserAuthenticationListener(final UserAuthenticationListener listener) {
userAuthenticationListeners.remove(listener);
}

private static void fireLoginListeners(HttpServletRequest request, HttpServletResponse response, final User user) {
for (UserAuthenticationListener listener : userAuthenticationListeners) {
listener.onLogin(request, response, user);
private static void fireLoginListeners(final HttpServletRequest request, final HttpServletResponse response, final AuthenticationContext authenticationContext) {
for (final UserAuthenticationListener listener : userAuthenticationListeners) {
listener.onLogin(request, response, authenticationContext);
}
}

private static void fireLogoutListeners(HttpServletRequest request, HttpServletResponse response, final User user) {
for (UserAuthenticationListener listener : userAuthenticationListeners) {
listener.onLogout(request, response, user);
private static void fireLogoutListeners(final HttpServletRequest request, final HttpServletResponse response, final AuthenticationContext authenticationContext) {
for (final UserAuthenticationListener listener : userAuthenticationListeners) {
listener.onLogout(request, response, authenticationContext);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.fenixedu.bennu.core.domain.AuthenticationContext;
import org.fenixedu.bennu.core.domain.User;

/**
Expand All @@ -12,7 +13,21 @@
* @see Authenticate#removeUserAuthenticationListener(UserAuthenticationListener)
*/
public interface UserAuthenticationListener {
void onLogin(HttpServletRequest request, HttpServletResponse response, User user);

void onLogout(HttpServletRequest request, HttpServletResponse response, User user);
default void onLogin(HttpServletRequest request, HttpServletResponse response, AuthenticationContext context) {
onLogin(request, response, context.getUser());
}

default void onLogout(HttpServletRequest request, HttpServletResponse response, AuthenticationContext context) {
onLogout(request, response, context.getUser());
}

default void onLogin(HttpServletRequest request, HttpServletResponse response, User user) {

}

default void onLogout(HttpServletRequest request, HttpServletResponse response, User user) {

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ public void filter(ContainerRequestContext requestContext) throws IOException {
return;
}

Authenticate.mock(foundUser);
Authenticate.mock(foundUser, "OAuth Access Token");
} else {
sendError(requestContext, "accessTokenInvalidFormat", "Access Token not recognized.");
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* Login Providers allow users to select the way they prefer to authenticate into the application.
*
* After the user has been successfuly identified, providers may use the
* {@link Authenticate#login(HttpServletRequest, HttpServletResponse, org.fenixedu.bennu.core.domain.User)} method to authenticate
* {@link Authenticate#login(HttpServletRequest, HttpServletResponse, org.fenixedu.bennu.core.domain.User, String)} method to authenticate
* the user with the application.
*
* Upon login, the provider is expected to redirect the user to the desired callback URL. If login is unsuccessful, the user
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1051,7 +1051,7 @@ public void testServiceApplicationOAuthAccessProvider() {

Assert.assertEquals("this is an endpoint with TEST scope: testServiceApplicationOAuthAccessProvider", result);

Authenticate.mock(user);
Authenticate.mock(user, "OAuth Access Token");

JsonArray authorizations =
target("bennu-oauth").path("authorizations").request().get(JsonElement.class).getAsJsonArray();
Expand Down

0 comments on commit 6554bc0

Please sign in to comment.