Modernized configuration UI and data binding.

This commit is contained in:
Jesse Glick 2014-05-07 13:55:05 -04:00
parent 9f7037d383
commit 0f4e172636
11 changed files with 169 additions and 137 deletions

View file

@ -26,53 +26,18 @@ package hudson.security;
import groovy.lang.Binding;
import hudson.Extension;
import static hudson.Util.fixNull;
import static hudson.Util.fixEmptyAndTrim;
import static hudson.Util.fixEmpty;
import static hudson.Util.fixEmptyAndTrim;
import static hudson.Util.fixNull;
import hudson.model.AbstractDescribableImpl;
import hudson.model.Descriptor;
import hudson.tasks.Mailer;
import jenkins.model.Jenkins;
import hudson.model.User;
import hudson.tasks.MailAddressResolver;
import hudson.tasks.Mailer;
import hudson.util.FormValidation;
import hudson.util.ListBoxModel;
import hudson.util.Scrambler;
import hudson.util.spring.BeanBuilder;
import org.acegisecurity.Authentication;
import org.acegisecurity.AuthenticationManager;
import org.acegisecurity.GrantedAuthority;
import org.acegisecurity.AcegiSecurityException;
import org.acegisecurity.AuthenticationException;
import org.acegisecurity.GrantedAuthorityImpl;
import org.acegisecurity.ldap.InitialDirContextFactory;
import org.acegisecurity.ldap.LdapDataAccessException;
import org.acegisecurity.ldap.LdapTemplate;
import org.acegisecurity.ldap.LdapUserSearch;
import org.acegisecurity.ldap.search.FilterBasedLdapUserSearch;
import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
import org.acegisecurity.providers.ldap.LdapAuthoritiesPopulator;
import org.acegisecurity.providers.ldap.populator.DefaultLdapAuthoritiesPopulator;
import org.acegisecurity.userdetails.UserDetails;
import org.acegisecurity.userdetails.UserDetailsService;
import org.acegisecurity.userdetails.UsernameNotFoundException;
import org.acegisecurity.userdetails.ldap.LdapUserDetails;
import org.acegisecurity.userdetails.ldap.LdapUserDetailsImpl;
import org.apache.commons.collections.map.LRUMap;
import org.apache.commons.io.input.AutoCloseInputStream;
import org.apache.commons.lang.StringUtils;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;
import org.springframework.dao.DataAccessException;
import org.springframework.web.context.WebApplicationContext;
import javax.naming.Context;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.BasicAttributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
@ -94,7 +59,40 @@ import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.naming.Context;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.BasicAttributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import jenkins.model.Jenkins;
import org.acegisecurity.AcegiSecurityException;
import org.acegisecurity.Authentication;
import org.acegisecurity.AuthenticationException;
import org.acegisecurity.AuthenticationManager;
import org.acegisecurity.GrantedAuthority;
import org.acegisecurity.GrantedAuthorityImpl;
import org.acegisecurity.ldap.InitialDirContextFactory;
import org.acegisecurity.ldap.LdapDataAccessException;
import org.acegisecurity.ldap.LdapTemplate;
import org.acegisecurity.ldap.LdapUserSearch;
import org.acegisecurity.ldap.search.FilterBasedLdapUserSearch;
import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
import org.acegisecurity.providers.ldap.LdapAuthoritiesPopulator;
import org.acegisecurity.providers.ldap.populator.DefaultLdapAuthoritiesPopulator;
import org.acegisecurity.userdetails.UserDetails;
import org.acegisecurity.userdetails.UserDetailsService;
import org.acegisecurity.userdetails.UsernameNotFoundException;
import org.acegisecurity.userdetails.ldap.LdapUserDetails;
import org.acegisecurity.userdetails.ldap.LdapUserDetailsImpl;
import org.apache.commons.collections.map.LRUMap;
import org.apache.commons.io.input.AutoCloseInputStream;
import org.apache.commons.lang.StringUtils;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;
import org.springframework.dao.DataAccessException;
import org.springframework.web.context.WebApplicationContext;
/**
* {@link SecurityRealm} implementation that uses LDAP for authentication.
@ -213,19 +211,17 @@ import java.util.regex.Pattern;
*
* <dt><a href="http://www.ietf.org/rfc/rfc2256.txt">RFC 2256</a>
* <dd>
* Defines the meaning of several key datatypes used in the schemas with some explanations.
* Defines the meaning of several key datatypes used in the schemas with some explanations.
*
* <dt><a href="http://msdn.microsoft.com/en-us/library/ms675085(VS.85).aspx">Active Directory schema</a>
* <dd>
* More navigable schema list, including core and MS extensions specific to Active Directory.
* </dl>
*
*
* @author Kohsuke Kawaguchi
* @since 1.166
*/
public class LDAPSecurityRealm extends AbstractPasswordBasedSecurityRealm {
private static final String DEFAULT_DISPLAYNAME_ATTRIBUTE_NAME = "displayname";
private static final String DEFAULT_MAILADDRESS_ATTRIBUTE_NAME = "mail";
private static final boolean FORCE_USERNAME_LOWERCASE =
Boolean.getBoolean(LDAPSecurityRealm.class.getName() + ".forceUsernameLowercase");
/**
@ -402,7 +398,7 @@ public class LDAPSecurityRealm extends AbstractPasswordBasedSecurityRealm {
this.rootDN = rootDN.trim();
this.userSearchBase = fixNull(userSearchBase).trim();
userSearch = fixEmptyAndTrim(userSearch);
this.userSearch = userSearch!=null ? userSearch : "uid={0}";
this.userSearch = userSearch!=null ? userSearch : DescriptorImpl.DEFAULT_USER_SEARCH;
this.groupSearchBase = fixEmptyAndTrim(groupSearchBase);
this.groupSearchFilter = fixEmptyAndTrim(groupSearchFilter);
this.groupMembershipFilter = fixEmptyAndTrim(groupMembershipFilter);
@ -412,9 +408,9 @@ public class LDAPSecurityRealm extends AbstractPasswordBasedSecurityRealm {
? null
: EnvironmentProperty.toMap(Arrays.asList(environmentProperties));
this.displayNameAttributeName = StringUtils.defaultString(fixEmptyAndTrim(displayNameAttributeName),
DEFAULT_DISPLAYNAME_ATTRIBUTE_NAME);
DescriptorImpl.DEFAULT_DISPLAYNAME_ATTRIBUTE_NAME);
this.mailAddressAttributeName = StringUtils.defaultString(fixEmptyAndTrim(mailAddressAttributeName),
DEFAULT_MAILADDRESS_ATTRIBUTE_NAME);
DescriptorImpl.DEFAULT_MAILADDRESS_ATTRIBUTE_NAME);
}
public String getServerUrl() {
@ -522,11 +518,11 @@ public class LDAPSecurityRealm extends AbstractPasswordBasedSecurityRealm {
}
public String getDisplayNameAttributeName() {
return StringUtils.defaultString(displayNameAttributeName, DEFAULT_DISPLAYNAME_ATTRIBUTE_NAME);
return StringUtils.defaultString(displayNameAttributeName, DescriptorImpl.DEFAULT_DISPLAYNAME_ATTRIBUTE_NAME);
}
public String getMailAddressAttributeName() {
return StringUtils.defaultString(mailAddressAttributeName, DEFAULT_MAILADDRESS_ATTRIBUTE_NAME);
return StringUtils.defaultString(mailAddressAttributeName, DescriptorImpl.DEFAULT_MAILADDRESS_ATTRIBUTE_NAME);
}
public SecurityComponents createSecurityComponents() {
@ -851,14 +847,18 @@ public class LDAPSecurityRealm extends AbstractPasswordBasedSecurityRealm {
@Extension
public static final class DescriptorImpl extends Descriptor<SecurityRealm> {
public static final String DEFAULT_DISPLAYNAME_ATTRIBUTE_NAME = "displayname";
public static final String DEFAULT_MAILADDRESS_ATTRIBUTE_NAME = "mail";
public static final String DEFAULT_USER_SEARCH = "uid={0}";
public String getDisplayName() {
return Messages.LDAPSecurityRealm_DisplayName();
}
public FormValidation doServerCheck(
@QueryParameter final String server,
@QueryParameter final String managerDN,
@QueryParameter final String managerPassword) {
// note that this works better in 1.528+ (JENKINS-19124)
public FormValidation doCheckServer(@QueryParameter String value, @QueryParameter String managerDN, @QueryParameter String managerPassword) {
String server = value;
if(!Jenkins.getInstance().hasPermission(Jenkins.ADMINISTER))
return FormValidation.ok();
@ -927,7 +927,7 @@ public class LDAPSecurityRealm extends AbstractPasswordBasedSecurityRealm {
public static String GROUP_SEARCH = System.getProperty(LDAPSecurityRealm.class.getName()+".groupSearch",
"(& (cn={0}) (| (objectclass=groupOfNames) (objectclass=groupOfUniqueNames) (objectclass=posixGroup)))");
public static class CacheConfiguration {
public static class CacheConfiguration extends AbstractDescribableImpl<CacheConfiguration> {
private final int size;
private final int ttl;
@ -944,6 +944,40 @@ public class LDAPSecurityRealm extends AbstractPasswordBasedSecurityRealm {
public int getTtl() {
return ttl;
}
@Extension public static class DescriptorImpl extends Descriptor<CacheConfiguration> {
@Override public String getDisplayName() {
return "";
}
public ListBoxModel doFillSizeItems() {
ListBoxModel m = new ListBoxModel();
m.add("10");
m.add("20");
m.add("50");
m.add("100");
m.add("200");
m.add("500");
m.add("1000");
return m;
}
public ListBoxModel doFillTtlItems() {
ListBoxModel m = new ListBoxModel();
// TODO use Messages (not that there were any translations before)
m.add("30 sec", "30");
m.add("1 min", "60");
m.add("2 min", "120");
m.add("5 min", "300");
m.add("10 min", "600");
m.add("15 min", "900");
m.add("30 min", "1800");
m.add("1 hour", "3600");
return m;
}
}
}
private static class CacheEntry<T> {

View file

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
The MIT License
Copyright 2014 Jesse Glick.
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
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-->
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:f="/lib/form">
<f:entry field="size" title="${%Cache size}">
<f:select default="20"/>
</f:entry>
<f:entry field="ttl" title="${%Cache TTL}">
<f:select default="300"/>
</f:entry>
</j:jelly>

View file

@ -32,6 +32,7 @@ THE SOFTWARE.
</f:entry>
<f:entry title="">
<div align="right">
<!-- TODO as of 1.532 this should be pulled up into the parent config.jelly -->
<f:repeatableDeleteButton />
</div>
</f:entry>

View file

@ -23,88 +23,51 @@ THE SOFTWARE.
-->
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<f:entry title="${%Server}" help="/plugin/ldap/help-server.html">
<f:textbox name="ldap.server" value="${instance.server}"
checkUrl="'${rootURL}/securityRealms/LDAPSecurityRealm/serverCheck?field=server&amp;server='+encodeURIComponent(this.value)+'&amp;managerDN='+encodeURIComponent(this.form.elements['ldap.managerDN'].value)+'&amp;managerPassword='+encodeURIComponent(this.form.elements['ldap.managerPassword'].value)"/>
</f:entry>
<f:advanced>
<f:entry title="${%root DN}" help="/help/security/ldap/rootDN.html">
<f:textbox name="ldap.rootDN" value="${instance.rootDN}" />
<f:checkbox name="ldap.inhibitInferRootDN" checked="${instance.inhibitInferRootDN}"
title="${%Allow blank rootDN}"/>
<j:jelly xmlns:j="jelly:core" xmlns:f="/lib/form">
<f:entry field="server" title="${%Server}">
<f:textbox/>
</f:entry>
<f:entry title="${%User search base}" help="/help/security/ldap/userSearchBase.html">
<f:textbox name="ldap.userSearchBase" value="${instance.userSearchBase}" />
</f:entry>
<f:entry title="${%User search filter}" help="/help/security/ldap/userSearchFilter.html">
<f:textbox name="ldap.userSearch" value="${instance.userSearch}" />
</f:entry>
<f:entry title="${%Group search base}" help="/help/security/ldap/groupSearchBase.html">
<f:textbox name="ldap.groupSearchBase" value="${instance.groupSearchBase}" />
</f:entry>
<f:entry title="${%Group search filter}" help="/plugin/ldap/help-groupSearchFilter.html">
<f:textbox name="ldap.groupSearchFilter" value="${instance.groupSearchFilter}" />
</f:entry>
<f:entry title="${%Group membership filter}" help="/plugin/ldap/help-groupMembershipFilter.html">
<f:textbox name="ldap.groupMembershipFilter" value="${instance.groupMembershipFilter}" />
</f:entry>
<f:entry title="${%Manager DN}" help="/help/security/ldap/managerDN.html">
<f:textbox name="ldap.managerDN" value="${instance.managerDN}" autocomplete="off"
checkUrl="'${rootURL}/securityRealms/LDAPSecurityRealm/serverCheck?field=managerDN&amp;server='+encodeURIComponent(this.form.elements['ldap.server'].value)+'&amp;managerDN='+encodeURIComponent(this.value)+'&amp;managerPassword='+encodeURIComponent(this.form.elements['ldap.managerPassword'].value)"
/>
</f:entry>
<f:entry title="${%Manager Password}" help="/help/security/ldap/managerPassword.html">
<f:password name="ldap.managerPassword" value="${instance.managerPassword}" autocomplete="off"
checkUrl="'${rootURL}/securityRealms/LDAPSecurityRealm/serverCheck?field=password&amp;server='+encodeURIComponent(this.form.elements['ldap.server'].value)+'&amp;managerDN='+encodeURIComponent(this.form.elements['ldap.managerDN'].value)+'&amp;managerPassword='+encodeURIComponent(this.value)"
/>
</f:entry>
<f:entry title="${%Display Name LDAP attribute}" help="/plugin/ldap/help-displayNameAttributeName.html">
<f:textbox name="ldap.displayNameAttributeName" value="${instance.displayNameAttributeName}"/>
</f:entry>
<f:entry title="${%Email Address LDAP attribute}" help="/plugin/ldap/help-mailAddressAttributeName.html">
<f:textbox name="ldap.mailAddressAttributeName" value="${instance.mailAddressAttributeName}"/>
</f:entry>
<f:entry title="${%Disable Ldap Email Resolver}">
<f:checkbox name="ldap.disableMailAddressResolver" checked="${instance.disableMailAddressResolver}"></f:checkbox>
</f:entry>
<f:optionalBlock name="ldap.cache" title="${%Enable cache}" checked="${instance.cache != null}" help="/plugin/ldap/help-cache.html">
<f:entry title="${%Cache size}">
<f:radio name="ldap.cache.size" value="10" checked="${instance.cacheSize == 10}" title="10"/>
<st:nbsp />
<f:radio name="ldap.cache.size" value="20" checked="${instance.cacheSize == 20 or instance.cacheSize == null}" title="20"/>
<st:nbsp />
<f:radio name="ldap.cache.size" value="50" checked="${instance.cacheSize == 50}" title="50"/>
<st:nbsp />
<f:radio name="ldap.cache.size" value="100" checked="${instance.cacheSize == 100}" title="100"/>
<st:nbsp />
<f:radio name="ldap.cache.size" value="200" checked="${instance.cacheSize == 200}" title="200"/>
<st:nbsp />
<f:radio name="ldap.cache.size" value="500" checked="${instance.cacheSize == 500}" title="500"/>
<st:nbsp />
<f:radio name="ldap.cache.size" value="1000" checked="${instance.cacheSize == 1000}" title="1000"/>
</f:entry>
<f:entry title="${%Cache TTL}">
<f:radio name="ldap.cache.ttl" value="30" checked="${instance.cacheTTL == 30}" title="${%30 sec}"/>
<st:nbsp />
<f:radio name="ldap.cache.ttl" value="60" checked="${instance.cacheTTL == 60}" title="${%1 min}"/>
<st:nbsp />
<f:radio name="ldap.cache.ttl" value="120" checked="${instance.cacheTTL == 120}" title="${%2 min}"/>
<st:nbsp />
<f:radio name="ldap.cache.ttl" value="300" checked="${instance.cacheTTL == 300 or instance.cacheTTL == null}" title="${%5 min}"/>
<st:nbsp />
<f:radio name="ldap.cache.ttl" value="600" checked="${instance.cacheTTL == 600}" title="${%10 min}"/>
<st:nbsp />
<f:radio name="ldap.cache.ttl" value="900" checked="${instance.cacheTTL == 900}" title="${%15 min}"/>
<st:nbsp />
<f:radio name="ldap.cache.ttl" value="1800" checked="${instance.cacheTTL == 1800}" title="${%30 min}"/>
<st:nbsp />
<f:radio name="ldap.cache.ttl" value="3600" checked="${instance.cacheTTL == 3600}" title="${%1 hour}"/>
</f:entry>
</f:optionalBlock>
<f:entry title="${%Environment Properties}" help="/plugin/ldap/help-envprop.html">
<f:repeatableProperty field="environmentProperties" />
</f:entry>
</f:advanced>
<f:advanced>
<f:entry field="rootDN" title="${%root DN}">
<f:textbox/>
</f:entry>
<f:entry field="inhibitInferRootDN">
<f:checkbox title="${%Allow blank rootDN}"/>
</f:entry>
<f:entry field="userSearchBase" title="${%User search base}">
<f:textbox/>
</f:entry>
<f:entry field="userSearch" title="${%User search filter}">
<f:textbox default="${descriptor.DEFAULT_USER_SEARCH}"/>
</f:entry>
<f:entry field="groupSearchBase" title="${%Group search base}">
<f:textbox/>
</f:entry>
<f:entry field="groupSearchFilter" title="${%Group search filter}">
<f:textbox/>
</f:entry>
<f:entry field="groupMembershipFilter" title="${%Group membership filter}">
<f:textbox/>
</f:entry>
<f:entry field="managerDN" title="${%Manager DN}">
<f:textbox autocomplete="off"/>
</f:entry>
<f:entry field="managerPassword" title="${%Manager Password}">
<f:password autocomplete="off"/>
</f:entry>
<f:entry field="displayNameAttributeName" title="${%Display Name LDAP attribute}">
<f:textbox default="${descriptor.DEFAULT_DISPLAYNAME_ATTRIBUTE_NAME}"/>
</f:entry>
<f:entry field="mailAddressAttributeName" title="${%Email Address LDAP attribute}">
<f:textbox default="${descriptor.DEFAULT_MAILADDRESS_ATTRIBUTE_NAME}"/>
</f:entry>
<f:entry field="disableMailAddressResolver">
<f:checkbox title="${%Disable Ldap Email Resolver}"/>
</f:entry>
<f:optionalProperty field="cache" title="${%Enable cache}"/>
<f:entry field="environmentProperties" title="${%Environment Properties}">
<!-- TODO JENKINS-22910 must repeat the field attr: -->
<f:repeatableProperty field="environmentProperties"/>
</f:entry>
</f:advanced>
</j:jelly>