James Moger
2014-02-19 6659fa5151ebd5fb744b7b07f929e580ce6f5843
API adjustments and elimination of duplicate config options
7 files modified
169 ■■■■■ changed files
releases.moxie 5 ●●●●● patch | view | raw | blame | history
src/main/distrib/data/gitblit.properties 64 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/auth/AuthenticationProvider.java 12 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/auth/LdapAuthProvider.java 62 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/manager/AuthenticationManager.java 7 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/service/LdapSyncService.java 11 ●●●● patch | view | raw | blame | history
src/test/java/com/gitblit/tests/LdapAuthenticationTest.java 8 ●●●● patch | view | raw | blame | history
releases.moxie
@@ -33,6 +33,7 @@
    - Reversed line links in blob view (issue-309)
    - Dashboard and Activity pages now obey the web.generateActivityGraph setting (issue-310)
    - Do not log passwords on failed authentication attempts (issue-316)
    - LDAP synchronization is now scheduled rather than on-demand (issue-336)
    - Show displayname and username in palettes (issue-364)
    - Updated default binary and Lucene ignore extensions
    - Change the WAR baseFolder context parameter to a JNDI env-entry to improve enterprise deployments
@@ -82,6 +83,10 @@
    - { name: 'git.defaultAccessRestriction', defaultValue: 'PUSH' }
    - { name: 'git.mirrorPeriod', defaultValue: '30 mins' }
    - { name: 'realm.authenticationProviders', defaultValue: ' ' }
    - { name: 'realm.ldap.groupEmptyMemberPattern', defaultValue: '(&(objectClass=group)(!(member=*)))' }
    - { name: 'realm.ldap.synchronize', defaultValue: 'false' }
    - { name: 'realm.ldap.syncPeriod', defaultValue: '5 MINUTES' }
    - { name: 'realm.ldap.removeDeletedUsers', defaultValue: 'true' }
    - { name: 'web.commitMessageRenderer', defaultValue: 'plain' }
    - { name: 'web.documents', defaultValue: 'readme home index changelog contributing submitting_patches copying license notice authors' }
    - { name: 'web.showBranchGraph', defaultValue: 'true' }
src/main/distrib/data/gitblit.properties
@@ -1502,46 +1502,44 @@
# SINCE 1.0.0
realm.ldap.email = email
# Defines the cache period to be used when caching LDAP queries. This is currently
# only used for LDAP user synchronization.
#
# Must be of the form '<long> <TimeUnit>' where <TimeUnit> is one of 'MILLISECONDS', 'SECONDS', 'MINUTES', 'HOURS', 'DAYS'
# default: 2 MINUTES
#
# RESTART REQUIRED
realm.ldap.ldapCachePeriod = 2 MINUTES
# Defines whether to synchronize all LDAP users into the backing user service
#
# Valid values: true, false
# If left blank, false is assumed
realm.ldap.synchronizeUsers.enable = false
# Defines the period to be used when synchronizing users from ldap. This is currently
# only used for LDAP user synchronization.
#
# Must be of the form '<long> <TimeUnit>' where <TimeUnit> is one of 'MILLISECONDS', 'SECONDS', 'MINUTES', 'HOURS', 'DAYS'
# <long> is at least the value from realm.ldap.ldapCachePeriod if lower the value from realm.ldap.ldapCachePeriod is used.
# default: 5 MINUTES
#
# RESTART REQUIRED
# SINCE 1.4.0
realm.ldap.synchronizeUsers.ldapSyncPeriod = 5 MINUTES
# Defines whether to delete non-existent LDAP users from the backing user service
# during synchronization. depends on  realm.ldap.synchronizeUsers.enable = true
#
# Valid values: true, false
# If left blank, true is assumed
realm.ldap.synchronizeUsers.removeDeleted = true
# Attribute on the USER record that indicate their username to be used in gitblit
# when synchronizing users from LDAP
# if blank, Gitblit will use uid
# For MS Active Directory this may be sAMAccountName
#
# SINCE 1.0.0
realm.ldap.uid = uid
# Defines whether to synchronize all LDAP users and teams into the user service
#
# Valid values: true, false
# If left blank, false is assumed
#
# SINCE 1.4.0
realm.ldap.synchronize = false
# Defines the period to be used when synchronizing users and teams from ldap.
#
# Must be of the form '<long> <TimeUnit>' where <TimeUnit> is one of 'MILLISECONDS', 'SECONDS', 'MINUTES', 'HOURS', 'DAYS'
# default: 5 MINUTES
#
# RESTART REQUIRED
# SINCE 1.4.0
realm.ldap.syncPeriod = 5 MINUTES
# Defines whether to delete non-existent LDAP users from the user service
# during synchronization. depends on  realm.ldap.synchronize = true
#
# Valid values: true, false
# If left blank, true is assumed
#
# SINCE 1.4.0
realm.ldap.removeDeletedUsers = true
# URL of the Redmine.
#
# SINCE 1.2.0
realm.redmine.url = http://example.com/redmine
#
src/main/java/com/gitblit/auth/AuthenticationProvider.java
@@ -100,6 +100,8 @@
    public abstract void setup();
    public abstract void stop();
    public abstract UserModel authenticate(String username, char[] password);
    public abstract AccountType getAccountType();
