Skip to content

Commit

Permalink
Merged in pac4j_update (pull request #669)
Browse files Browse the repository at this point in the history
Pac4j update

* NOJIRA

Updates for Pac4J changes

* NOJIRA: Pac4J libs update

Updates for Pac4J updates to current release

* NOJIRA: Pac4J libs update

Updates for Pac4J updates to current release

* NOJIRA: Pac4J libs update

Updates for Pac4J updates to current release
  • Loading branch information
chasegawa committed Jan 30, 2024
1 parent 2ce50cb commit 3a145e1
Show file tree
Hide file tree
Showing 16 changed files with 323 additions and 109 deletions.
4 changes: 3 additions & 1 deletion backend/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ repositories {
}

tasks.withType(War) {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
duplicatesStrategy = DuplicatesStrategy.INCLUDE
}

configurations.all {
Expand Down Expand Up @@ -231,9 +231,11 @@ dependencies {
// CSV file support
implementation "com.opencsv:opencsv:${project.'opencsvVersion'}", {
exclude group: 'commons-collections'
exclude group: 'commons-lang3'
}

implementation "org.apache.commons:commons-collections4:${project.'commonsCollections4Version'}"
implementation "org.apache.commons:commons-lang3:${project.'commonsLang3Version'}"

// Envers for persistent entities versioning
implementation "org.hibernate:hibernate-envers:${project.'hibernateVersion'}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
@EnableJpaAuditing
@EnableScheduling
@EnableAsync
@OpenAPIDefinition(info=@Info(description = "The Shibboleth UI is specifically designed to help manage and edit metadata-driven configuration support for Shibboleth", title = "Shibboleth UI API", version = "1.0"))
@OpenAPIDefinition(info=@Info(description = "The SAML Metadata Configuration Manager is specifically designed to help manage and edit metadata-driven configuration support", title = "SAML Metadata Configuration Manager API", version = "2.0"))
public class ShibbolethUiApplication extends SpringBootServletInitializer {

private static final Logger logger = LoggerFactory.getLogger(ShibbolethUiApplication.class);
Expand Down
2 changes: 1 addition & 1 deletion backend/src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ shibui.pac4j-enabled=false
#environment variables must be set for beacon publisher to be used (the ones that are set when running shib-ui in
#docker container
shibui.beacon.enabled=true
shibui.beacon.productName=ShibUi
shibui.beacon.productName=SAML Metadata Configuration Manager
shibui.beacon.installationID=UNICON-SHIBUI-TESTING
shibui.beacon.url=http://collector.testbed.tier.internet2.edu:5001
#shibui.beacon.send.cron=0 59 3 * * ?
Expand Down
7 changes: 4 additions & 3 deletions gradle.properties
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
name=shibui
group=edu.internet2.tier.shibboleth.admin.ui
version=2.0.0-BETA-R2
version=2.0.0-BETA-RC5

### library versions ###
## As of 2-23-23
commonsCollections4Version=4.4
commonsLang3Version=3.14.0
comsunjaxbVersion=4.0.2
cryptacularVersion=1.2.5
groovyVersion=4.0.9
Expand All @@ -15,8 +16,8 @@ lombokVersion=6.6.2
nashornVersion=15.4
opencsvVersion=5.7.1
opensamlVersion=5.0.0
pac4JVersion=5.7.0
pac4jSpringSecurityVersion=9.0.0
pac4JVersion=6.0.0
pac4jSpringSecurityVersion=10.0.0
seleneseRunnerVersion=4.3.0
shedlockVersion=5.2.0
shibbolethVersion=5.0.0
Expand Down
3 changes: 2 additions & 1 deletion pac4j-module/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ dependencies {
implementation "org.pac4j:spring-security-pac4j:${project.'pac4jSpringSecurityVersion'}"
implementation "org.pac4j:pac4j-core:${project.'pac4JVersion'}"
implementation "org.pac4j:pac4j-http:${project.'pac4JVersion'}"
implementation "org.pac4j:pac4j-jakartaee:${project.'pac4JVersion'}"
implementation "org.pac4j:pac4j-saml:${project.'pac4JVersion'}", {
// opensaml libraries are provided
exclude group: 'org.opensaml'
Expand All @@ -57,7 +58,7 @@ dependencies {
implementation "org.opensaml:opensaml-storage-impl:${project.'opensamlVersion'}"
implementation "org.apache.commons:commons-collections4:${project.'commonsCollections4Version'}"
// As listed by - https://github.com/pac4j/spring-security-pac4j/wiki/Dependencies
implementation "org.pac4j:jakartaee-pac4j:7.1.0"
//implementation "org.pac4j:jakartaee-pac4j:7.1.0" changed to pac4j-jakartaee and released with core numbering...

testImplementation "jakarta.servlet:jakarta.servlet-api:6.0.0"
testImplementation project(':backend')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.pac4j.core.context.CallContext;
import org.pac4j.core.context.WebContext;
import org.pac4j.core.context.session.SessionStore;
import org.pac4j.core.matching.matcher.Matcher;
Expand Down Expand Up @@ -106,9 +107,9 @@ public void destroy() {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
WebContext context = new JEEContext((HttpServletRequest)request, (HttpServletResponse)response);
Optional optionalSession = JEESessionStore.INSTANCE.buildFromTrackableSession(context, ((HttpServletRequest) request).getSession());
Optional optionalSession = new JEESessionStore().buildFromTrackableSession(context, ((HttpServletRequest) request).getSession());
SessionStore session = optionalSession.isPresent() ? (SessionStore) optionalSession.get() : null;
if (!matcher.matches(context, session)) {
if (!matcher.matches(new CallContext(context, session))) {
return;
}
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import edu.internet2.tier.shibboleth.admin.ui.security.model.User;
import edu.internet2.tier.shibboleth.admin.ui.security.repository.UserRepository;
import org.pac4j.core.authorization.generator.AuthorizationGenerator;
import org.pac4j.core.context.CallContext;
import org.pac4j.core.context.WebContext;
import org.pac4j.core.context.session.SessionStore;
import org.pac4j.core.profile.UserProfile;
Expand All @@ -17,7 +18,7 @@ public LocalUserProfileAuthorizationGenerator(UserRepository userRepository) {
}

@Override
public Optional<UserProfile> generate(WebContext context, SessionStore sessionStore, UserProfile profile) {
public Optional<UserProfile> generate(CallContext callContext, UserProfile profile) {
Optional<User> user = userRepository.findByUsername(profile.getUsername());
user.ifPresent(u -> profile.addRole(u.getRole()));
return Optional.of(profile);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package net.unicon.shibui.pac4j;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import org.pac4j.core.profile.ProfileHelper;
import org.pac4j.core.profile.UserProfile;

import java.security.Principal;
import java.util.Collection;
import java.util.Optional;

/**
* FROM: https://github.com/pac4j/jee-pac4j/blob/master/jakartaee-pac4j/src/main/java/org/pac4j/jee/util/Pac4JHttpServletRequestWrapper.java
*/
public class Pac4JHttpServletRequestWrapper extends HttpServletRequestWrapper {
private Collection<UserProfile> profiles;

public Pac4JHttpServletRequestWrapper(final HttpServletRequest request, final Collection<UserProfile> profiles) {
super(request);
this.profiles = profiles;
}

@Override
public String getRemoteUser() {
return getPrincipal().map(p -> p.getName()).orElse(null);
}

private Optional<UserProfile> getProfile() {
return ProfileHelper.flatIntoOneProfile(profiles);
}

private Optional<Principal> getPrincipal() {
return getProfile().map(UserProfile::asPrincipal);
}

@Override
public Principal getUserPrincipal() {
return getPrincipal().orElse(null);
}

@Override
public boolean isUserInRole(String role) {
return this.profiles.stream().anyMatch(p -> p.getRoles().contains(role));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,14 @@
import edu.internet2.tier.shibboleth.admin.ui.security.service.UserService;
import lombok.extern.slf4j.Slf4j;
import net.unicon.shibui.pac4j.authenticator.ShibuiPac4JHeaderClientAuthenticator;
import net.unicon.shibui.pac4j.authenticator.ShibuiSAML2Authenticator;
import org.pac4j.core.client.Clients;
import org.pac4j.core.config.Config;
import org.pac4j.core.context.WebContext;
import org.pac4j.core.context.session.SessionStore;
import org.pac4j.core.matching.matcher.PathMatcher;
import org.pac4j.core.profile.ProfileManager;
import org.pac4j.core.profile.definition.CommonProfileDefinition;
import org.pac4j.core.profile.factory.ProfileManagerFactory;
import org.pac4j.http.client.direct.HeaderClient;
import org.pac4j.saml.client.SAML2Client;
import org.pac4j.saml.config.SAML2Configuration;
import org.pac4j.saml.credentials.authenticator.SAML2Authenticator;
import org.pac4j.springframework.security.profile.SpringSecurityProfileManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringBootConfiguration;
Expand Down Expand Up @@ -53,8 +48,7 @@ public LocalUserProfileAuthorizationGenerator saml2ModelAuthorizationGenerator(U
}

@Bean(name = "pac4j-config")
public Config config(final Pac4jConfigurationProperties pac4jConfigProps,
final LocalUserProfileAuthorizationGenerator saml2ModelAuthorizationGenerator) {
public Config pac4jConfig(final Pac4jConfigurationProperties pac4jConfigProps, final LocalUserProfileAuthorizationGenerator saml2ModelAuthorizationGenerator) {
log.info("**** Configuring PAC4J ");
final Config config = new Config();
final Clients clients = new Clients(pac4jConfigProps.getCallbackUrl());
Expand All @@ -63,7 +57,7 @@ public Config config(final Pac4jConfigurationProperties pac4jConfigProps,
PathMatcher pm = new PathMatcher();
pm.setExcludedPaths(Lists.newArrayList("/favicon.ico", "/unsecured/**/*", "/assets/**/*.png", "/static/**/*"));
config.addMatcher("exclude-paths-matcher", pm);
config.defaultProfileManagerFactory(new ProfileManagerFactory() {
config.setProfileManagerFactory(new ProfileManagerFactory() {
@Override
public ProfileManager apply(WebContext webContext, SessionStore sessionStore) {
return new SpringSecurityProfileManager(webContext, sessionStore);
Expand All @@ -82,15 +76,9 @@ public ProfileManager apply(WebContext webContext, SessionStore sessionStore) {
case "SAML2":
default:
log.info("**** Configuring PAC4J SAML2");
final SAML2Configuration saml2Config = buildSaml2ConfigFromPac4JConfiguration(pac4jConfigProps);


final SAML2Client saml2Client = new SAML2Client(saml2Config);
final ShibuiSAML2Client saml2Client = new ShibuiSAML2Client(pac4jConfigProps, userService);
saml2Client.setName(PAC4J_CLIENT_NAME);
saml2Client.addAuthorizationGenerator(saml2ModelAuthorizationGenerator);
SAML2Authenticator saml2Authenticator = new ShibuiSAML2Authenticator(saml2Config.getAttributeAsId(), saml2Config.getMappedAttributes(), userService);
saml2Authenticator.setProfileDefinition(new CommonProfileDefinition(p -> new BetterSAML2Profile(pac4jConfigProps.getSimpleProfileMapping())));
saml2Client.setAuthenticator(saml2Authenticator);
saml2Client.setCallbackUrl(pac4jConfigProps.getCallbackUrl());
saml2Client.init();

Expand All @@ -101,24 +89,6 @@ public ProfileManager apply(WebContext webContext, SessionStore sessionStore) {
return config;
}

private SAML2Configuration buildSaml2ConfigFromPac4JConfiguration(Pac4jConfigurationProperties pac4jConfigProps) {
SAML2Configuration saml2Config = new SAML2Configuration();
saml2Config.setKeystorePath(pac4jConfigProps.getKeystorePath());
saml2Config.setKeystorePassword(pac4jConfigProps.getKeystorePassword());
saml2Config.setPrivateKeyPassword(pac4jConfigProps.getPrivateKeyPassword());
saml2Config.setIdentityProviderMetadataPath(pac4jConfigProps.getIdentityProviderMetadataPath());
saml2Config.setMaximumAuthenticationLifetime(pac4jConfigProps.getMaximumAuthenticationLifetime());
saml2Config.setServiceProviderEntityId(pac4jConfigProps.getServiceProviderEntityId());
saml2Config.setServiceProviderMetadataPath(pac4jConfigProps.getServiceProviderMetadataPath());
saml2Config.setForceServiceProviderMetadataGeneration(pac4jConfigProps.isForceServiceProviderMetadataGeneration());
saml2Config.setWantsAssertionsSigned(pac4jConfigProps.isWantAssertionsSigned());
saml2Config.setAttributeAsId(pac4jConfigProps.getSimpleProfileMapping().getUsername());
saml2Config.setPostLogoutURL(pac4jConfigProps.getPostLogoutURL());
saml2Config.setAuthnRequestBindingType("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST");

return saml2Config;
}

@Bean
public ErrorPageRegistrar errorPageRegistrar() {
return this::registerErrorPages;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
import edu.internet2.tier.shibboleth.admin.ui.security.service.IRolesService;
import edu.internet2.tier.shibboleth.admin.ui.security.service.UserService;
import edu.internet2.tier.shibboleth.admin.ui.service.EmailService;
import jakarta.servlet.Filter;
import org.apache.commons.lang3.StringUtils;
import org.pac4j.core.authorization.authorizer.DefaultAuthorizers;
import org.pac4j.core.config.Config;
import org.pac4j.core.matching.matcher.Matcher;
import org.pac4j.jee.filter.SecurityFilter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
Expand Down Expand Up @@ -86,7 +86,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
logoutFilter.setCentralLogout(Boolean.TRUE);
logoutFilter.setDefaultUrl(pac4jConfigurationProperties.getPostLogoutURL());
logoutFilter.setDestroySession(true);
http.securityMatcher("/login*", "/logout").addFilterBefore(logoutFilter, BasicAuthenticationFilter.class);
http.securityMatcher("/login").addFilterBefore((Filter) logoutFilter, BasicAuthenticationFilter.class);
}

// add correct auth filter
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package net.unicon.shibui.pac4j;

import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import jakarta.servlet.http.HttpServletResponse;
import lombok.Getter;
import lombok.Setter;
import org.pac4j.core.adapter.FrameworkAdapter;
import org.pac4j.core.config.Config;
import org.pac4j.core.profile.ProfileHelper;
import org.pac4j.core.profile.UserProfile;
import org.pac4j.core.util.Pac4jConstants;
import org.pac4j.core.util.security.SecurityEndpoint;
import org.pac4j.core.util.security.SecurityEndpointBuilder;
import org.pac4j.jee.config.AbstractConfigFilter;
import org.pac4j.jee.context.JEEFrameworkParameters;

import java.io.IOException;
import java.security.Principal;
import java.util.Collection;
import java.util.Optional;

@Getter
@Setter
public class SecurityFilter extends AbstractConfigFilter implements SecurityEndpoint {
private String clients;
private String authorizers;
private String matchers;
public SecurityFilter() {}

public SecurityFilter(final Config config) {
setConfig(config);
}

public SecurityFilter(final Config config, final String clients) {
this(config);
this.clients = clients;
}

public SecurityFilter(final Config config, final String clients, final String authorizers) {
this(config, clients);
this.authorizers = authorizers;
}

public SecurityFilter(final Config config, final String clients, final String authorizers, final String matchers) {
this(config, clients, authorizers);
this.matchers = matchers;
}

public static SecurityFilter build(final Object... parameters) {
final SecurityFilter securityFilter = new SecurityFilter();
SecurityEndpointBuilder.buildConfig(securityFilter, parameters);
return securityFilter;
}

@Override
public void init(final FilterConfig filterConfig) throws ServletException {
super.init(filterConfig);

this.clients = getStringParam(filterConfig, Pac4jConstants.CLIENTS, this.clients);
this.authorizers = getStringParam(filterConfig, Pac4jConstants.AUTHORIZERS, this.authorizers);
this.matchers = getStringParam(filterConfig, Pac4jConstants.MATCHERS, this.matchers);
}

@Override
protected final void internalFilter(final HttpServletRequest request, final HttpServletResponse response, final FilterChain filterChain) throws IOException, ServletException {
Config config = getSharedConfig();
FrameworkAdapter.INSTANCE.applyDefaultSettingsIfUndefined(config);
config.getSecurityLogic().perform(config, (ctx, session, profiles) -> {
// if no profiles are loaded, pac4j is not concerned with this request
filterChain.doFilter(profiles.isEmpty() ? request : new Pac4JHttpServletRequestWrapper(request, profiles), response);
return null;
}, clients, authorizers, matchers, new JEEFrameworkParameters(request, response));
}
}
Loading

0 comments on commit 3a145e1

Please sign in to comment.