diff --git a/src/main/java/hudson/security/LDAPSecurityRealm.java b/src/main/java/hudson/security/LDAPSecurityRealm.java index 4f366e38..08d95f52 100644 --- a/src/main/java/hudson/security/LDAPSecurityRealm.java +++ b/src/main/java/hudson/security/LDAPSecurityRealm.java @@ -1,20 +1,20 @@ /* * The MIT License - * + * * Copyright (c) 2004-2010, Sun Microsystems, Inc., Kohsuke Kawaguchi, Seiji Sogabe, * Olivier Lamy * Copyright (c) 2017 CloudBees, Inc. - * + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - * + * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. - * + * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -293,7 +293,7 @@ public class LDAPSecurityRealm extends AbstractPasswordBasedSecurityRealm { justification = "This public field is exposed to the plugin's API") @Deprecated @Restricted(NoExternalUse.class) public transient String userSearch; - + /** * This defines the organizational unit that contains groups. * @@ -326,7 +326,7 @@ public class LDAPSecurityRealm extends AbstractPasswordBasedSecurityRealm { * @deprecated use {@link #groupMembershipStrategy} */ @Deprecated @Restricted(NoExternalUse.class) - @SuppressFBWarnings(value = "UUF_UNUSED_PUBLIC_OR_PROTECTED_FIELD", + @SuppressFBWarnings(value = "UUF_UNUSED_PUBLIC_OR_PROTECTED_FIELD", justification = "This public field is exposed to the plugin's API") public transient String groupMembershipFilter; @@ -363,7 +363,7 @@ group target (CN is a reasonable default) public transient String managerDN; @Deprecated @Restricted(NoExternalUse.class) - @SuppressFBWarnings(value = "UUF_UNUSED_PUBLIC_OR_PROTECTED_FIELD", + @SuppressFBWarnings(value = "UUF_UNUSED_PUBLIC_OR_PROTECTED_FIELD", justification = "This public field is exposed to the plugin's API") private transient String managerPassword; @@ -1391,10 +1391,15 @@ public static final class AuthoritiesPopulatorImpl extends DefaultLdapAuthoritie boolean convertToUpperCase = true; private GrantedAuthority defaultRole = null; - public AuthoritiesPopulatorImpl(ContextSource contextSource, String groupSearchBase) { + public AuthoritiesPopulatorImpl(ContextSource contextSource, String groupSearchBase, LDAPGroupMembershipStrategy ldapGroupMembershipStrategy) { super(contextSource, fixNull(groupSearchBase)); - super.setRolePrefix(""); + if (ldapGroupMembershipStrategy instanceof FromGroupSearchLDAPGroupMembershipStrategy) { + FromGroupSearchLDAPGroupMembershipStrategy fromGroupSearchLDAPGroupMembershipStrategy = (FromGroupSearchLDAPGroupMembershipStrategy) ldapGroupMembershipStrategy; + if (StringUtils.isNotBlank(fromGroupSearchLDAPGroupMembershipStrategy.getAttribute())) { + super.setGroupRoleAttribute(fromGroupSearchLDAPGroupMembershipStrategy.getAttribute()); + } + } super.setConvertToUpperCase(false); } @@ -1524,7 +1529,7 @@ public FormValidation doValidate(StaplerRequest req) throws Exception { String user = json.getString("testUser"); String password = json.getString("testPassword"); JSONObject realmCfg = json.getJSONObject("securityRealm"); - + // instantiate the realm LDAPSecurityRealm realm = req.bindJSON(LDAPSecurityRealm.class, realmCfg); return validate(realm, user, password); diff --git a/src/main/java/jenkins/security/plugins/ldap/FromGroupSearchLDAPGroupMembershipStrategy.java b/src/main/java/jenkins/security/plugins/ldap/FromGroupSearchLDAPGroupMembershipStrategy.java index eca43f78..3a24ad6b 100644 --- a/src/main/java/jenkins/security/plugins/ldap/FromGroupSearchLDAPGroupMembershipStrategy.java +++ b/src/main/java/jenkins/security/plugins/ldap/FromGroupSearchLDAPGroupMembershipStrategy.java @@ -59,19 +59,39 @@ public class FromGroupSearchLDAPGroupMembershipStrategy extends LDAPGroupMembers */ private final String filter; - @DataBoundConstructor + /** + * The LDAP attribute name which contains the group name (default = "cn"). + */ + private final String attribute; + public FromGroupSearchLDAPGroupMembershipStrategy(String filter) { this.filter = filter; + this.attribute = ""; + } + + @DataBoundConstructor + public FromGroupSearchLDAPGroupMembershipStrategy(String filter, String attribute) { + this.filter = filter; + this.attribute = attribute; } public String getFilter() { return filter; } + public String getAttribute() { + return attribute; + } + @Override public void setAuthoritiesPopulator(LdapAuthoritiesPopulator authoritiesPopulator) { - if (authoritiesPopulator instanceof LDAPSecurityRealm.AuthoritiesPopulatorImpl && StringUtils.isNotBlank(filter)) { - ((LDAPSecurityRealm.AuthoritiesPopulatorImpl) authoritiesPopulator).setGroupSearchFilter(filter); + if (authoritiesPopulator instanceof LDAPSecurityRealm.AuthoritiesPopulatorImpl) { + if (StringUtils.isNotBlank(filter)) { + ((LDAPSecurityRealm.AuthoritiesPopulatorImpl) authoritiesPopulator).setGroupSearchFilter(filter); + } + if (StringUtils.isNotBlank(attribute)) { + ((LDAPSecurityRealm.AuthoritiesPopulatorImpl) authoritiesPopulator).setGroupRoleAttribute(attribute); + } } super.setAuthoritiesPopulator(authoritiesPopulator); } diff --git a/src/main/java/jenkins/security/plugins/ldap/LDAPConfiguration.java b/src/main/java/jenkins/security/plugins/ldap/LDAPConfiguration.java index 7aa22444..162fdad9 100644 --- a/src/main/java/jenkins/security/plugins/ldap/LDAPConfiguration.java +++ b/src/main/java/jenkins/security/plugins/ldap/LDAPConfiguration.java @@ -623,7 +623,7 @@ public ApplicationContext createApplicationContext(LDAPSecurityRealm realm) { // this is when we need to find it. bindAuthenticator.setUserSearch(ldapUserSearch); - LDAPSecurityRealm.AuthoritiesPopulatorImpl ldapAuthoritiesPopulator = new LDAPSecurityRealm.AuthoritiesPopulatorImpl(contextSource, getGroupSearchBase()); + LDAPSecurityRealm.AuthoritiesPopulatorImpl ldapAuthoritiesPopulator = new LDAPSecurityRealm.AuthoritiesPopulatorImpl(contextSource, getGroupSearchBase(), getGroupMembershipStrategy()); ldapAuthoritiesPopulator.setSearchSubtree(true); ldapAuthoritiesPopulator.setGroupSearchFilter("(| (member={0}) (uniqueMember={0}) (memberUid={1}))"); if (realm.isDisableRolePrefixing()) { diff --git a/src/main/resources/jenkins/security/plugins/ldap/FromGroupSearchLDAPGroupMembershipStrategy/config.jelly b/src/main/resources/jenkins/security/plugins/ldap/FromGroupSearchLDAPGroupMembershipStrategy/config.jelly index 791acbf8..f5b5a24d 100644 --- a/src/main/resources/jenkins/security/plugins/ldap/FromGroupSearchLDAPGroupMembershipStrategy/config.jelly +++ b/src/main/resources/jenkins/security/plugins/ldap/FromGroupSearchLDAPGroupMembershipStrategy/config.jelly @@ -28,4 +28,7 @@ THE SOFTWARE. + + + diff --git a/src/main/resources/jenkins/security/plugins/ldap/FromGroupSearchLDAPGroupMembershipStrategy/help-attribute.html b/src/main/resources/jenkins/security/plugins/ldap/FromGroupSearchLDAPGroupMembershipStrategy/help-attribute.html new file mode 100644 index 00000000..3164dd5f --- /dev/null +++ b/src/main/resources/jenkins/security/plugins/ldap/FromGroupSearchLDAPGroupMembershipStrategy/help-attribute.html @@ -0,0 +1,3 @@ +
+ The LDAP attribute name which contains the group name (default = "cn"). +
diff --git a/src/main/resources/jenkins/security/plugins/ldap/FromGroupSearchLDAPGroupMembershipStrategy/help-attribute_fr.html b/src/main/resources/jenkins/security/plugins/ldap/FromGroupSearchLDAPGroupMembershipStrategy/help-attribute_fr.html new file mode 100644 index 00000000..269b252e --- /dev/null +++ b/src/main/resources/jenkins/security/plugins/ldap/FromGroupSearchLDAPGroupMembershipStrategy/help-attribute_fr.html @@ -0,0 +1,3 @@ +
+ Le nom de l'attribut LDAP contenant le nom du groupe (valeur par défaut = "cn") +
diff --git a/src/main/resources/jenkins/security/plugins/ldap/LDAPConfiguration/config.jelly b/src/main/resources/jenkins/security/plugins/ldap/LDAPConfiguration/config.jelly index 65e6f72e..e5c8f362 100644 --- a/src/main/resources/jenkins/security/plugins/ldap/LDAPConfiguration/config.jelly +++ b/src/main/resources/jenkins/security/plugins/ldap/LDAPConfiguration/config.jelly @@ -51,4 +51,4 @@ - \ No newline at end of file + diff --git a/src/test/java/hudson/security/LDAPEmbeddedTest.java b/src/test/java/hudson/security/LDAPEmbeddedTest.java index 23d6741a..b6bb7d12 100644 --- a/src/test/java/hudson/security/LDAPEmbeddedTest.java +++ b/src/test/java/hudson/security/LDAPEmbeddedTest.java @@ -228,6 +228,43 @@ public void userLookup_rolesFromUserRecord_modern() throws Exception { assertThat(userGetAuthorities(details), containsInAnyOrder("HMS_Victory")); } + @Test + @LDAPSchema(ldif = "sevenSeas", id = "sevenSeas", dn = "o=sevenSeas") + public void userLookup_rolesFromGroupSearchWithGroupAttribute() throws Exception { + LDAPSecurityRealm realm = new LDAPSecurityRealm( + ads.getUrl(), + null, + null, + null, + null, + null, + new FromGroupSearchLDAPGroupMembershipStrategy(null, "description"), + "uid=admin,ou=system", + Secret.fromString("pass"), + false, + false, + new LDAPSecurityRealm.CacheConfiguration(100, 1000), + new LDAPSecurityRealm.EnvironmentProperty[0], + "cn", + null, + IdStrategy.CASE_INSENSITIVE, + IdStrategy.CASE_INSENSITIVE); + r.jenkins.setSecurityRealm(realm); + User user = User.get("hhornblo", true, Collections.emptyMap()); + List authorities = user.getAuthorities(); + assertThat(user.getAuthorities(), containsInAnyOrder("HMS_Lydia", "ROLE_HMS_LYDIA")); + assertThat(user.getDisplayName(), is("Horatio Hornblower")); + assertThat(user.getProperty(Mailer.UserProperty.class).getAddress(), is("hhornblo@royalnavy.mod.uk")); + UserDetails details = realm.authenticate2("hhornblo", "pass"); + assertThat(userGetAuthorities(details), containsInAnyOrder("HMS_Lydia", "ROLE_HMS_LYDIA")); + user = User.get("hnelson", true, Collections.emptyMap()); + assertThat(user.getAuthorities(), containsInAnyOrder("HMS_Victory", "ROLE_HMS_VICTORY")); + assertThat(user.getDisplayName(), is("Horatio Nelson")); + assertThat(user.getProperty(Mailer.UserProperty.class).getAddress(), is("hnelson@royalnavy.mod.uk")); + details = realm.authenticate2("hnelson", "pass"); + assertThat(userGetAuthorities(details), containsInAnyOrder("HMS_Victory", "ROLE_HMS_VICTORY")); + } + private Set userGetAuthorities(UserDetails details) { Set authorities = new LinkedHashSet<>(); for (GrantedAuthority a : details.getAuthorities()) { diff --git a/src/test/java/jenkins/security/plugins/ldap/CasCTest.java b/src/test/java/jenkins/security/plugins/ldap/CasCTest.java index a0c71903..b68392cd 100644 --- a/src/test/java/jenkins/security/plugins/ldap/CasCTest.java +++ b/src/test/java/jenkins/security/plugins/ldap/CasCTest.java @@ -35,5 +35,6 @@ public void configure_ldap() { assertEquals("(&(cn={0})(objectclass=group))", configuration.getGroupSearchFilter()); final FromGroupSearchLDAPGroupMembershipStrategy strategy = ((FromGroupSearchLDAPGroupMembershipStrategy) configuration.getGroupMembershipStrategy()); assertEquals("(&(objectClass=group)(|(cn=GROUP_1)(cn=GROUP_2)))", strategy.getFilter()); + assertEquals("description", strategy.getAttribute()); } } diff --git a/src/test/resources/hudson/security/sevenSeas.ldif b/src/test/resources/hudson/security/sevenSeas.ldif index 34538e89..358b80d5 100644 --- a/src/test/resources/hudson/security/sevenSeas.ldif +++ b/src/test/resources/hudson/security/sevenSeas.ldif @@ -168,6 +168,7 @@ dn: cn=HMS Lydia,ou=crews,ou=groups,o=sevenSeas objectclass: groupOfUniqueNames objectclass: top cn: HMS Lydia +description: HMS_Lydia uniquemember: cn=Horatio Hornblower,ou=people,o=sevenSeas uniquemember: cn=William Bush,ou=people,o=sevenSeas uniquemember: cn=Thomas Quist,ou=people,o=sevenSeas @@ -236,6 +237,7 @@ dn: cn=HMS Victory,ou=crews,ou=groups,o=sevenSeas objectclass: groupOfUniqueNames objectclass: top cn: HMS Victory +description: HMS_Victory uniquemember: cn=Horatio Nelson,ou=people,o=sevenSeas uniquemember: cn=Thomas Masterman Hardy,ou=people,o=sevenSeas uniquemember: cn=Cornelius Buckley,ou=people,o=sevenSeas @@ -320,6 +322,7 @@ dn: cn=HMS Bounty,ou=crews,ou=groups,o=sevenSeas objectclass: groupOfUniqueNames objectclass: top cn: HMS Bounty +description: HMS_Bounty uniquemember: cn=William Bligh,ou=people,o=sevenSeas uniquemember: cn=Fletcher Christian,ou=people,o=sevenSeas uniquemember: cn=John Fryer,ou=people,o=sevenSeas diff --git a/src/test/resources/jenkins/security/plugins/ldap/casc.yml b/src/test/resources/jenkins/security/plugins/ldap/casc.yml index 96efcb2c..9580a096 100644 --- a/src/test/resources/jenkins/security/plugins/ldap/casc.yml +++ b/src/test/resources/jenkins/security/plugins/ldap/casc.yml @@ -11,6 +11,7 @@ jenkins: groupMembershipStrategy: fromGroupSearch: filter: "(&(objectClass=group)(|(cn=GROUP_1)(cn=GROUP_2)))" + attribute: "description" cache: size: 100 ttl: 10