Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for the HttpAuthenticationMechanismHandler #371

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 40 additions & 26 deletions impl/src/main/java/org/glassfish/soteria/cdi/CdiExtension.java
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/*
* Copyright (c) 2024 Contributors to the Eclipse Foundation.
* Copyright (c) 2015, 2020 Oracle and/or its affiliates and others.
* All rights reserved.
*
Expand Down Expand Up @@ -37,6 +38,7 @@
import jakarta.security.enterprise.authentication.mechanism.http.CustomFormAuthenticationMechanismDefinition;
import jakarta.security.enterprise.authentication.mechanism.http.FormAuthenticationMechanismDefinition;
import jakarta.security.enterprise.authentication.mechanism.http.HttpAuthenticationMechanism;
import jakarta.security.enterprise.authentication.mechanism.http.HttpAuthenticationMechanismHandler;
import jakarta.security.enterprise.authentication.mechanism.http.LoginToContinue;
import jakarta.security.enterprise.authentication.mechanism.http.OpenIdAuthenticationMechanismDefinition;
import jakarta.security.enterprise.authentication.mechanism.http.RememberMe;
Expand All @@ -62,6 +64,7 @@
import org.glassfish.soteria.identitystores.hash.Pbkdf2PasswordHashImpl;
import org.glassfish.soteria.mechanisms.BasicAuthenticationMechanism;
import org.glassfish.soteria.mechanisms.CustomFormAuthenticationMechanism;
import org.glassfish.soteria.mechanisms.DefaultHttpAuthenticationMechanismHandler;
import org.glassfish.soteria.mechanisms.FormAuthenticationMechanism;
import org.glassfish.soteria.mechanisms.OpenIdAuthenticationMechanism;
import org.glassfish.soteria.mechanisms.openid.OpenIdIdentityStore;
Expand All @@ -79,11 +82,8 @@ public class CdiExtension implements Extension {

private static final Logger LOGGER = Logger.getLogger(CdiExtension.class.getName());

// Note: for now use the highlander rule: "there can be only one" for
// authentication mechanisms.
// This could be extended later to support multiple
private List<Bean<IdentityStore>> identityStoreBeans = new ArrayList<>();
private Bean<HttpAuthenticationMechanism> authenticationMechanismBean;
private List<Bean<HttpAuthenticationMechanism>> authenticationMechanismBeans = new ArrayList<>();
private List<Bean<?>> extraBeans = new ArrayList<>();

private boolean httpAuthenticationMechanismFound;
Expand Down Expand Up @@ -162,20 +162,20 @@ public <T> void processBean(@Observes ProcessBean<T> eventIn, BeanManager beanMa
optionalBasicMechanism.ifPresent(basicAuthenticationMechanismDefinition -> {
logActivatedAuthenticationMechanism(BasicAuthenticationMechanismDefinition.class, beanClass);

authenticationMechanismBean = new CdiProducer<HttpAuthenticationMechanism>()
authenticationMechanismBeans.add(new CdiProducer<HttpAuthenticationMechanism>()
.scope(ApplicationScoped.class)
.types(Object.class, HttpAuthenticationMechanism.class, BasicAuthenticationMechanism.class)
.addToId(BasicAuthenticationMechanismDefinition.class)
.create(e -> new BasicAuthenticationMechanism(
BasicAuthenticationMechanismDefinitionAnnotationLiteral.eval(
basicAuthenticationMechanismDefinition)));
basicAuthenticationMechanismDefinition))));
});

Optional<FormAuthenticationMechanismDefinition> optionalFormMechanism = getAnnotation(beanManager, event.getAnnotated(), FormAuthenticationMechanismDefinition.class);
optionalFormMechanism.ifPresent(formAuthenticationMechanismDefinition -> {
logActivatedAuthenticationMechanism(FormAuthenticationMechanismDefinition.class, beanClass);

authenticationMechanismBean = new CdiProducer<HttpAuthenticationMechanism>()
authenticationMechanismBeans.add(new CdiProducer<HttpAuthenticationMechanism>()
.scope(ApplicationScoped.class)
.types(Object.class, HttpAuthenticationMechanism.class)
.addToId(FormAuthenticationMechanismDefinition.class)
Expand All @@ -186,14 +186,14 @@ public <T> void processBean(@Observes ProcessBean<T> eventIn, BeanManager beanMa
LoginToContinueAnnotationLiteral.eval(formAuthenticationMechanismDefinition.loginToContinue()));

return authMethod;
});
}));
});

Optional<CustomFormAuthenticationMechanismDefinition> optionalCustomFormMechanism = getAnnotation(beanManager, event.getAnnotated(), CustomFormAuthenticationMechanismDefinition.class);
optionalCustomFormMechanism.ifPresent(customFormAuthenticationMechanismDefinition -> {
logActivatedAuthenticationMechanism(CustomFormAuthenticationMechanismDefinition.class, beanClass);

authenticationMechanismBean = new CdiProducer<HttpAuthenticationMechanism>()
authenticationMechanismBeans.add(new CdiProducer<HttpAuthenticationMechanism>()
.scope(ApplicationScoped.class)
.types(Object.class, HttpAuthenticationMechanism.class)
.addToId(CustomFormAuthenticationMechanismDefinition.class)
Expand All @@ -204,7 +204,7 @@ public <T> void processBean(@Observes ProcessBean<T> eventIn, BeanManager beanMa
LoginToContinueAnnotationLiteral.eval(customFormAuthenticationMechanismDefinition.loginToContinue()));

return authMethod;
});
}));
});

Optional<OpenIdAuthenticationMechanismDefinition> opentionalOpenIdMechanism = getAnnotation(beanManager, event.getAnnotated(), OpenIdAuthenticationMechanismDefinition.class);
Expand All @@ -213,11 +213,11 @@ public <T> void processBean(@Observes ProcessBean<T> eventIn, BeanManager beanMa

validateOpenIdParametersFormat(definition);

authenticationMechanismBean = new CdiProducer<HttpAuthenticationMechanism>()
authenticationMechanismBeans.add(new CdiProducer<HttpAuthenticationMechanism>()
.scope(ApplicationScoped.class)
.types(HttpAuthenticationMechanism.class)
.addToId(OpenIdAuthenticationMechanism.class)
.create(e -> getBeanReference(OpenIdAuthenticationMechanism.class));
.create(e -> getBeanReference(OpenIdAuthenticationMechanism.class)));

identityStoreBeans.add(new CdiProducer<IdentityStore>()
.scope(ApplicationScoped.class)
Expand Down Expand Up @@ -249,27 +249,26 @@ public void afterBean(final @Observes AfterBeanDiscovery afterBeanDiscovery, Bea
BeanDecorator decorator = SoteriaServiceProviders.getServiceProvider(BeanDecorator.class);
WebXmlLoginConfig loginConfig = SoteriaServiceProviders.getServiceProvider(WebXmlLoginConfig.class);

if (!identityStoreBeans.isEmpty()) {
for (Bean<IdentityStore> identityStoreBean : identityStoreBeans) {
afterBeanDiscovery.addBean(
decorator.decorateBean(identityStoreBean, IdentityStore.class, beanManager));
}
}
if (!identityStoreBeans.isEmpty()) {
for (Bean<IdentityStore> identityStoreBean : identityStoreBeans) {
afterBeanDiscovery.addBean(decorator.decorateBean(identityStoreBean, IdentityStore.class, beanManager));
}
}

if (authenticationMechanismBean == null && loginConfig.getAuthMethod() != null) {
if (authenticationMechanismBeans.isEmpty() && loginConfig.getAuthMethod() != null) {

if ("basic".equalsIgnoreCase(loginConfig.getAuthMethod())) {
authenticationMechanismBean = new CdiProducer<HttpAuthenticationMechanism>()
authenticationMechanismBeans.add(new CdiProducer<HttpAuthenticationMechanism>()
.scope(ApplicationScoped.class)
.types(Object.class, HttpAuthenticationMechanism.class, BasicAuthenticationMechanism.class)
.addToId(BasicAuthenticationMechanismDefinition.class)
.create(e ->
new BasicAuthenticationMechanism(
new BasicAuthenticationMechanismDefinitionAnnotationLiteral(loginConfig.getRealmName())));
new BasicAuthenticationMechanismDefinitionAnnotationLiteral(loginConfig.getRealmName()))));

httpAuthenticationMechanismFound = true;
} else if ("form".equalsIgnoreCase(loginConfig.getAuthMethod())) {
authenticationMechanismBean = new CdiProducer<HttpAuthenticationMechanism>()
authenticationMechanismBeans.add(new CdiProducer<HttpAuthenticationMechanism>()
.scope(ApplicationScoped.class)
.types(Object.class, HttpAuthenticationMechanism.class)
.addToId(FormAuthenticationMechanismDefinition.class)
Expand All @@ -284,14 +283,15 @@ public void afterBean(final @Observes AfterBeanDiscovery afterBeanDiscovery, Bea
);

return authMethod;
});
}));
httpAuthenticationMechanismFound = true;
}
}

if (authenticationMechanismBean != null) {
afterBeanDiscovery.addBean(
decorator.decorateBean(authenticationMechanismBean, HttpAuthenticationMechanism.class, beanManager));
if (!authenticationMechanismBeans.isEmpty()) {
for (Bean<HttpAuthenticationMechanism> authenticationMechanismBean : authenticationMechanismBeans) {
afterBeanDiscovery.addBean(decorator.decorateBean(authenticationMechanismBean, HttpAuthenticationMechanism.class, beanManager));
}
}

for (Bean<?> bean : extraBeans) {
Expand Down Expand Up @@ -324,6 +324,20 @@ public void afterBean(final @Observes AfterBeanDiscovery afterBeanDiscovery, Bea
}),
IdentityStoreHandler.class,
beanManager));

afterBeanDiscovery.addBean(
decorator.decorateBean(
new CdiProducer<HttpAuthenticationMechanismHandler>()
.scope(ApplicationScoped.class)
.types(Object.class, HttpAuthenticationMechanismHandler.class)
.addToId(IdentityStoreHandler.class)
.create(e -> {
DefaultHttpAuthenticationMechanismHandler defaultHttpAuthenticationMechanismHandler = new DefaultHttpAuthenticationMechanismHandler();
defaultHttpAuthenticationMechanismHandler.init();
return defaultHttpAuthenticationMechanismHandler;
}),
HttpAuthenticationMechanismHandler.class,
beanManager));
}

public boolean isHttpAuthenticationMechanismFound() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/*
* 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
Expand All @@ -16,24 +17,20 @@

package org.glassfish.soteria.cdi;

import jakarta.security.enterprise.CallerPrincipal;
import jakarta.security.enterprise.credential.Credential;
import jakarta.security.enterprise.identitystore.CredentialValidationResult;
import jakarta.security.enterprise.identitystore.IdentityStore;
import jakarta.security.enterprise.identitystore.IdentityStoreHandler;

import static java.util.Comparator.comparing;
import static java.util.stream.Collectors.toList;
import static jakarta.security.enterprise.identitystore.CredentialValidationResult.INVALID_RESULT;
import static jakarta.security.enterprise.identitystore.CredentialValidationResult.NOT_VALIDATED_RESULT;
import static jakarta.security.enterprise.identitystore.CredentialValidationResult.Status.VALID;
import static jakarta.security.enterprise.identitystore.CredentialValidationResult.Status.INVALID;
import static jakarta.security.enterprise.identitystore.CredentialValidationResult.Status.VALID;
import static jakarta.security.enterprise.identitystore.IdentityStore.ValidationType.PROVIDE_GROUPS;
import static jakarta.security.enterprise.identitystore.IdentityStore.ValidationType.VALIDATE;
import static java.util.Comparator.comparing;
import static java.util.stream.Collectors.toList;
import static org.glassfish.soteria.cdi.CdiUtils.getBeanReferencesByType;

import java.security.AccessController;
import java.security.PrivilegedAction;
import jakarta.security.enterprise.credential.Credential;
import jakarta.security.enterprise.identitystore.CredentialValidationResult;
import jakarta.security.enterprise.identitystore.IdentityStore;
import jakarta.security.enterprise.identitystore.IdentityStoreHandler;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
Expand Down Expand Up @@ -86,9 +83,8 @@ else if (validationResult.getStatus() == INVALID) {
if (isGotAnInvalidResult) {
return INVALID_RESULT;
}
else {
return NOT_VALIDATED_RESULT;
}

return NOT_VALIDATED_RESULT;
}

Set<String> groups = new HashSet<>();
Expand All @@ -101,15 +97,9 @@ else if (validationResult.getStatus() == INVALID) {

// Ask all stores that were configured for group providing only to get the groups for the
// authenticated caller
CredentialValidationResult finalResult = validationResult; // compiler didn't like validationResult in the enclosed scope
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
for (IdentityStore authorizationIdentityStore : authorizationIdentityStores) {
groups.addAll(authorizationIdentityStore.getCallerGroups(finalResult));
}
return null;
}
});
for (IdentityStore authorizationIdentityStore : authorizationIdentityStores) {
groups.addAll(authorizationIdentityStore.getCallerGroups(validationResult));
}

return new CredentialValidationResult(
validationResult.getIdentityStoreId(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* 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 org.glassfish.soteria.mechanisms;

import static org.glassfish.soteria.cdi.CdiUtils.getBeanReferencesByType;

import jakarta.security.enterprise.AuthenticationException;
import jakarta.security.enterprise.AuthenticationStatus;
import jakarta.security.enterprise.authentication.mechanism.http.HttpAuthenticationMechanism;
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;
import java.util.List;

public class DefaultHttpAuthenticationMechanismHandler implements HttpAuthenticationMechanismHandler {

private HttpAuthenticationMechanism httpAuthenticationMechanism;

public void init() {
List<HttpAuthenticationMechanism> mechanisms = getBeanReferencesByType(HttpAuthenticationMechanism.class, false);
if (mechanisms.size() > 1) {
throw new IllegalStateException("");
}

httpAuthenticationMechanism = mechanisms.get(0);

}

@Override
public AuthenticationStatus validateRequest(HttpServletRequest request, HttpServletResponse response,
HttpMessageContext httpMessageContext) throws AuthenticationException {
return httpAuthenticationMechanism.validateRequest(request, response, httpMessageContext);
}

@Override
public AuthenticationStatus secureResponse(HttpServletRequest request, HttpServletResponse response,
HttpMessageContext httpMessageContext) throws AuthenticationException {
return httpAuthenticationMechanism.secureResponse(request, response, httpMessageContext);
}

@Override
public void cleanSubject(HttpServletRequest request, HttpServletResponse response, HttpMessageContext httpMessageContext) {
httpAuthenticationMechanism.cleanSubject(request, response, httpMessageContext);
}

}
Loading