|
26 | 26 | package jenkins.security.plugins.ldap; |
27 | 27 |
|
28 | 28 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; |
29 | | -import groovy.lang.Binding; |
30 | 29 | import hudson.DescriptorExtensionList; |
31 | 30 | import hudson.Extension; |
32 | 31 | import hudson.Util; |
33 | 32 | import hudson.model.AbstractDescribableImpl; |
34 | 33 | import hudson.model.Descriptor; |
35 | 34 | import hudson.security.LDAPSecurityRealm; |
36 | | -import hudson.security.SecurityRealm; |
37 | 35 | import hudson.util.FormValidation; |
38 | 36 | import hudson.util.Secret; |
39 | | -import hudson.util.spring.BeanBuilder; |
40 | 37 | import jenkins.model.Jenkins; |
41 | | -import org.acegisecurity.ldap.InitialDirContextFactory; |
42 | | -import org.acegisecurity.ldap.LdapTemplate; |
| 38 | +import org.acegisecurity.AuthenticationManager; |
| 39 | +import org.acegisecurity.ldap.DefaultInitialDirContextFactory; |
| 40 | +import org.acegisecurity.ldap.LdapUserSearch; |
43 | 41 | import org.acegisecurity.ldap.search.FilterBasedLdapUserSearch; |
| 42 | +import org.acegisecurity.providers.AuthenticationProvider; |
| 43 | +import org.acegisecurity.providers.ProviderManager; |
| 44 | +import org.acegisecurity.providers.anonymous.AnonymousAuthenticationProvider; |
44 | 45 | import org.acegisecurity.providers.ldap.LdapAuthoritiesPopulator; |
| 46 | +import org.acegisecurity.providers.ldap.authenticator.BindAuthenticator2; |
| 47 | +import org.acegisecurity.providers.rememberme.RememberMeAuthenticationProvider; |
45 | 48 | import org.apache.commons.codec.Charsets; |
46 | 49 | import org.apache.commons.codec.binary.Base64; |
47 | 50 | import org.apache.commons.codec.digest.DigestUtils; |
48 | | -import org.apache.commons.io.input.AutoCloseInputStream; |
49 | 51 | import org.apache.commons.lang.StringUtils; |
50 | 52 | import org.kohsuke.accmod.Restricted; |
51 | 53 | import org.kohsuke.accmod.restrictions.NoExternalUse; |
52 | 54 | import org.kohsuke.stapler.DataBoundConstructor; |
53 | 55 | import org.kohsuke.stapler.DataBoundSetter; |
54 | 56 | import org.kohsuke.stapler.QueryParameter; |
55 | | -import org.springframework.web.context.WebApplicationContext; |
56 | 57 |
|
57 | 58 | import javax.annotation.Nonnull; |
58 | 59 | import javax.naming.Context; |
|
62 | 63 | import javax.naming.directory.DirContext; |
63 | 64 | import javax.naming.directory.InitialDirContext; |
64 | 65 |
|
65 | | -import java.io.File; |
66 | | -import java.io.FileInputStream; |
67 | | -import java.io.FileNotFoundException; |
68 | 66 | import java.io.IOException; |
69 | 67 | import java.net.InetAddress; |
70 | 68 | import java.net.Socket; |
|
76 | 74 | import java.util.Arrays; |
77 | 75 | import java.util.Collections; |
78 | 76 | import java.util.Hashtable; |
| 77 | +import java.util.HashMap; |
79 | 78 | import java.util.List; |
80 | 79 | import java.util.Map; |
81 | 80 | import java.util.logging.Level; |
|
95 | 94 | public class LDAPConfiguration extends AbstractDescribableImpl<LDAPConfiguration> { |
96 | 95 |
|
97 | 96 | private static final Logger LOGGER = LDAPSecurityRealm.LOGGER; |
98 | | - @Restricted(NoExternalUse.class) |
99 | | - public static final String SECURITY_REALM_LDAPBIND_GROOVY = "LDAPBindSecurityRealm.groovy"; |
100 | 97 |
|
101 | 98 | /** |
102 | 99 | * LDAP server name(s) separated by spaces, optionally with TCP port number, like "ldap.acme.org" |
@@ -400,10 +397,6 @@ public String getDisplayName() { |
400 | 397 | return "ldap"; |
401 | 398 | } |
402 | 399 |
|
403 | | - public boolean noCustomBindScript() { |
404 | | - return !getLdapBindOverrideFile(Jenkins.getActiveInstance()).exists(); |
405 | | - } |
406 | | - |
407 | 400 | // note that this works better in 1.528+ (JENKINS-19124) |
408 | 401 | @SuppressFBWarnings(value = "RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE", justification = "Only on newer core versions") //TODO remove when core is bumped |
409 | 402 | public FormValidation doCheckServer(@QueryParameter String value, @QueryParameter String managerDN, @QueryParameter Secret managerPasswordSecret) { |
@@ -572,50 +565,80 @@ static String normalizeServer(String server) { /*package scope for testing*/ |
572 | 565 | return StringUtils.join(normalised, ' '); |
573 | 566 | } |
574 | 567 |
|
575 | | - @Restricted(NoExternalUse.class) |
576 | | - public WebApplicationContext createApplicationContext(LDAPSecurityRealm realm, boolean usePotentialUserProvidedBinding) { |
577 | | - Binding binding = new Binding(); |
578 | | - binding.setVariable("instance", this); |
579 | | - binding.setVariable("realmInstance", realm); |
580 | | - |
581 | | - final Jenkins jenkins = Jenkins.getInstance(); |
582 | | - if (jenkins == null) { |
583 | | - throw new IllegalStateException("Jenkins has not been started, or was already shut down"); |
| 568 | + public static final class ApplicationContext { |
| 569 | + public final AuthenticationManager authenticationManager; |
| 570 | + public final LdapUserSearch ldapUserSearch; |
| 571 | + public final LdapAuthoritiesPopulator ldapAuthoritiesPopulator; |
| 572 | + ApplicationContext(AuthenticationManager authenticationManager, LdapUserSearch ldapUserSearch, LdapAuthoritiesPopulator ldapAuthoritiesPopulator) { |
| 573 | + this.authenticationManager = authenticationManager; |
| 574 | + this.ldapUserSearch = ldapUserSearch; |
| 575 | + this.ldapAuthoritiesPopulator = ldapAuthoritiesPopulator; |
584 | 576 | } |
| 577 | + } |
585 | 578 |
|
586 | | - BeanBuilder builder = new BeanBuilder(jenkins.pluginManager.uberClassLoader); |
587 | | - try { |
588 | | - File override = getLdapBindOverrideFile(jenkins); |
589 | | - if (usePotentialUserProvidedBinding && override.exists()) { |
590 | | - builder.parse(new AutoCloseInputStream(new FileInputStream(override)), binding); |
591 | | - } else { |
592 | | - if (override.exists()) { |
593 | | - LOGGER.warning("Not loading custom " + SECURITY_REALM_LDAPBIND_GROOVY); |
594 | | - } |
595 | | - builder.parse(new AutoCloseInputStream(LDAPSecurityRealm.class.getResourceAsStream(SECURITY_REALM_LDAPBIND_GROOVY)), binding); |
596 | | - } |
| 579 | + @Restricted(NoExternalUse.class) |
| 580 | + public ApplicationContext createApplicationContext(LDAPSecurityRealm realm) { |
| 581 | + DefaultInitialDirContextFactory initialDirContextFactory = new DefaultInitialDirContextFactory(getLDAPURL()); |
| 582 | + if (getManagerDN() != null) { |
| 583 | + initialDirContextFactory.setManagerDn(getManagerDN()); |
| 584 | + initialDirContextFactory.setManagerPassword(getManagerPassword()); |
| 585 | + } |
| 586 | + Map<String, String> vars = new HashMap<>(); |
| 587 | + vars.put(Context.REFERRAL, "follow"); |
| 588 | + vars.put("com.sun.jndi.ldap.connect.timeout", "30000"); // timeout if no connection after 30 seconds |
| 589 | + vars.put("com.sun.jndi.ldap.read.timeout", "60000"); // timeout if no response after 60 seconds |
| 590 | + vars.putAll(getExtraEnvVars()); |
| 591 | + initialDirContextFactory.setExtraEnvVars(vars); |
| 592 | + |
| 593 | + FilterBasedLdapUserSearch ldapUserSearch = new FilterBasedLdapUserSearch(getUserSearchBase(), getUserSearch(), initialDirContextFactory); |
| 594 | + ldapUserSearch.setSearchSubtree(true); |
| 595 | + |
| 596 | + BindAuthenticator2 bindAuthenticator = new BindAuthenticator2(initialDirContextFactory); |
| 597 | + // this is when you the user name can be translated into DN. |
| 598 | + // bindAuthenticator.setUserDnPatterns(new String[] {"uid={0},ou=people"}); |
| 599 | + // this is when we need to find it. |
| 600 | + bindAuthenticator.setUserSearch(ldapUserSearch); |
| 601 | + |
| 602 | + LDAPSecurityRealm.AuthoritiesPopulatorImpl ldapAuthoritiesPopulator = new LDAPSecurityRealm.AuthoritiesPopulatorImpl(initialDirContextFactory, getGroupSearchBase()); |
| 603 | + ldapAuthoritiesPopulator.setSearchSubtree(true); |
| 604 | + ldapAuthoritiesPopulator.setGroupSearchFilter("(| (member={0}) (uniqueMember={0}) (memberUid={1}))"); |
| 605 | + if (realm.isDisableRolePrefixing()) { |
| 606 | + ldapAuthoritiesPopulator.setRolePrefix(""); |
| 607 | + ldapAuthoritiesPopulator.setConvertToUpperCase(false); |
| 608 | + } |
597 | 609 |
|
598 | | - } catch (FileNotFoundException e) { |
599 | | - throw new IllegalStateException("Failed to load "+ SECURITY_REALM_LDAPBIND_GROOVY, e); |
| 610 | + ProviderManager authenticationManager = new ProviderManager(); |
| 611 | + List<AuthenticationProvider> providers = new ArrayList<>(); |
| 612 | + // talk to LDAP |
| 613 | + providers.add(new LDAPSecurityRealm.LdapAuthenticationProviderImpl(bindAuthenticator, ldapAuthoritiesPopulator, getGroupMembershipStrategy())); |
| 614 | + // these providers apply everywhere |
| 615 | + { |
| 616 | + RememberMeAuthenticationProvider rememberMeAuthenticationProvider = new RememberMeAuthenticationProvider(); |
| 617 | + rememberMeAuthenticationProvider.setKey(Jenkins.getInstance().getSecretKey()); |
| 618 | + providers.add(rememberMeAuthenticationProvider); |
600 | 619 | } |
601 | | - WebApplicationContext appContext = builder.createApplicationContext(); |
| 620 | + { |
| 621 | + // this doesn't mean we allow anonymous access. |
| 622 | + // we just authenticate anonymous users as such, |
| 623 | + // so that later authorization can reject them if so configured |
| 624 | + AnonymousAuthenticationProvider anonymousAuthenticationProvider = new AnonymousAuthenticationProvider(); |
| 625 | + anonymousAuthenticationProvider.setKey("anonymous"); |
| 626 | + providers.add(anonymousAuthenticationProvider); |
| 627 | + } |
| 628 | + authenticationManager.setProviders(providers); |
602 | 629 |
|
603 | | - ldapTemplate = new LDAPExtendedTemplate(SecurityRealm.findBean(InitialDirContextFactory.class, appContext)); |
| 630 | + ldapTemplate = new LDAPExtendedTemplate(initialDirContextFactory); |
604 | 631 |
|
605 | 632 | if (groupMembershipStrategy != null) { |
606 | | - groupMembershipStrategy.setAuthoritiesPopulator(SecurityRealm.findBean(LdapAuthoritiesPopulator.class, appContext)); |
| 633 | + groupMembershipStrategy.setAuthoritiesPopulator(ldapAuthoritiesPopulator); |
607 | 634 | } |
608 | 635 |
|
609 | | - return appContext; |
| 636 | + return new ApplicationContext(authenticationManager, ldapUserSearch, ldapAuthoritiesPopulator); |
610 | 637 | } |
611 | 638 |
|
612 | 639 | @Restricted(NoExternalUse.class) |
613 | 640 | public LDAPExtendedTemplate getLdapTemplate() { |
614 | 641 | return ldapTemplate; |
615 | 642 | } |
616 | 643 |
|
617 | | - @Restricted(NoExternalUse.class) |
618 | | - public static File getLdapBindOverrideFile(Jenkins jenkins) { |
619 | | - return new File(jenkins.getRootDir(), SECURITY_REALM_LDAPBIND_GROOVY); |
620 | | - } |
621 | 644 | } |
0 commit comments