/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.cxf.sts.claims; import java.net.URI; import java.net.URISyntaxException; import java.security.Principal; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.StringTokenizer; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Pattern; import javax.naming.Name; import javax.security.auth.kerberos.KerberosPrincipal; import javax.security.auth.x500.X500Principal; import org.apache.cxf.common.logging.LogUtils; import org.apache.cxf.rt.security.claims.Claim; import org.apache.cxf.rt.security.claims.ClaimCollection; import org.apache.cxf.sts.token.realm.RealmSupport; import org.springframework.ldap.core.LdapTemplate; import org.springframework.ldap.filter.EqualsFilter; import org.springframework.ldap.filter.Filter; public class LdapGroupClaimsHandler implements ClaimsHandler, RealmSupport { private static final Logger LOG = LogUtils.getL7dLogger(LdapGroupClaimsHandler.class); private static final String SCOPE = "%SCOPE%"; private static final String ROLE = "%ROLE%"; private LdapTemplate ldap; private String userBaseDn; private String groupBaseDn; private String userObjectClass = "person"; private String groupObjectClass = "groupOfNames"; private String userNameAttribute = "cn"; private String groupMemberAttribute = "member"; private String groupURI = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/role"; private String groupNameGlobalFilter = ROLE; private String groupNameScopedFilter = SCOPE + "_" + ROLE; private Map<String, String> appliesToScopeMapping; private boolean useFullGroupNameAsValue; private List<String> supportedRealms; private List<Filter> customFilters; private String realm; public void setSupportedRealms(List<String> supportedRealms) { this.supportedRealms = supportedRealms; } public void setRealm(String realm) { this.realm = realm; } public boolean isUseFullGroupNameAsValue() { return useFullGroupNameAsValue; } public void setUseFullGroupNameAsValue(boolean useFullGroupNameAsValue) { this.useFullGroupNameAsValue = useFullGroupNameAsValue; } public String getUserObjectClass() { return userObjectClass; } public void setUserObjectClass(String userObjectClass) { this.userObjectClass = userObjectClass; } public String getGroupObjectClass() { return groupObjectClass; } public void setGroupObjectClass(String groupObjectClass) { this.groupObjectClass = groupObjectClass; } public String getUserNameAttribute() { return userNameAttribute; } public void setUserNameAttribute(String userNameAttribute) { this.userNameAttribute = userNameAttribute; } public void setLdapTemplate(LdapTemplate ldapTemplate) { this.ldap = ldapTemplate; } public LdapTemplate getLdapTemplate() { return ldap; } public void setUserBaseDN(String userBaseDN) { this.userBaseDn = userBaseDN; } public String getUserBaseDN() { return userBaseDn; } public String getGroupMemberAttribute() { return groupMemberAttribute; } public void setGroupMemberAttribute(String groupMemberAttribute) { this.groupMemberAttribute = groupMemberAttribute; } public String getGroupURI() { return groupURI; } public void setGroupURI(String groupURI) { this.groupURI = groupURI; } public void setAppliesToScopeMapping(Map<String, String> appliesToScopeMapping) { this.appliesToScopeMapping = appliesToScopeMapping; } public Map<String, String> getAppliesToScopeMapping() { return appliesToScopeMapping; } public String getGroupBaseDN() { return groupBaseDn; } public void setGroupBaseDN(String groupBaseDN) { this.groupBaseDn = groupBaseDN; } public String getGroupNameGlobalFilter() { return groupNameGlobalFilter; } public void setGroupNameGlobalFilter(String groupNameGlobalFilter) { this.groupNameGlobalFilter = groupNameGlobalFilter; } public String getGroupNameScopedFilter() { return groupNameScopedFilter; } public void setGroupNameScopedFilter(String groupNameScopedFilter) { this.groupNameScopedFilter = groupNameScopedFilter; } public List<URI> getSupportedClaimTypes() { List<URI> list = new ArrayList<>(); try { list.add(new URI(this.groupURI)); } catch (URISyntaxException e) { LOG.warning("Invalid groupURI '" + this.groupURI + "'"); } return list; } public ProcessedClaimCollection retrieveClaimValues( ClaimCollection claims, ClaimsParameters parameters) { boolean found = false; for (Claim claim: claims) { if (claim.getClaimType().toString().equals(this.groupURI)) { found = true; break; } } if (!found) { return new ProcessedClaimCollection(); } String user = null; Principal principal = parameters.getPrincipal(); if (principal instanceof KerberosPrincipal) { KerberosPrincipal kp = (KerberosPrincipal)principal; StringTokenizer st = new StringTokenizer(kp.getName(), "@"); user = st.nextToken(); } else if (principal instanceof X500Principal) { X500Principal x500p = (X500Principal)principal; LOG.warning("Unsupported principal type X500: " + x500p.getName()); } else if (principal != null) { user = principal.getName(); if (user == null) { LOG.warning("Principal name must not be null"); } } else { LOG.warning("Principal is null"); } if (user == null) { return new ProcessedClaimCollection(); } if (!LdapUtils.isDN(user)) { Name dn = LdapUtils.getDnOfEntry(ldap, this.userBaseDn, this.getUserObjectClass(), this.getUserNameAttribute(), user); if (dn != null) { user = dn.toString(); if (LOG.isLoggable(Level.FINE)) { LOG.fine("DN for (" + this.getUserNameAttribute() + "=" + user + ") found: " + user); } } else { LOG.warning("DN not found for user '" + user + "'"); return new ProcessedClaimCollection(); } } if (LOG.isLoggable(Level.FINER)) { LOG.finer("Retrieve groups for user " + user); } List<Filter> filters = new ArrayList<>(); filters.add(new EqualsFilter(this.groupMemberAttribute, user)); if (customFilters != null && !customFilters.isEmpty()) { filters.addAll(customFilters); } List<String> groups = LdapUtils.getAttributeOfEntries(ldap, this.groupBaseDn, this.getGroupObjectClass(), filters, "cn"); if (groups == null || groups.size() == 0) { if (LOG.isLoggable(Level.INFO)) { LOG.info("No groups found for user '" + user + "'"); } return new ProcessedClaimCollection(); } if (LOG.isLoggable(Level.FINE)) { LOG.fine("Groups for user '" + parameters.getPrincipal().getName() + "': " + groups); } String scope = null; if (getAppliesToScopeMapping() != null && getAppliesToScopeMapping().size() > 0 && parameters.getAppliesToAddress() != null) { scope = getAppliesToScopeMapping().get(parameters.getAppliesToAddress()); if (LOG.isLoggable(Level.FINE)) { LOG.fine("AppliesTo matches with scope: " + scope); } } String regex = this.groupNameGlobalFilter; regex = regex.replaceAll(ROLE, ".*"); Pattern globalPattern = Pattern.compile(regex); //If AppliesTo value can be mapped to a Scope Name //ex. https://localhost/doubleit/services/doubleittransport -> Demo Pattern scopePattern = null; if (scope != null) { regex = this.groupNameScopedFilter; regex = regex.replaceAll(SCOPE, scope).replaceAll(ROLE, ".*"); scopePattern = Pattern.compile(regex); } List<String> filteredGroups = new ArrayList<>(); for (String group: groups) { if (scopePattern != null && scopePattern.matcher(group).matches()) { //Group matches the scoped filter //ex. (default groupNameScopeFilter) // Demo_User -> Role=User // Demo_Admin -> Role=Admin String filter = this.groupNameScopedFilter; String role = null; if (isUseFullGroupNameAsValue()) { role = group; } else { role = parseRole(group, filter.replaceAll(SCOPE, scope)); } filteredGroups.add(role); } else { if (globalPattern.matcher(group).matches()) { //Group matches the global filter //ex. (default groupNameGlobalFilter) // User -> Role=User // Admin -> Role=Admin String role = null; if (isUseFullGroupNameAsValue()) { role = group; } else { role = parseRole(group, this.groupNameGlobalFilter); } filteredGroups.add(role); } else if (LOG.isLoggable(Level.FINER)) { LOG.finer("Group '" + group + "' doesn't match scoped and global group filter"); } } } LOG.info("Filtered groups: " + filteredGroups); if (filteredGroups.size() == 0) { LOG.info("No matching groups found for user '" + principal + "'"); return new ProcessedClaimCollection(); } ProcessedClaimCollection claimsColl = new ProcessedClaimCollection(); ProcessedClaim c = new ProcessedClaim(); c.setClaimType(URI.create(this.groupURI)); c.setPrincipal(principal); c.setValues(new ArrayList<>(filteredGroups)); // c.setIssuer(issuer); // c.setOriginalIssuer(originalIssuer); // c.setNamespace(namespace); claimsColl.add(c); return claimsColl; } @Override public List<String> getSupportedRealms() { return supportedRealms; } @Override public String getHandlerRealm() { return realm; } private String parseRole(String group, String filter) { int roleStart = filter.indexOf(ROLE); int trimEnd = filter.length() - ROLE.length() - roleStart; return group.substring(roleStart, group.length() - trimEnd); } public List<Filter> getCustomFilters() { return customFilters; } /** * Define some custom filters to use in retrieving group membership information. This allows you to restrict * the groups that are returned based on some attribute value, for example. * @param customFilters */ public void setCustomFilters(List<Filter> customFilters) { this.customFilters = customFilters; } }