Skip to content

Commit

Permalink
Merge branch 'release/v7.0.8-10'
Browse files Browse the repository at this point in the history
  • Loading branch information
nfranzeck authored and cesmarvin committed Jan 10, 2025
2 parents 65ad7e9 + 358f54c commit 13e0553
Show file tree
Hide file tree
Showing 20 changed files with 582 additions and 38 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [v7.0.8-10] - 2025-01-10
### Added
- Add configuration for `allowed_groups` and `initial_admin_user` in delegated authentication

### Changed
- [#246] Update base image to Alpine 3.21.0 and Java to 21.0.5-p11
- Update Tomcat to 10.1.34

### Fixed
- [#246] Fix a restart loop if the config key `oidc/enabled` was not set.

## [v7.0.8-9] - 2024-12-20
### Added
- Add http health-check, so that the dogu will get healthy when the start of the web-application is completed
Expand Down
8 changes: 4 additions & 4 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
ARG TOMCAT_MAJOR_VERSION=10
ARG TOMCAT_VERSION=10.1.26
ARG TOMCAT_TARGZ_SHA256=f73f76760137833b3305dfb18ed174f87feac3ab78f65289a0835a851d7cfeb2
ARG TOMCAT_VERSION=10.1.34
ARG TOMCAT_TARGZ_SHA256=f799541380bfff2b674cefd86c5376d2d7d566b3a2e7c4579d2b491de8ec6c36

FROM eclipse-temurin:21-jdk-alpine AS builder

Expand Down Expand Up @@ -41,10 +41,10 @@ RUN apk update && apk add wget && wget -O "apache-tomcat-${TOMCAT_VERSION}.tar.


# registry.cloudogu.com/official/cas
FROM registry.cloudogu.com/official/java:21.0.4-4
FROM registry.cloudogu.com/official/java:21.0.5-1

LABEL NAME="official/cas" \
VERSION="7.0.8-9" \
VERSION="7.0.8-10" \
maintainer="[email protected]"

ARG TOMCAT_VERSION
Expand Down
48 changes: 43 additions & 5 deletions app/src/main/java/de/triology/cas/ldap/UserManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,29 @@ public class UserManager {
public static final String LDAP_TRUE = "TRUE";
public static final String LDAP_FALSE = "FALSE";

public static final String GroupOu = "Groups";
public static final String GroupCnAttribute = "cn";
public static final String GroupMemberAttribute = "member";

public static final String ObjectClassAttributeName = "objectClass";

private final String baseDN;
private final String userBaseDN;
private final String groupBaseDN;
private final LdapOperationFactory operationFactory;

/**
* Creates a new Usermanager that can load, create and update {@link CesInternalLdapUser}s.
*/
public UserManager(String baseDN, LdapOperationFactory operationFactory) {
this.baseDN = baseDN;
public UserManager(String userBaseDN, LdapOperationFactory operationFactory) {
this.userBaseDN = userBaseDN;
this.groupBaseDN = createGroupBaseDNFromBaseDN(userBaseDN);
this.operationFactory = operationFactory;
}

private static String createGroupBaseDNFromBaseDN(String userBaseDN) {
return userBaseDN.replaceFirst("(?<=ou=)[^,]+(?=,)", GroupOu);
}

/**
* Gets the {@link CesInternalLdapUser} for the given UID.
*
Expand Down Expand Up @@ -118,15 +128,43 @@ public void updateUser(CesInternalLdapUser user) throws CesLdapException {
}
}

/**
* Adds the given user to the given group in LDAP
*
* @param user the user to add
* @param groupName the name of the group to add the user to
* @throws CesLdapException for errors in LDAP
*/
public void addUserToGroup(CesInternalLdapUser user, String groupName) throws CesLdapException {
try {
final ModifyOperation modify = operationFactory.modifyOperation();
ModifyRequest request = new ModifyRequest(
createDnForGroup(groupName),
new AttributeModification(AttributeModification.Type.ADD, new LdapAttribute(GroupMemberAttribute, createDnForUser(user)))
);
final ModifyResponse response = modify.execute(request);

if (!response.isSuccess()) {
throw new CesLdapException(response.getDiagnosticMessage());
}
} catch (LdapException e) {
throw new CesLdapException("error while adding user to group", e);
}
}

private String createDnForUser(CesInternalLdapUser user) {
return CesInternalLdapUser.UidAttribute + "=" + user.getUid() + "," + this.baseDN;
return CesInternalLdapUser.UidAttribute + "=" + user.getUid() + "," + this.userBaseDN;
}

private String createDnForGroup(String groupName) {
return GroupCnAttribute + "=" + groupName + "," + this.groupBaseDN;
}

private SearchRequest createGetUserRequest(String uid) {
String filter = String.format("(&%s%s)", uidFilter(uid), externalUsersFilter());

SearchRequest request = new SearchRequest();
request.setBaseDn(this.baseDN);
request.setBaseDn(this.userBaseDN);
request.setReturnAttributes(CesInternalLdapUser.UidAttribute, CesInternalLdapUser.CnAttribute, CesInternalLdapUser.SnAttribute, CesInternalLdapUser.GivenNameAttribute, CesInternalLdapUser.DisplayNameAttribute, CesInternalLdapUser.MailAttribute, CesInternalLdapUser.ExternalAttribute, CesInternalLdapUser.MailAttribute, CesInternalLdapUser.ExternalAttribute, CesInternalLdapUser.MemberOfAttribute);
request.setFilter(filter);
request.setSearchScope(SearchScope.SUBTREE);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
import de.triology.cas.ldap.CesInternalLdapUser;
import de.triology.cas.ldap.UserManager;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang.ArrayUtils;
import org.apereo.cas.authentication.Credential;
import org.apereo.cas.authentication.adaptive.UnauthorizedAuthenticationException;
import org.apereo.cas.authentication.principal.DelegatedAuthenticationPreProcessor;
import org.apereo.cas.authentication.principal.Principal;
import org.apereo.cas.authentication.principal.Service;
Expand All @@ -22,30 +24,68 @@
@RequiredArgsConstructor
public class CesDelegatedAuthenticationPreProcessor implements DelegatedAuthenticationPreProcessor {

private final static String groupsAttributeName = "groups";
private final static String alreadyMappedAttributeName = "cesAttributesAlreadyMapped";
private final static String externalGroupsAttributeName = "externalGroups";

private final UserManager userManager;

private final List<AttributeMapping> attributeMappings;

private final UserManager userManager;
private final String[] allowedGroups;

@Override
public Principal process(Principal principal, BaseClient client, Credential credential, Service service) throws Throwable {
mapAttributes(principal);

// map attributes
if (!checkExternalAllowedGroups(principal)) {
throw new UnauthorizedAuthenticationException("user is not assigned to any of the allowed groups");
}

// attach internal groups
CesInternalLdapUser existingLdapUser = userManager.getUserByUid(principal.getId());
if (existingLdapUser != null) {
PrincipalGroups.setGroupsInPrincipal(principal, Arrays.asList(existingLdapUser.getGroups().toArray()));
}

return principal;
}

private void mapAttributes(Principal principal) {
Map<String, List<Object>> principalAttributes = principal.getAttributes();

if (principalAttributes.containsKey(alreadyMappedAttributeName)) {
// attributes were already mapped
return;
}

for (AttributeMapping mapping : attributeMappings) {
List<Object> attributeValues = principalAttributes.get(mapping.getSource());
if (attributeValues != null) {
principalAttributes.put(mapping.getTarget(), attributeValues);
}
}

// attach groups
CesInternalLdapUser existingLdapUser = userManager.getUserByUid(principal.getId());
if (existingLdapUser != null) {
principal.getAttributes().put(groupsAttributeName, Arrays.asList(existingLdapUser.getGroups().toArray()));
principalAttributes.put(alreadyMappedAttributeName, List.of(true));
}

private boolean checkExternalAllowedGroups(Principal principal) {
if (ArrayUtils.isEmpty(allowedGroups)) {
// no allowed groups configured -> access for all
return true;
}

return principal;
List<Object> groups = principal.getAttributes().get(externalGroupsAttributeName);
if (groups == null) {
return false;
}

for (Object group : groups) {
if (ArrayUtils.contains(allowedGroups, group.toString())) {
return true;
}
}

return false;

}
}
Original file line number Diff line number Diff line change
@@ -1,26 +1,36 @@
package de.triology.cas.oidc.beans.delegation;

import de.triology.cas.ldap.CesInternalLdapUser;
import de.triology.cas.ldap.CesLdapException;
import de.triology.cas.ldap.UserManager;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang.ArrayUtils;
import org.apereo.cas.authentication.Credential;
import org.apereo.cas.authentication.principal.Principal;
import org.apereo.cas.authentication.principal.provision.DelegatedClientUserProfileProvisioner;
import org.pac4j.core.client.BaseClient;
import org.pac4j.core.profile.UserProfile;
import org.pac4j.oidc.profile.OidcProfile;

import java.util.List;

/**
* The CesDelegatedClientUserProfileProvisioner handles th provisioning of UserProfiles from the delegated authentication.
* Currently only supports OIDC-UserProfiles ({@link OidcProfile}) and the internal CES-LDAP.
* The CesDelegatedClientUserProfileProvisioner checks if a user for the given userProfile exists in the internal CES-LDAP.
* If no user exists a new user will be created. If the user already exists, it will be updated with the values from the userProfile.
*
* The CesDelegatedClientUserProfileProvisioner also checks if the user belongs to the initial admin-users and assigns the admin-groups accordingly.
*/
@RequiredArgsConstructor
public class CesDelegatedClientUserProfileProvisioner implements DelegatedClientUserProfileProvisioner {

private final UserManager userManager;

private final String[] initialAdminUsernames;

private final String[] adminGroups;

@Override
public void execute(Principal principal, UserProfile profile, BaseClient client, Credential credential) throws Throwable {
CesInternalLdapUser userFromProfile = userFromProfile(profile);
Expand All @@ -29,12 +39,27 @@ public void execute(Principal principal, UserProfile profile, BaseClient client,
if (existingLdapUser == null) {
// user does not exist -> create
userManager.createUser(userFromProfile);

addAdminGroupsForUser(userFromProfile, principal);
} else {
// user does not exist -> update
userManager.updateUser(userFromProfile);
}
}

private void addAdminGroupsForUser(CesInternalLdapUser user, Principal principal) throws CesLdapException {
List<Object> principalGroups = PrincipalGroups.getGroupsFromPrincipal(principal);

if (ArrayUtils.contains(initialAdminUsernames, user.getUid())) {
for (final String group : adminGroups) {
userManager.addUserToGroup(user, group);
principalGroups.add(group);
}

PrincipalGroups.setGroupsInPrincipal(principal, principalGroups);
}
}

private static CesInternalLdapUser userFromProfile(UserProfile profile) {
if (profile instanceof OidcProfile oidcProfile) {
return new CesInternalLdapUser(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package de.triology.cas.oidc.beans.delegation;

import org.apereo.cas.authentication.principal.Principal;

import java.util.ArrayList;
import java.util.List;

public class PrincipalGroups {

private final static String groupsAttributeName = "groups";

public static List<Object> getGroupsFromPrincipal(Principal principal) {
List<Object> groups = principal.getAttributes().get(groupsAttributeName);
if (groups == null) {
groups = new ArrayList<>();
}

return groups;
}

public static void setGroupsInPrincipal(Principal principal, List<Object> groups) {
principal.getAttributes().put(groupsAttributeName, groups);
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import de.triology.cas.oidc.beans.delegation.CesDelegatedAuthenticationPreProcessor;
import de.triology.cas.oidc.beans.delegation.CesDelegatedClientUserProfileProvisioner;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apereo.cas.authentication.principal.DelegatedAuthenticationPreProcessor;
import org.apereo.cas.config.OidcConfiguration;
import org.apereo.cas.configuration.CasConfigurationProperties;
Expand All @@ -27,6 +28,7 @@
import org.springframework.context.annotation.Configuration;
import org.springframework.webflow.execution.Action;

import java.util.Arrays;
import java.util.List;

@Configuration("CesOidcConfiguration")
Expand All @@ -44,6 +46,15 @@ public class CesOidcConfiguration {
@Value("${ces.delegation.oidc.attributeMapping:#{\"\"}}")
private String attributesMappingsString;

@Value("${ces.delegation.oidc.allowedGroups:#{\"\"}}")
private String allowedGroupsConfigString;

@Value("${ces.delegation.oidc.initialAdminUsernames:#{\"\"}}")
private String initialAadminUsernamesConfigString;

@Value("${ces.delegation.oidc.adminGroups:#{\"\"}}")
private String adminGroupsConfigString;

@Bean
@RefreshScope
public OAuth20CasClientRedirectActionBuilder oidcCasClientRedirectActionBuilder() {
Expand All @@ -65,17 +76,20 @@ public Action delegatedAuthenticationClientLogoutAction(
@RefreshScope
public DelegatedClientUserProfileProvisioner clientUserProfileProvisioner(final CasConfigurationProperties casProperties) {
UserManager userManager = getUserManager(casProperties);
String[] initialAdminUsernames = splitAndTrim(initialAadminUsernamesConfigString);
String[] adminGroups = splitAndTrim(adminGroupsConfigString);

return new CesDelegatedClientUserProfileProvisioner(userManager);
return new CesDelegatedClientUserProfileProvisioner(userManager, initialAdminUsernames, adminGroups);
}

@Bean
@RefreshScope
public DelegatedAuthenticationPreProcessor delegatedAuthenticationPreProcessor(final CasConfigurationProperties casProperties) {
UserManager userManager = getUserManager(casProperties);
List<AttributeMapping> attributeMappings = AttributeMapping.fromPropertyString(attributesMappingsString);
String[] allowedGroups = splitAndTrim(allowedGroupsConfigString);

return new CesDelegatedAuthenticationPreProcessor(attributeMappings, userManager);
return new CesDelegatedAuthenticationPreProcessor(userManager, attributeMappings, allowedGroups);
}

private static UserManager getUserManager(CasConfigurationProperties casProperties) {
Expand All @@ -84,4 +98,14 @@ private static UserManager getUserManager(CasConfigurationProperties casProperti

return new UserManager(ldapProperties.getBaseDn(), new LdapOperationFactory(connectionFactory));
}

private static String[] splitAndTrim(String input) {
if (StringUtils.isBlank(input)) {
return new String[0];
}

String[] result = StringUtils.split(input, ",");

return Arrays.stream(result).map(String::trim).toArray(String[]::new);
}
}
Loading

0 comments on commit 13e0553

Please sign in to comment.