Skip to content

Commit

Permalink
Merge pull request #310 from arjantijms/309_HttpAuthenticationMechani…
Browse files Browse the repository at this point in the history
…smHandler

Introduce HttpAuthenticationMechanismHandler for #309
  • Loading branch information
arjantijms authored Feb 22, 2024
2 parents 482e8c8 + 479fdeb commit 0146f9d
Show file tree
Hide file tree
Showing 10 changed files with 673 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
/*
* Copyright (c) 2024 Contributors to Eclipse Foundation.
* Copyright (c) 2015, 2020 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/

package jakarta.security.enterprise.authentication.mechanism.http;

import static jakarta.security.enterprise.AuthenticationStatus.SUCCESS;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.security.enterprise.AuthenticationException;
import jakarta.security.enterprise.AuthenticationStatus;
import jakarta.servlet.Filter;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;


/**
* <code>HttpAuthenticationMechanismHandler</code> is a mechanism for obtaining a caller's credentials in some way,
* using the HTTP protocol where necessary, by consulting a set of one or more {@link HttpAuthenticationMechanism}s.
*
* <p>
* This is a special variant of an {@link HttpAuthenticationMechanism} intended for coordination
* between multiple {@link HttpAuthenticationMechanism}s. Implementations are therefore expected and
* encouraged to delegate actually obtaining the caller's credential to an actual {@link HttpAuthenticationMechanism}.
* This is however <b>not</b> required and implementations can do as they choose.
*
* <p>
* Implementations of Jakarta Security <b>must</b> supply a default implementation of the
* {@code HttpAuthenticationMechanismHandler}. This implementation must be {@link ApplicationScoped} and this implementation
* <b>must</b> behave as described below:
*
* <ol>
* <li> Before servicing any calls as defined by this interface, the implementation must check if there is more than
* one enabled bean of type {@code HttpAuthenticationMechanism} available.
* </li>
* <li> If there is more than one enabled bean of type {@code HttpAuthenticationMechanism} available, the implementation
* must throw an {@link IllegalStateException}.
* </li>
* <li>
* If there is one enabled bean of type {@code HttpAuthenticationMechanism} available, the implementation
* must remember this one enabled bean.
* </li>
* <li>
* When servicing any calls as defined by this interface, the implementation must call the method in the
* {@code HttpAuthenticationMechanism} bean with the same name and arguments, and where applicable return
* the result from that call.
* </li>
* </ol>
*
* <p>
* Applications do not need to supply an {@code HttpAuthenticationMechanismHandler} unless application-specific
* behavior is desired.
*/
public interface HttpAuthenticationMechanismHandler {

/**
* Authenticate an HTTP request.
*
* <p>
* This method is called in response to an HTTP client request for a resource, and is always invoked
* <strong>before</strong> any {@link Filter} or {@link HttpServlet}. Additionally this method is called
* in response to {@link HttpServletRequest#authenticate(HttpServletResponse)}
*
* <p>
* Note that by default this method is <strong>always</strong> called for every request, independent of whether
* the request is to a protected or non-protected resource, or whether a caller was successfully authenticated
* before within the same HTTP session or not.
*
* <p>
* A CDI/Interceptor spec interceptor can be used to prevent calls to this method if needed.
* See {@link AutoApplySession} and {@link RememberMe} for two examples.
*
* @param request contains the request the client has made
* @param response contains the response that will be send to the client
* @param httpMessageContext context for interacting with the container
* @return the completion status of the processing performed by this method
* @throws AuthenticationException when the processing failed
*/
AuthenticationStatus validateRequest(HttpServletRequest request, HttpServletResponse response, HttpMessageContext httpMessageContext) throws AuthenticationException;

/**
* Secure the response, optionally.
*
* <p>
* This method is called to allow for any post processing to be done on the request, and is always invoked
* <strong>after</strong> any {@link Filter} or {@link HttpServlet}.
*
* <p>
* Note that this method is only called when a (Servlet) resource has indeed been invoked, i.e. if a previous call
* to <code>validateRequest</code> that was invoked before any {@link Filter} or {@link HttpServlet} returned SUCCESS.
*
* @param request contains the request the client has made
* @param response contains the response that will be send to the client
* @param httpMessageContext context for interacting with the container
* @return the completion status of the processing performed by this method
* @throws AuthenticationException when the processing failed
*/
default AuthenticationStatus secureResponse(HttpServletRequest request, HttpServletResponse response, HttpMessageContext httpMessageContext) throws AuthenticationException {
return SUCCESS;
}

/**
* Remove mechanism specific principals and credentials from the subject and any other state the mechanism
* might have used.
*
* <p>
* This method is called in response to {@link HttpServletRequest#logout()} and gives the authentication mechanism
* the option to remove any state associated with an earlier established authenticated identity. For example, an
* authentication mechanism that stores state within a cookie can send remove that cookie here.
*
* @param request contains the request the client has made
* @param response contains the response that will be send to the client
* @param httpMessageContext context for interacting with the container
*/
default void cleanSubject(HttpServletRequest request, HttpServletResponse response, HttpMessageContext httpMessageContext) {
httpMessageContext.cleanClientSubject();
}

}
47 changes: 47 additions & 0 deletions tck/app-custom-authentication-mechanism-handler/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (c) 2024 Contributors to the Eclipse Foundation.
This program and the accompanying materials are made available under the
terms of the Eclipse Public License v. 2.0, which is available at
http://www.eclipse.org/legal/epl-2.0.
This Source Code may also be made available under the following Secondary
Licenses when the conditions for such availability set forth in the
Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
version 2 with the GNU Classpath Exception, which is available at
https://www.gnu.org/software/classpath/license.html.
SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
-->

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>org.eclipse.ee4j.security.tck</groupId>
<artifactId>jakarta-security-tck</artifactId>
<version>4.0.0-SNAPSHOT</version>
</parent>

