Rework LDAP implementation with unboundid. Also allows for an LDAP server to be started with Gitblit GO (backed by an LDIF file).
3 files added
10 files modified
| | |
| | | <classpathentry kind="lib" path="ext/org.eclipse.jgit.http.server-1.3.0.201202151440-r.jar" sourcepath="ext/org.eclipse.jgit.http.server-1.3.0.201202151440-r-sources.jar"/> |
| | | <classpathentry kind="lib" path="ext/lucene-highlighter-3.5.0.jar" sourcepath="ext/lucene-highlighter-3.5.0-sources.jar"/> |
| | | <classpathentry kind="lib" path="ext/lucene-memory-3.5.0.jar" sourcepath="ext/lucene-memory-3.5.0-sources.jar"/> |
| | | <classpathentry kind="lib" path="ext/unboundid-ldapsdk-2.3.0.jar" sourcepath="ext/unboundid-ldapsdk-2.3.0-sources.jar"/> |
| | | <classpathentry kind="output" path="bin"/> |
| | | </classpath> |
| | |
| | |
|
| | | http://glyphicons.com
|
| | | |
| | | ---------------------------------------------------------------------------
|
| | | UnboundID
|
| | | ---------------------------------------------------------------------------
|
| | | UnboundID, released under the
|
| | | GNU LESSER GENERAL PUBLIC LICENSE. (http://www.unboundid.com/products/ldap-sdk/docs/LICENSE-LGPLv2.1.txt)
|
| | |
|
| | | http://www.unboundid.com
|
| | | |
| | |
| | | # SINCE 1.0.0
|
| | | realm.ldap.server = ldap://my.ldap.server
|
| | |
|
| | | # The LDAP domain to prepend to all usernames during authentication. If
|
| | | # unspecified, all logins must prepend the domain to their username.
|
| | | # e.g. mydomain
|
| | | #
|
| | | # SINCE 1.0.0
|
| | | realm.ldap.domain = |
| | |
|
| | | # Login username for LDAP searches.
|
| | | # The domain prefix may be omitted if it matches the domain specified in
|
| | | # *realm.ldap.domain*. If this value is unspecified, anonymous LDAP login will
|
| | |
| | | # SINCE 1.0.0
|
| | | realm.ldap.maintainTeams = false
|
| | |
|
| | | # Root node that all Users sit under in LDAP
|
| | | #
|
| | | # This is the node that searches for user information will begin from in LDAP
|
| | | # If blank, it will search ALL of ldap. |
| | | #
|
| | | # SINCE 1.0.0
|
| | | realm.ldap.accountBase = ou=people,dc=example,dc=com
|
| | |
|
| | | # Filter Criteria for Users in LDAP
|
| | | #
|
| | | # Query pattern to use when searching for a user account. This may be any valid |
| | | # LDAP query expression, including the standard (&) and (|) operators.
|
| | | # The variable ${username} is replaced by the string entered by the end user
|
| | | #
|
| | | # SINCE 1.0.0
|
| | | realm.ldap.accountPattern = (&(objectClass=person)(sAMAccountName=${username}))
|
| | |
|
| | | # Root node that all Teams sit under in LDAP
|
| | | #
|
| | | # This is the node that searches for user information will begin from in LDAP
|
| | | # If blank, it will search ALL of ldap. |
| | | #
|
| | | # SINCE 1.0.0
|
| | | realm.ldap.groupBase = ou=groups,dc=example,dc=com
|
| | |
|
| | | # Filter Criteria for Teams in LDAP
|
| | | #
|
| | | # Query pattern to use when searching for a team. This may be any valid |
| | | # LDAP query expression, including the standard (&) and (|) operators.
|
| | | # The variable ${username} is replaced by the string entered by the end user.
|
| | | # Other variables appearing in the pattern, such as ${fooBarAttribute}, |
| | | # are replaced with the value of the corresponding attribute (in this case, fooBarAttribute) |
| | | # as read from the user's account object matched under realm.ldap.accountBase. Attributes such |
| | | # as ${dn} or ${uidNumber} may be useful.
|
| | | #
|
| | | # SINCE 1.0.0
|
| | | realm.ldap.groupMemberPattern = (&(objectClass=group)(member=${dn}))
|
| | |
|
| | | # Users and or teams that are Admins, read from LDAP
|
| | | #
|
| | | # This is a space delimited list. If it starts with @, it indicates a Team Name |
| | | #
|
| | | # SINCE 1.0.0
|
| | | realm.ldap.admins= @Git_Admins
|
| | |
|
| | | #
|
| | | # Gitblit Web Settings
|
| | | #
|
| | |
| | | - [javamail](http://kenai.com/projects/javamail) (CDDL-1.0, BSD, GPL-2.0, GNU-Classpath)
|
| | | - [Groovy](http://groovy.codehaus.org) (Apache 2.0)
|
| | | - [Lucene](http://lucene.apache.org) (Apache 2.0)
|
| | | - [UnboundID](http://www.unboundid.com) (LGPL 2.1)
|
| | |
|
| | | ### Other Build Dependencies
|
| | | - [Fancybox image viewer](http://fancybox.net) (MIT and GPL dual-licensed)
|
| | |
| | | import java.text.MessageFormat;
|
| | | import java.util.ArrayList;
|
| | | import java.util.List;
|
| | | import java.util.Scanner;
|
| | |
|
| | | import org.eclipse.jetty.ajp.Ajp13SocketConnector;
|
| | | import org.eclipse.jetty.server.Connector;
|
| | |
| | | import com.beust.jcommander.ParameterException;
|
| | | import com.beust.jcommander.Parameters;
|
| | | import com.gitblit.utils.StringUtils;
|
| | | import com.unboundid.ldap.listener.InMemoryDirectoryServer;
|
| | | import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
|
| | | import com.unboundid.ldap.listener.InMemoryListenerConfig;
|
| | | import com.unboundid.ldif.LDIFReader;
|
| | |
|
| | | /**
|
| | | * GitBlitServer is the embedded Jetty server for Gitblit GO. This class starts
|
| | |
| | | // Override settings from the command-line
|
| | | settings.overrideSetting(Keys.realm.userService, params.userService);
|
| | | settings.overrideSetting(Keys.git.repositoriesFolder, params.repositoriesFolder);
|
| | | |
| | | // Start up an in-memory LDAP server, if configured
|
| | | try {
|
| | | if (StringUtils.isEmpty(params.ldapLdifFile) == false) {
|
| | | File ldifFile = new File(params.ldapLdifFile);
|
| | | if (ldifFile != null && ldifFile.exists()) {
|
| | | String firstLine = new Scanner(ldifFile).nextLine();
|
| | | String rootDN = firstLine.substring(4);
|
| | | String bindUserName = settings.getString(Keys.realm.ldap_username, "");
|
| | | String bindPassword = settings.getString(Keys.realm.ldap_password, "");
|
| | | |
| | | InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(rootDN);
|
| | | config.addAdditionalBindCredentials(bindUserName, bindPassword);
|
| | | config.setListenerConfigs(InMemoryListenerConfig.createLDAPConfig("default", 389));
|
| | | config.setSchema(null);
|
| | | |
| | | InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);
|
| | | ds.importFromLDIF(true, new LDIFReader(ldifFile));
|
| | | ds.startListening();
|
| | | |
| | | logger.info("LDAP Server started at ldap://localhost:389");
|
| | | }
|
| | | }
|
| | | } catch (Exception e) {
|
| | | // Completely optional, just show a warning
|
| | | logger.warn("Unable to start LDAP server", e);
|
| | | }
|
| | |
|
| | | // Set the server's contexts
|
| | | server.setHandler(rootContext);
|
| | |
| | | @Parameter(names = { "--settings" }, description = "Path to alternative settings")
|
| | | public String settingsfile;
|
| | |
|
| | | @Parameter(names = { "--ldapLdifFile" }, description = "Path to LDIF file. This will cause an in-memory LDAP server to be started according to gitblit settings")
|
| | | public String ldapLdifFile;
|
| | |
|
| | | }
|
| | | } |
| | |
| | | public boolean updateUserModel(String username, UserModel model) {
|
| | | if (supportsCredentialChanges()) {
|
| | | if (!supportsTeamMembershipChanges()) {
|
| | | // teams are externally controlled
|
| | | // teams are externally controlled - copy from original model
|
| | | UserModel existingModel = getUserModel(username);
|
| | | |
| | | model = DeepCopier.copy(model);
|
| | | model.teams.clear();
|
| | | model.teams.addAll(existingModel.teams);
|
| | | }
|
| | | return serviceImpl.updateUserModel(username, model);
|
| | | }
|
| | |
| | | // passwords are not persisted by the backing user service
|
| | | model.password = null;
|
| | | if (!supportsTeamMembershipChanges()) {
|
| | | // teams are externally controlled
|
| | | // teams are externally controlled- copy from original model
|
| | | UserModel existingModel = getUserModel(username);
|
| | | |
| | | model = DeepCopier.copy(model);
|
| | | model.teams.clear();
|
| | | model.teams.addAll(existingModel.teams);
|
| | | }
|
| | | return serviceImpl.updateUserModel(username, model);
|
| | | }
|
| | |
| | | @Override
|
| | | public boolean updateTeamModel(String teamname, TeamModel model) {
|
| | | if (!supportsTeamMembershipChanges()) {
|
| | | // teams are externally controlled
|
| | | // teams are externally controlled - copy from original model
|
| | | TeamModel existingModel = getTeamModel(teamname);
|
| | | |
| | | model = DeepCopier.copy(model);
|
| | | model.users.clear();
|
| | | model.users.addAll(existingModel.users);
|
| | | }
|
| | | return serviceImpl.updateTeamModel(teamname, model);
|
| | | }
|
| | |
| | | package com.gitblit;
|
| | |
|
| | | import java.io.File;
|
| | | import java.text.MessageFormat;
|
| | | import java.util.HashSet;
|
| | | import java.util.Hashtable;
|
| | | import java.util.Set;
|
| | |
|
| | | import javax.naming.Context;
|
| | | import javax.naming.NamingException;
|
| | | import javax.naming.directory.DirContext;
|
| | | import javax.naming.directory.InitialDirContext;
|
| | | import java.net.URI;
|
| | | import java.net.URISyntaxException;
|
| | | import java.security.GeneralSecurityException;
|
| | |
|
| | | import org.slf4j.Logger;
|
| | | import org.slf4j.LoggerFactory;
|
| | |
|
| | | import com.gitblit.models.TeamModel;
|
| | | import com.gitblit.models.UserModel;
|
| | | import com.gitblit.utils.ConnectionUtils.BlindSSLSocketFactory;
|
| | | import com.gitblit.utils.StringUtils;
|
| | | import com.unboundid.ldap.sdk.Attribute;
|
| | | import com.unboundid.ldap.sdk.LDAPConnection;
|
| | | import com.unboundid.ldap.sdk.LDAPException;
|
| | | import com.unboundid.ldap.sdk.LDAPSearchException;
|
| | | import com.unboundid.ldap.sdk.SearchResult;
|
| | | import com.unboundid.ldap.sdk.SearchResultEntry;
|
| | | import com.unboundid.ldap.sdk.SearchScope;
|
| | | import com.unboundid.util.ssl.SSLUtil;
|
| | | import com.unboundid.util.ssl.TrustAllTrustManager;
|
| | |
|
| | | /**
|
| | | * Implementation of an LDAP user service.
|
| | |
| | | public class LdapUserService extends GitblitUserService {
|
| | |
|
| | | public static final Logger logger = LoggerFactory.getLogger(LdapUserService.class);
|
| | | private final String CONTEXT_FACTORY = "com.sun.jndi.ldap.LdapCtxFactory";
|
| | |
|
| | | private IStoredSettings settings;
|
| | |
|
| | |
| | |
|
| | | serviceImpl = createUserService(realmFile);
|
| | | logger.info("LDAP User Service backed by " + serviceImpl.toString());
|
| | | }
|
| | | |
| | | private LDAPConnection getLdapConnection() {
|
| | | try {
|
| | | URI ldapUrl = new URI(settings.getRequiredString(Keys.realm.ldap_server));
|
| | | String bindUserName = settings.getString(Keys.realm.ldap_username, "");
|
| | | String bindPassword = settings.getString(Keys.realm.ldap_password, "");
|
| | | int ldapPort = ldapUrl.getPort();
|
| | | |
| | | if (ldapUrl.getScheme().equalsIgnoreCase("ldaps")) { // SSL
|
| | | if (ldapPort == -1) // Default Port
|
| | | ldapPort = 636;
|
| | | |
| | | SSLUtil sslUtil = new SSLUtil(new TrustAllTrustManager()); |
| | | return new LDAPConnection(sslUtil.createSSLSocketFactory(), ldapUrl.getHost(), ldapPort, bindUserName, bindPassword);
|
| | | } else {
|
| | | if (ldapPort == -1) // Default Port
|
| | | ldapPort = 389;
|
| | | |
| | | return new LDAPConnection(ldapUrl.getHost(), ldapPort, bindUserName, bindPassword);
|
| | | }
|
| | | } catch (URISyntaxException e) {
|
| | | logger.error("Bad LDAP URL, should be in the form: ldap(s)://<server>:<port>", e);
|
| | | } catch (GeneralSecurityException e) {
|
| | | logger.error("Unable to create SSL Connection", e);
|
| | | } catch (LDAPException e) {
|
| | | logger.error("Error Connecting to LDAP", e);
|
| | | }
|
| | | |
| | | return null;
|
| | | }
|
| | |
|
| | | /**
|
| | |
| | |
|
| | | @Override
|
| | | public UserModel authenticate(String username, char[] password) {
|
| | | String domainUser = getDomainUsername(username); |
| | | DirContext ctx = getDirContext(domainUser, new String(password));
|
| | | // TODO do we need a bind here?
|
| | | if (ctx != null) {
|
| | | String simpleUsername = getSimpleUsername(username);
|
| | | |
| | | LDAPConnection ldapConnection = getLdapConnection(); |
| | | if (ldapConnection != null) {
|
| | | // Find the logging in user's DN
|
| | | String accountBase = settings.getString(Keys.realm.ldap_accountBase, "");
|
| | | String accountPattern = settings.getString(Keys.realm.ldap_accountPattern, "(&(objectClass=person)(sAMAccountName=${username}))");
|
| | | accountPattern = StringUtils.replace(accountPattern, "${username}", simpleUsername);
|
| | |
|
| | | SearchResult result = doSearch(ldapConnection, accountBase, accountPattern);
|
| | | if (result != null && result.getEntryCount() == 1) {
|
| | | SearchResultEntry loggingInUser = result.getSearchEntries().get(0);
|
| | | String loggingInUserDN = loggingInUser.getDN();
|
| | | |
| | | if (isAuthenticated(ldapConnection, loggingInUserDN, new String(password))) {
|
| | | logger.debug("Authenitcated: " + username);
|
| | | |
| | | UserModel user = getUserModel(simpleUsername);
|
| | | if (user == null) {
|
| | | // create user object for new authenticated user
|
| | | user = new UserModel(simpleUsername.toLowerCase());
|
| | | }
|
| | | user.password = new String(password);
|
| | | if (user == null) // create user object for new authenticated user
|
| | | user = createUserFromLdap(loggingInUser);
|
| | |
|
| | | user.password = "StoredInLDAP";
|
| | | |
| | | if (!supportsTeamMembershipChanges())
|
| | | getTeamsFromLdap(ldapConnection, simpleUsername, loggingInUser, user);
|
| | | |
| | | // Get Admin Attributes
|
| | | setAdminAttribute(user);
|
| | |
|
| | | // Push the ldap looked up values to backing file
|
| | | super.updateUserModel(user);
|
| | | if (!supportsTeamMembershipChanges()) {
|
| | | // Teams are specified in LDAP server
|
| | | // TODO search LDAP for team memberships
|
| | | Set<String> foundTeams = new HashSet<String>();
|
| | | for (String team : foundTeams) { |
| | | TeamModel model = getTeamModel(team);
|
| | | if (model == null) {
|
| | | // create the team
|
| | | model = new TeamModel(team.toLowerCase());
|
| | | updateTeamModel(model);
|
| | | }
|
| | | // add team to the user
|
| | | user.teams.add(model);
|
| | | }
|
| | | for (TeamModel userTeam : user.teams)
|
| | | updateTeamModel(userTeam);
|
| | | }
|
| | |
|
| | | try {
|
| | | ctx.close();
|
| | | } catch (NamingException e) {
|
| | | logger.error("Can not close context", e);
|
| | | }
|
| | | return user;
|
| | | }
|
| | | }
|
| | | }
|
| | | |
| | | return null;
|
| | | }
|
| | |
|
| | | protected DirContext getDirContext() {
|
| | | String username = settings.getString(Keys.realm.ldap_username, "");
|
| | | String password = settings.getString(Keys.realm.ldap_password, "");
|
| | | return getDirContext(username, password);
|
| | | private void setAdminAttribute(UserModel user) {
|
| | | String adminString = settings.getString(Keys.realm.ldap_admins, "");
|
| | | String[] admins = adminString.split(" ");
|
| | | user.canAdmin = false;
|
| | | for (String admin : admins) {
|
| | | if (admin.startsWith("@")) { // Team
|
| | | if (user.getTeam(admin.substring(1)) != null)
|
| | | user.canAdmin = true;
|
| | | } else
|
| | | if (user.getName().equalsIgnoreCase(admin))
|
| | | user.canAdmin = true;
|
| | | }
|
| | | }
|
| | |
|
| | | protected DirContext getDirContext(String username, String password) {
|
| | | private void getTeamsFromLdap(LDAPConnection ldapConnection, String simpleUsername, SearchResultEntry loggingInUser, UserModel user) {
|
| | | String loggingInUserDN = loggingInUser.getDN();
|
| | | |
| | | user.teams.clear(); // Clear the users team memberships - we're going to get them from LDAP
|
| | | String groupBase = settings.getString(Keys.realm.ldap_groupBase, "");
|
| | | String groupMemberPattern = settings.getString(Keys.realm.ldap_groupMemberPattern, "(&(objectClass=group)(member=${dn}))");
|
| | | |
| | | groupMemberPattern = StringUtils.replace(groupMemberPattern, "${dn}", loggingInUserDN);
|
| | | groupMemberPattern = StringUtils.replace(groupMemberPattern, "${username}", simpleUsername);
|
| | | |
| | | // Fill in attributes into groupMemberPattern
|
| | | for (Attribute userAttribute : loggingInUser.getAttributes())
|
| | | groupMemberPattern = StringUtils.replace(groupMemberPattern, "${" + userAttribute.getName() + "}", userAttribute.getValue());
|
| | | |
| | | SearchResult teamMembershipResult = doSearch(ldapConnection, groupBase, groupMemberPattern);
|
| | | if (teamMembershipResult != null && teamMembershipResult.getEntryCount() > 0) {
|
| | | for (int i = 0; i < teamMembershipResult.getEntryCount(); i++) {
|
| | | SearchResultEntry teamEntry = teamMembershipResult.getSearchEntries().get(i);
|
| | | String teamName = teamEntry.getAttribute("cn").getValue();
|
| | | |
| | | TeamModel teamModel = getTeamModel(teamName);
|
| | | if (teamModel == null)
|
| | | teamModel = createTeamFromLdap(teamEntry);
|
| | | |
| | | user.teams.add(teamModel);
|
| | | teamModel.addUser(user.getName());
|
| | | }
|
| | | }
|
| | | }
|
| | | |
| | | private TeamModel createTeamFromLdap(SearchResultEntry teamEntry) {
|
| | | TeamModel answer = new TeamModel(teamEntry.getAttributeValue("cn"));
|
| | | // If attributes other than team name ever from from LDAP, this is where to get them
|
| | | |
| | | return answer; |
| | | }
|
| | | |
| | | private UserModel createUserFromLdap(SearchResultEntry userEntry) {
|
| | | UserModel answer = new UserModel(userEntry.getAttributeValue("cn"));
|
| | | //If attributes other than user name ever from from LDAP, this is where to get them
|
| | | |
| | | return answer;
|
| | | }
|
| | |
|
| | | private SearchResult doSearch(LDAPConnection ldapConnection, String base, String filter) {
|
| | | try {
|
| | | String server = settings.getRequiredString(Keys.realm.ldap_server);
|
| | | Hashtable<String, String> env = new Hashtable<String, String>();
|
| | | env.put(Context.INITIAL_CONTEXT_FACTORY, CONTEXT_FACTORY);
|
| | | env.put(Context.PROVIDER_URL, server);
|
| | | if (server.startsWith("ldaps:")) {
|
| | | env.put("java.naming.ldap.factory.socket", BlindSSLSocketFactory.class.getName());
|
| | | }
|
| | | // TODO consider making this a setting
|
| | | env.put("com.sun.jndi.ldap.read.timeout", "5000");
|
| | | return ldapConnection.search(base, SearchScope.SUB, filter);
|
| | | } catch (LDAPSearchException e) {
|
| | | logger.error("Problem Searching LDAP", e);
|
| | |
|
| | | if (!StringUtils.isEmpty(username)) {
|
| | | // authenticated login
|
| | | env.put(Context.SECURITY_AUTHENTICATION, "simple");
|
| | | env.put(Context.SECURITY_PRINCIPAL, getDomainUsername(username));
|
| | | env.put(Context.SECURITY_CREDENTIALS, password == null ? "":password.trim());
|
| | | }
|
| | | return new InitialDirContext(env);
|
| | | } catch (NamingException e) {
|
| | | logger.warn(MessageFormat.format("Error connecting to LDAP with credentials. Please check {0}, {1}, and {2}",
|
| | | Keys.realm.ldap_server, Keys.realm.ldap_username, Keys.realm.ldap_password), e);
|
| | | return null;
|
| | | }
|
| | | }
|
| | | |
| | | private boolean isAuthenticated(LDAPConnection ldapConnection, String userDn, String password) {
|
| | | try {
|
| | | ldapConnection.bind(userDn, password);
|
| | | return true;
|
| | | } catch (LDAPException e) {
|
| | | logger.error("Error authenitcating user", e);
|
| | | return false;
|
| | | }
|
| | | }
|
| | |
|
| | |
|
| | | /**
|
| | | * Returns a simple username without any domain prefixes.
|
| | |
| | | username = username.substring(lastSlash + 1);
|
| | | }
|
| | | return username;
|
| | | }
|
| | |
|
| | | /**
|
| | | * Returns a username with a domain prefix as long as the username does not
|
| | | * already have a comain prefix.
|
| | | * |
| | | * @param username
|
| | | * @return a domain username
|
| | | */
|
| | | protected String getDomainUsername(String username) {
|
| | | String domain = settings.getString(Keys.realm.ldap_domain, null);
|
| | | String domainUsername = username;
|
| | | if (!StringUtils.isEmpty(domain) && (domainUsername.indexOf('\\') == -1)) {
|
| | | domainUsername = domain + "\\" + username;
|
| | | }
|
| | | return domainUsername.trim();
|
| | | }
|
| | | }
|
| | |
| | | downloadFromApache(MavenObject.LUCENE, BuildType.RUNTIME);
|
| | | downloadFromApache(MavenObject.LUCENE_HIGHLIGHTER, BuildType.RUNTIME);
|
| | | downloadFromApache(MavenObject.LUCENE_MEMORY, BuildType.RUNTIME);
|
| | | downloadFromApache(MavenObject.UNBOUND_ID, BuildType.RUNTIME);
|
| | |
|
| | | downloadFromEclipse(MavenObject.JGIT, BuildType.RUNTIME);
|
| | | downloadFromEclipse(MavenObject.JGIT_HTTP, BuildType.RUNTIME);
|
| | |
| | | downloadFromApache(MavenObject.LUCENE, BuildType.COMPILETIME);
|
| | | downloadFromApache(MavenObject.LUCENE_HIGHLIGHTER, BuildType.COMPILETIME);
|
| | | downloadFromApache(MavenObject.LUCENE_MEMORY, BuildType.COMPILETIME);
|
| | | downloadFromApache(MavenObject.UNBOUND_ID, BuildType.COMPILETIME);
|
| | |
|
| | | downloadFromEclipse(MavenObject.JGIT, BuildType.COMPILETIME);
|
| | | downloadFromEclipse(MavenObject.JGIT_HTTP, BuildType.COMPILETIME);
|
| | |
| | | "3.5.0", 30000, 23000, 0, "7908e954e8c1b4b2463aa712b34fa4a5612e241d",
|
| | | "69b19b38d78cc3b27ea5542a14f0ebbb1625ffdd", "");
|
| | |
|
| | | public static final MavenObject UNBOUND_ID = new MavenObject("unbound id", "com/unboundid", "unboundid-ldapsdk",
|
| | | "2.3.0", 1383417, 1439721, 0, "6fde8d9fb4ee3e7e3d7e764e3ea57195971e2eb2",
|
| | | "5276d3d29630693dba99ab9f7ea54f4c471d3af1", "");
|
| | |
|
| | |
|
| | | public final String name;
|
| | | public final String group;
|
| | |
| | | }
|
| | | return "";
|
| | | }
|
| | | |
| | | /**
|
| | | * Replace all occurences of a substring within a string with
|
| | | * another string.
|
| | | * |
| | | * From Spring StringUtils.
|
| | | * |
| | | * @param inString String to examine
|
| | | * @param oldPattern String to replace
|
| | | * @param newPattern String to insert
|
| | | * @return a String with the replacements
|
| | | */
|
| | | public static String replace(String inString, String oldPattern, String newPattern) {
|
| | | StringBuilder sb = new StringBuilder();
|
| | | int pos = 0; // our position in the old string
|
| | | int index = inString.indexOf(oldPattern);
|
| | | // the index of an occurrence we've found, or -1
|
| | | int patLen = oldPattern.length();
|
| | | while (index >= 0) {
|
| | | sb.append(inString.substring(pos, index));
|
| | | sb.append(newPattern);
|
| | | pos = index + patLen;
|
| | | index = inString.indexOf(oldPattern, pos);
|
| | | }
|
| | | sb.append(inString.substring(pos));
|
| | | // remember to append any characters to the right of a match
|
| | | return sb.toString();
|
| | | }
|
| | | } |
| | |
| | | }
|
| | | boolean rename = !StringUtils.isEmpty(oldName)
|
| | | && !oldName.equalsIgnoreCase(username);
|
| | | if (GitBlit.self().supportsCredentialChanges()) {
|
| | | if (!userModel.password.equals(confirmPassword.getObject())) {
|
| | | error(getString("gb.passwordsDoNotMatch"));
|
| | | return;
|
| | |
| | | error(getString("gb.combinedMd5Rename"));
|
| | | return;
|
| | | }
|
| | | }
|
| | |
|
| | | Iterator<String> selectedRepositories = repositories.getSelectedChoices();
|
| | | List<String> repos = new ArrayList<String>();
|
New file |
| | |
| | | /* |
| | | * Copyright 2012 John Crygier |
| | | * Copyright 2012 gitblit.com |
| | | * |
| | | * Licensed 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 com.gitblit.tests; |
| | | |
| | | import static org.junit.Assert.*; |
| | | |
| | | import java.util.HashMap; |
| | | import java.util.Map; |
| | | |
| | | import org.junit.Before; |
| | | import org.junit.Test; |
| | | |
| | | import com.gitblit.LdapUserService; |
| | | import com.gitblit.models.UserModel; |
| | | import com.gitblit.tests.mock.MemorySettings; |
| | | import com.unboundid.ldap.listener.InMemoryDirectoryServer; |
| | | import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig; |
| | | import com.unboundid.ldap.listener.InMemoryListenerConfig; |
| | | import com.unboundid.ldap.sdk.LDAPConnection; |
| | | import com.unboundid.ldif.LDIFReader; |
| | | |
| | | /** |
| | | * An Integration test for LDAP that tests going against an in-memory UnboundID |
| | | * LDAP server. |
| | | * |
| | | * @author jcrygier |
| | | * |
| | | */ |
| | | public class LdapUserServiceTest { |
| | | |
| | | private LdapUserService ldapUserService; |
| | | |
| | | @Before |
| | | public void createInMemoryLdapServer() throws Exception { |
| | | InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig("dc=MyDomain"); |
| | | config.addAdditionalBindCredentials("cn=Directory Manager", "password"); |
| | | config.setListenerConfigs(InMemoryListenerConfig.createLDAPConfig("default", 389)); |
| | | config.setSchema(null); |
| | | |
| | | InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config); |
| | | ds.importFromLDIF(true, new LDIFReader(this.getClass().getResourceAsStream("resources/ldapUserServiceSampleData.ldif"))); |
| | | ds.startListening(); |
| | | } |
| | | |
| | | @Before |
| | | public void createLdapUserService() { |
| | | Map<Object, Object> backingMap = new HashMap<Object, Object>(); |
| | | backingMap.put("realm.ldap.server", "ldap://localhost:389"); |
| | | backingMap.put("realm.ldap.domain", ""); |
| | | backingMap.put("realm.ldap.username", "cn=Directory Manager"); |
| | | backingMap.put("realm.ldap.password", "password"); |
| | | backingMap.put("realm.ldap.backingUserService", "users.conf"); |
| | | backingMap.put("realm.ldap.maintainTeams", "true"); |
| | | backingMap.put("realm.ldap.accountBase", "OU=Users,OU=UserControl,OU=MyOrganization,DC=MyDomain"); |
| | | backingMap.put("realm.ldap.accountPattern", "(&(objectClass=person)(sAMAccountName=${username}))"); |
| | | backingMap.put("realm.ldap.groupBase", "OU=Groups,OU=UserControl,OU=MyOrganization,DC=MyDomain"); |
| | | backingMap.put("realm.ldap.groupPattern", "(&(objectClass=group)(member=${dn}))"); |
| | | backingMap.put("realm.ldap.admins", "UserThree @Git_Admins"); |
| | | |
| | | MemorySettings ms = new MemorySettings(backingMap); |
| | | |
| | | ldapUserService = new LdapUserService(); |
| | | ldapUserService.setup(ms); |
| | | } |
| | | |
| | | @Test |
| | | public void testAuthenticate() { |
| | | UserModel userOneModel = ldapUserService.authenticate("UserOne", "userOnePassword".toCharArray()); |
| | | assertNotNull(userOneModel); |
| | | assertNotNull(userOneModel.getTeam("git_admins")); |
| | | assertNotNull(userOneModel.getTeam("git_users")); |
| | | assertTrue(userOneModel.canAdmin); |
| | | |
| | | UserModel userOneModelFailedAuth = ldapUserService.authenticate("UserOne", "userTwoPassword".toCharArray()); |
| | | assertNull(userOneModelFailedAuth); |
| | | |
| | | UserModel userTwoModel = ldapUserService.authenticate("UserTwo", "userTwoPassword".toCharArray()); |
| | | assertNotNull(userTwoModel); |
| | | assertNotNull(userTwoModel.getTeam("git_users")); |
| | | assertNull(userTwoModel.getTeam("git_admins")); |
| | | assertFalse(userTwoModel.canAdmin); |
| | | |
| | | UserModel userThreeModel = ldapUserService.authenticate("UserThree", "userThreePassword".toCharArray()); |
| | | assertNotNull(userThreeModel); |
| | | assertNotNull(userThreeModel.getTeam("git_users")); |
| | | assertNull(userThreeModel.getTeam("git_admins")); |
| | | assertTrue(userThreeModel.canAdmin); |
| | | } |
| | | |
| | | } |
New file |
| | |
| | | /* |
| | | * Copyright 2012 John Crygier |
| | | * Copyright 2012 gitblit.com |
| | | * |
| | | * Licensed 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 com.gitblit.tests.mock; |
| | | |
| | | import java.util.Map; |
| | | import java.util.Properties; |
| | | |
| | | import com.gitblit.IStoredSettings; |
| | | |
| | | public class MemorySettings extends IStoredSettings { |
| | | |
| | | private Map<Object, Object> backingMap; |
| | | |
| | | public MemorySettings(Map<Object, Object> backingMap) { |
| | | super(MemorySettings.class); |
| | | this.backingMap = backingMap; |
| | | } |
| | | |
| | | @Override |
| | | protected Properties read() { |
| | | Properties props = new Properties(); |
| | | props.putAll(backingMap); |
| | | |
| | | return props; |
| | | } |
| | | |
| | | public void put(Object key, Object value) { |
| | | backingMap.put(key, value); |
| | | } |
| | | |
| | | @Override |
| | | public boolean saveSettings(Map<String, String> updatedSettings) { |
| | | return false; |
| | | } |
| | | |
| | | } |
New file |
| | |
| | | dn: DC=MyDomain |
| | | dc: MyDomain |
| | | objectClass: top |
| | | objectClass: domain |
| | | |
| | | dn: OU=MyOrganization,DC=MyDomain |
| | | objectClass: top |
| | | objectClass: organizationalUnit |
| | | ou: MyOrganization |
| | | |
| | | dn: OU=UserControl,OU=MyOrganization,DC=MyDomain |
| | | objectClass: top |
| | | objectClass: organizationalUnit |
| | | ou: UserControl |
| | | |
| | | dn: OU=Groups,OU=UserControl,OU=MyOrganization,DC=MyDomain |
| | | objectClass: top |
| | | objectClass: organizationalUnit |
| | | ou: Groups |
| | | |
| | | dn: CN=Git_Admins,OU=Groups,OU=UserControl,OU=MyOrganization,DC=MyDomain |
| | | objectClass: top |
| | | objectClass: group |
| | | cn: Git_Admins |
| | | sAMAccountName: Git_Admins |
| | | member: CN=UserOne,OU=US,OU=Users,OU=UserControl,OU=MyOrganization,DC=MyDomain |
| | | |
| | | dn: CN=Git_Users,OU=Groups,OU=UserControl,OU=MyOrganization,DC=MyDomain |
| | | objectClass: top |
| | | objectClass: group |
| | | cn: Git_Users |
| | | sAMAccountName: Git_Users |
| | | member: CN=UserOne,OU=US,OU=Users,OU=UserControl,OU=MyOrganization,DC=MyDomain |
| | | member: CN=UserTwo,OU=US,OU=Users,OU=UserControl,OU=MyOrganization,DC=MyDomain |
| | | member: CN=UserThree,OU=Canada,OU=Users,OU=UserControl,OU=MyOrganization,DC=MyDomain |
| | | member: CN=UserFour,OU=Canada,OU=Users,OU=UserControl,OU=MyOrganization,DC=MyDomain |
| | | |
| | | dn: OU=Users,OU=UserControl,OU=MyOrganization,DC=MyDomain |
| | | objectClass: top |
| | | objectClass: organizationalUnit |
| | | ou: Users |
| | | |
| | | dn: OU=US,OU=Users,OU=UserControl,OU=MyOrganization,DC=MyDomain |
| | | objectClass: top |
| | | objectClass: organizationalUnit |
| | | ou: US |
| | | |
| | | dn: OU=Canada,OU=Users,OU=UserControl,OU=MyOrganization,DC=MyDomain |
| | | objectClass: top |
| | | objectClass: organizationalUnit |
| | | ou: Canada |
| | | |
| | | dn: CN=UserOne,OU=US,OU=Users,OU=UserControl,OU=MyOrganization,DC=MyDomain |
| | | objectClass: user |
| | | objectClass: person |
| | | sAMAccountName: UserOne |
| | | userPassword: userOnePassword |
| | | memberOf: CN=Git_Admins,OU=Groups,OU=UserControl,OU=MyOrganization,DC=MyDomain |
| | | memberOf: CN=Git_Users,OU=Groups,OU=UserControl,OU=MyOrganization,DC=MyDomain |
| | | |
| | | dn: CN=UserTwo,OU=US,OU=Users,OU=UserControl,OU=MyOrganization,DC=MyDomain |
| | | objectClass: user |
| | | objectClass: person |
| | | sAMAccountName: UserTwo |
| | | userPassword: userTwoPassword |
| | | memberOf: CN=Git_Users,OU=Groups,OU=UserControl,OU=MyOrganization,DC=MyDomain |
| | | |
| | | dn: CN=UserThree,OU=Canada,OU=Users,OU=UserControl,OU=MyOrganization,DC=MyDomain |
| | | objectClass: user |
| | | objectClass: person |
| | | sAMAccountName: UserThree |
| | | userPassword: userThreePassword |
| | | memberOf: CN=Git_Users,OU=Groups,OU=UserControl,OU=MyOrganization,DC=MyDomain |
| | | |
| | | dn: CN=UserFour,OU=Canada,OU=Users,OU=UserControl,OU=MyOrganization,DC=MyDomain |
| | | objectClass: user |
| | | objectClass: person |
| | | sAMAccountName: UserFour |
| | | userPassword: userFourPassword |
| | | memberOf: CN=Git_Users,OU=Groups,OU=UserControl,OU=MyOrganization,DC=MyDomain |