@@ -145,6 +147,11 @@
        protected UsernamePasswordAuthenticationProvider(String serviceName) {
            super(serviceName);
        }
        @Override
        public void stop() {
        }
    }
    public static class NullProvider extends AuthenticationProvider {
@@ -159,6 +166,11 @@
        }
        @Override
        public void stop() {
        }
        @Override
        public UserModel authenticate(String username, char[] password) {
            return null;
        }
src/main/java/com/gitblit/auth/LdapAuthProvider.java
@@ -19,6 +19,7 @@
import java.net.URI;
import java.net.URISyntaxException;
import java.security.GeneralSecurityException;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
@@ -26,7 +27,6 @@
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import com.gitblit.Constants;
import com.gitblit.Constants.AccountType;
@@ -60,40 +60,52 @@
 */
public class LdapAuthProvider extends UsernamePasswordAuthenticationProvider {
    private final AtomicLong lastLdapUserSync = new AtomicLong(0L);
    private final ScheduledExecutorService scheduledExecutorService;
    public LdapAuthProvider() {
        super("ldap");
        scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
    }
     private long getSynchronizationPeriodInMilliseconds(String name) {
        final String cacheDuration = settings.getString(name, "2 MINUTES");
     private long getSynchronizationPeriodInMilliseconds() {
         String period = settings.getString(Keys.realm.ldap.syncPeriod, null);
         if (StringUtils.isEmpty(period)) {
              period = settings.getString("realm.ldap.ldapCachePeriod", null);
              if (StringUtils.isEmpty(period)) {
                  period = "5 MINUTES";
              } else {
                  logger.warn("realm.ldap.ldapCachePeriod is obsolete!");
                  logger.warn(MessageFormat.format("Please set {0}={1} in gitblit.properties!", Keys.realm.ldap.syncPeriod, period));
                  settings.overrideSetting(Keys.realm.ldap.syncPeriod, period);
              }
         }
        try {
            final String[] s = cacheDuration.split(" ", 2);
            final String[] s = period.split(" ", 2);
            long duration = Math.abs(Long.parseLong(s[0]));
            TimeUnit timeUnit = TimeUnit.valueOf(s[1]);
            return timeUnit.toMillis(duration);
        } catch (RuntimeException ex) {
            throw new IllegalArgumentException(name + " must have format '<long> <TimeUnit>' where <TimeUnit> is one of 'MILLISECONDS', 'SECONDS', 'MINUTES', 'HOURS', 'DAYS'");
            throw new IllegalArgumentException(Keys.realm.ldap.syncPeriod + " must have format '<long> <TimeUnit>' where <TimeUnit> is one of 'MILLISECONDS', 'SECONDS', 'MINUTES', 'HOURS', 'DAYS'");
        }
    }
    @Override
    public void setup() {
        synchronizeLdapUsers();
        configureLdapSyncService();
        configureSyncService();
    }
    
    public void synchronizeWithLdapService() {
        synchronizeLdapUsers();
    @Override
    public void stop() {
        scheduledExecutorService.shutdownNow();
    }
    protected synchronized void synchronizeLdapUsers() {
        final boolean enabled = settings.getBoolean(Keys.realm.ldap.synchronizeUsers.enable, false);
    public synchronized void sync() {
        final boolean enabled = settings.getBoolean(Keys.realm.ldap.synchronize, false);
        if (enabled) {
            if (System.currentTimeMillis() > (lastLdapUserSync.get() + getSynchronizationPeriodInMilliseconds(Keys.realm.ldap.ldapCachePeriod))) {
                logger.info("Synchronizing with LDAP @ " + settings.getRequiredString(Keys.realm.ldap.server));
                final boolean deleteRemovedLdapUsers = settings.getBoolean(Keys.realm.ldap.synchronizeUsers.removeDeleted, true);
            final boolean deleteRemovedLdapUsers = settings.getBoolean(Keys.realm.ldap.removeDeletedUsers, true);
                LDAPConnection ldapConnection = getLdapConnection();
                if (ldapConnection != null) {
                    try {
@@ -131,7 +143,7 @@
                                logger.debug("detecting removed LDAP users...");
                                for (UserModel userModel : userManager.getAllUsers()) {
                                    if (Constants.EXTERNAL_ACCOUNT.equals(userModel.password)) {
                                if (AccountType.LDAP == userModel.accountType) {
                                        if (!ldapUsers.containsKey(userModel.username)) {
                                            logger.info("deleting removed LDAP user " + userModel.username + " from user service");
                                            userManager.deleteUser(userModel.username);
@@ -155,10 +167,8 @@
                        if (!supportsTeamMembershipChanges()) {
                            getEmptyTeamsFromLdap(ldapConnection);
                        }
                        lastLdapUserSync.set(System.currentTimeMillis());
                    } finally {
                        ldapConnection.close();
                    }
                }
            }
        }
@@ -439,7 +449,7 @@
    }
    private void getEmptyTeamsFromLdap(LDAPConnection ldapConnection) {
        logger.info("Start fetching empty teams form ldap.");
        logger.info("Start fetching empty teams from ldap.");
        String groupBase = settings.getString(Keys.realm.ldap.groupBase, "");
        String groupMemberPattern = settings.getString(Keys.realm.ldap.groupEmptyMemberPattern, "(&(objectClass=group)(!(member=*)))");
@@ -458,7 +468,7 @@
                }
            }
        }
        logger.info("Finished fetching empty teams form ldap.");
        logger.info("Finished fetching empty teams from ldap.");
    }
    private TeamModel createTeamFromLdap(SearchResultEntry teamEntry) {
@@ -554,24 +564,16 @@
        return sb.toString();
    }
    private void configureLdapSyncService() {
        logger.info("Start configuring ldap sync service");
    private void configureSyncService() {
        LdapSyncService ldapSyncService = new LdapSyncService(settings, this);
        if (ldapSyncService.isReady()) {
            long ldapSyncPeriod = getSynchronizationPeriodInMilliseconds(Keys.realm.ldap.synchronizeUsers.ldapSyncPeriod);
            long ldapCachePeriod = getSynchronizationPeriodInMilliseconds(Keys.realm.ldap.synchronizeUsers.ldapSyncPeriod);
            if (ldapSyncPeriod < ldapCachePeriod) {
                ldapSyncPeriod = ldapCachePeriod;
            }
            long ldapSyncPeriod = getSynchronizationPeriodInMilliseconds();
            int delay = 1;
            ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
            logger.info("Ldap sync service will update users and groups every {} minutes.", ldapSyncPeriod);
            scheduledExecutorService.scheduleAtFixedRate(ldapSyncService, delay, ldapSyncPeriod,  TimeUnit.MILLISECONDS);
            logger.info("Ldap sync service will update user and groups every {} minutes.", ldapSyncPeriod);
            logger.info("Next scheduled ldap sync is in {} minutes", delay);
        } else {
            logger.info("Ldap sync service is disabled.");
        }
        logger.info("Finished configuring ldap sync service");
    }
}
src/main/java/com/gitblit/manager/AuthenticationManager.java
@@ -150,6 +150,13 @@
    @Override
    public AuthenticationManager stop() {
        for (AuthenticationProvider provider : authenticationProviders) {
            try {
                provider.stop();
            } catch (Exception e) {
                logger.error("Failed to stop " + provider.getClass().getSimpleName(), e);
            }
        }
        return this;
    }
src/main/java/com/gitblit/service/LdapSyncService.java
@@ -1,5 +1,5 @@
/*
 * Copyright 2013 gitblit.com.
 * Copyright 2014 gitblit.com.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
@@ -51,14 +51,19 @@
    public void run() {
        logger.info("Starting user and group sync with ldap service");
        if (!running.getAndSet(true)) {
            ldapAuthProvider.synchronizeWithLdapService();
            try {
                ldapAuthProvider.sync();
            } catch (Exception e) {
                logger.error("Failed to synchronize with ldap", e);
            } finally {
            running.getAndSet(false);
            }
        }
        logger.info("Finished user and group sync with ldap service");
    }
    public boolean isReady() {
        return settings.getBoolean(Keys.realm.ldap.synchronizeUsers.enable, false);
        return settings.getBoolean(Keys.realm.ldap.synchronize, false);
    }
}
src/test/java/com/gitblit/tests/LdapAuthenticationTest.java
@@ -196,7 +196,7 @@
    public void addingUserInLdapShouldNotUpdateGitBlitUsersAndGroups() throws Exception {
        settings.put("realm.ldap.ldapCachePeriod", "0 MINUTES");
        ds.addEntries(LDIFReader.readEntries(RESOURCE_DIR + "adduser.ldif"));
        ldap.synchronizeWithLdapService();
        ldap.sync();
        assertEquals("Number of ldap users in gitblit user model", 5, countLdapUsersInUserManager());
    }
@@ -205,7 +205,7 @@
        settings.put("realm.ldap.synchronizeUsers.enable", "true");
        settings.put("realm.ldap.ldapCachePeriod", "0 MINUTES");
        ds.addEntries(LDIFReader.readEntries(RESOURCE_DIR + "adduser.ldif"));
        ldap.synchronizeWithLdapService();
        ldap.sync();
        assertEquals("Number of ldap users in gitblit user model", 6, countLdapUsersInUserManager());
    }
@@ -213,7 +213,7 @@
    public void addingGroupsInLdapShouldNotUpdateGitBlitUsersAndGroups() throws Exception {
        settings.put("realm.ldap.ldapCachePeriod", "0 MINUTES");
        ds.addEntries(LDIFReader.readEntries(RESOURCE_DIR + "addgroup.ldif"));
        ldap.synchronizeWithLdapService();
        ldap.sync();
        assertEquals("Number of ldap groups in gitblit team model", 0, countLdapTeamsInUserManager());
    }
@@ -222,7 +222,7 @@
        settings.put("realm.ldap.synchronizeUsers.enable", "true");
        settings.put("realm.ldap.ldapCachePeriod", "0 MINUTES");
        ds.addEntries(LDIFReader.readEntries(RESOURCE_DIR + "addgroup.ldif"));
        ldap.synchronizeWithLdapService();
        ldap.sync();
        assertEquals("Number of ldap groups in gitblit team model", 1, countLdapTeamsInUserManager());
    }