<artifactId>app-custom-authentication-mechanism-handler</artifactId>
<packaging>war</packaging>

<properties>
<failOnMissingWebXml>false</failOnMissingWebXml>
</properties>

<dependencies>
<dependency>
<groupId>org.eclipse.ee4j.security.tck</groupId>
<artifactId>common</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>

<build>
<finalName>app-custom-authentication-mechanism-handler</finalName>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* Copyright (c) 2024 Contributors to the Eclipse Foundation.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package ee.jakarta.tck.security.test;

import static jakarta.interceptor.Interceptor.Priority.APPLICATION;

import jakarta.annotation.Priority;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Alternative;
import jakarta.inject.Inject;
import jakarta.security.enterprise.AuthenticationException;
import jakarta.security.enterprise.AuthenticationStatus;
import jakarta.security.enterprise.authentication.mechanism.http.HttpAuthenticationMechanismHandler;
import jakarta.security.enterprise.authentication.mechanism.http.HttpMessageContext;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

/**
* This HttpAuthenticationMechanismHandler overrides the default provided one and delegates
* requests to two individual authentication mechanisms depending on the request path.
*/
@Alternative
@Priority(APPLICATION)
@ApplicationScoped
public class CustomAuthenticationMechanismHandler implements HttpAuthenticationMechanismHandler {

@Inject
TestAuthenticationMechanism1 authenticationMechanism1;

@Inject
TestAuthenticationMechanism2 authenticationMechanism2;

@Override
public AuthenticationStatus validateRequest(HttpServletRequest request, HttpServletResponse response,
HttpMessageContext httpMessageContext) throws AuthenticationException {

if (getRequestRelativeURI(request).startsWith("/protectedServlet1")) {
return authenticationMechanism1.validateRequest(request, response, httpMessageContext);
}

return authenticationMechanism2.validateRequest(request, response, httpMessageContext);
}

public static String getRequestRelativeURI(HttpServletRequest request) {
return request.getRequestURI().substring(request.getContextPath().length());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* Copyright (c) 2024 Contributors to the Eclipse Foundation.
* Copyright (c) 2015, 2020 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/

package ee.jakarta.tck.security.test;

import jakarta.annotation.security.DeclareRoles;
import jakarta.inject.Inject;
import jakarta.security.enterprise.SecurityContext;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.HttpConstraint;
import jakarta.servlet.annotation.ServletSecurity;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
* Test Servlet that prints out the name of the authenticated caller and whether
* this caller is in any of the roles {foo, bar, kaz}
*
*/
@WebServlet("/protectedServlet1")
@ServletSecurity(@HttpConstraint(rolesAllowed = "foo"))
@DeclareRoles({"bar", "kaz"})
public class ProtectedServlet1 extends HttpServlet {

private static final long serialVersionUID = 1L;

@Inject
private SecurityContext securityContext;

@Override
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

response.getWriter().write("This is a servlet \n");

String webName = null;
if (request.getUserPrincipal() != null) {
webName = request.getUserPrincipal().getName();
}

response.getWriter().write("web username: " + webName + "\n");

response.getWriter().write("web user has role \"foo\": " + request.isUserInRole("foo") + "\n");
response.getWriter().write("web user has role \"bar\": " + request.isUserInRole("bar") + "\n");
response.getWriter().write("web user has role \"kaz\": " + request.isUserInRole("kaz") + "\n");

String contextName = null;
if (securityContext.getCallerPrincipal() != null) {
contextName = securityContext.getCallerPrincipal().getName();
}

response.getWriter().write("context username: " + contextName + "\n");

response.getWriter().write("context user has role \"foo\": " + securityContext.isCallerInRole("foo") + "\n");
response.getWriter().write("context user has role \"bar\": " + securityContext.isCallerInRole("bar") + "\n");
response.getWriter().write("context user has role \"kaz\": " + securityContext.isCallerInRole("kaz") + "\n");

response.getWriter().write("has access " + securityContext.hasAccessToWebResource("/servlets"));

}

}
Loading

0 comments on commit 0146f9d

Please sign in to comment.