You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

LdapAuthProvider.java 20KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593
  1. /*
  2. * Copyright 2012 John Crygier
  3. * Copyright 2012 gitblit.com
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. package com.gitblit.auth;
  18. import java.net.URI;
  19. import java.net.URISyntaxException;
  20. import java.security.GeneralSecurityException;
  21. import java.text.MessageFormat;
  22. import java.util.Arrays;
  23. import java.util.HashMap;
  24. import java.util.List;
  25. import java.util.Map;
  26. import java.util.concurrent.Executors;
  27. import java.util.concurrent.ScheduledExecutorService;
  28. import java.util.concurrent.TimeUnit;
  29. import com.gitblit.Constants;
  30. import com.gitblit.Constants.AccountType;
  31. import com.gitblit.Keys;
  32. import com.gitblit.auth.AuthenticationProvider.UsernamePasswordAuthenticationProvider;
  33. import com.gitblit.models.TeamModel;
  34. import com.gitblit.models.UserModel;
  35. import com.gitblit.service.LdapSyncService;
  36. import com.gitblit.utils.ArrayUtils;
  37. import com.gitblit.utils.StringUtils;
  38. import com.unboundid.ldap.sdk.Attribute;
  39. import com.unboundid.ldap.sdk.DereferencePolicy;
  40. import com.unboundid.ldap.sdk.ExtendedResult;
  41. import com.unboundid.ldap.sdk.LDAPConnection;
  42. import com.unboundid.ldap.sdk.LDAPException;
  43. import com.unboundid.ldap.sdk.LDAPSearchException;
  44. import com.unboundid.ldap.sdk.ResultCode;
  45. import com.unboundid.ldap.sdk.SearchRequest;
  46. import com.unboundid.ldap.sdk.SearchResult;
  47. import com.unboundid.ldap.sdk.SearchResultEntry;
  48. import com.unboundid.ldap.sdk.SearchScope;
  49. import com.unboundid.ldap.sdk.SimpleBindRequest;
  50. import com.unboundid.ldap.sdk.extensions.StartTLSExtendedRequest;
  51. import com.unboundid.util.ssl.SSLUtil;
  52. import com.unboundid.util.ssl.TrustAllTrustManager;
  53. /**
  54. * Implementation of an LDAP user service.
  55. *
  56. * @author John Crygier
  57. */
  58. public class LdapAuthProvider extends UsernamePasswordAuthenticationProvider {
  59. private final ScheduledExecutorService scheduledExecutorService;
  60. public LdapAuthProvider() {
  61. super("ldap");
  62. scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
  63. }
  64. private long getSynchronizationPeriodInMilliseconds() {
  65. String period = settings.getString(Keys.realm.ldap.syncPeriod, null);
  66. if (StringUtils.isEmpty(period)) {
  67. period = settings.getString("realm.ldap.ldapCachePeriod", null);
  68. if (StringUtils.isEmpty(period)) {
  69. period = "5 MINUTES";
  70. } else {
  71. logger.warn("realm.ldap.ldapCachePeriod is obsolete!");
  72. logger.warn(MessageFormat.format("Please set {0}={1} in gitblit.properties!", Keys.realm.ldap.syncPeriod, period));
  73. settings.overrideSetting(Keys.realm.ldap.syncPeriod, period);
  74. }
  75. }
  76. try {
  77. final String[] s = period.split(" ", 2);
  78. long duration = Math.abs(Long.parseLong(s[0]));
  79. TimeUnit timeUnit = TimeUnit.valueOf(s[1]);
  80. return timeUnit.toMillis(duration);
  81. } catch (RuntimeException ex) {
  82. throw new IllegalArgumentException(Keys.realm.ldap.syncPeriod + " must have format '<long> <TimeUnit>' where <TimeUnit> is one of 'MILLISECONDS', 'SECONDS', 'MINUTES', 'HOURS', 'DAYS'");
  83. }
  84. }
  85. @Override
  86. public void setup() {
  87. configureSyncService();
  88. }
  89. @Override
  90. public void stop() {
  91. scheduledExecutorService.shutdownNow();
  92. }
  93. public synchronized void sync() {
  94. final boolean enabled = settings.getBoolean(Keys.realm.ldap.synchronize, false);
  95. if (enabled) {
  96. logger.info("Synchronizing with LDAP @ " + settings.getRequiredString(Keys.realm.ldap.server));
  97. final boolean deleteRemovedLdapUsers = settings.getBoolean(Keys.realm.ldap.removeDeletedUsers, true);
  98. LDAPConnection ldapConnection = getLdapConnection();
  99. if (ldapConnection != null) {
  100. try {
  101. String accountBase = settings.getString(Keys.realm.ldap.accountBase, "");
  102. String uidAttribute = settings.getString(Keys.realm.ldap.uid, "uid");
  103. String accountPattern = settings.getString(Keys.realm.ldap.accountPattern, "(&(objectClass=person)(sAMAccountName=${username}))");
  104. accountPattern = StringUtils.replace(accountPattern, "${username}", "*");
  105. SearchResult result = doSearch(ldapConnection, accountBase, accountPattern);
  106. if (result != null && result.getEntryCount() > 0) {
  107. final Map<String, UserModel> ldapUsers = new HashMap<String, UserModel>();
  108. for (SearchResultEntry loggingInUser : result.getSearchEntries()) {
  109. final String username = loggingInUser.getAttribute(uidAttribute).getValue();
  110. logger.debug("LDAP synchronizing: " + username);
  111. UserModel user = userManager.getUserModel(username);
  112. if (user == null) {
  113. user = new UserModel(username);
  114. }
  115. if (!supportsTeamMembershipChanges()) {
  116. getTeamsFromLdap(ldapConnection, username, loggingInUser, user);
  117. }
  118. // Get User Attributes
  119. setUserAttributes(user, loggingInUser);
  120. // store in map
  121. ldapUsers.put(username.toLowerCase(), user);
  122. }
  123. if (deleteRemovedLdapUsers) {
  124. logger.debug("detecting removed LDAP users...");
  125. for (UserModel userModel : userManager.getAllUsers()) {
  126. if (AccountType.LDAP == userModel.accountType) {
  127. if (!ldapUsers.containsKey(userModel.username)) {
  128. logger.info("deleting removed LDAP user " + userModel.username + " from user service");
  129. userManager.deleteUser(userModel.username);
  130. }
  131. }
  132. }
  133. }
  134. userManager.updateUserModels(ldapUsers.values());
  135. if (!supportsTeamMembershipChanges()) {
  136. final Map<String, TeamModel> userTeams = new HashMap<String, TeamModel>();
  137. for (UserModel user : ldapUsers.values()) {
  138. for (TeamModel userTeam : user.teams) {
  139. userTeams.put(userTeam.name, userTeam);
  140. }
  141. }
  142. userManager.updateTeamModels(userTeams.values());
  143. }
  144. }
  145. if (!supportsTeamMembershipChanges()) {
  146. getEmptyTeamsFromLdap(ldapConnection);
  147. }
  148. } finally {
  149. ldapConnection.close();
  150. }
  151. }
  152. }
  153. }
  154. private LDAPConnection getLdapConnection() {
  155. try {
  156. URI ldapUrl = new URI(settings.getRequiredString(Keys.realm.ldap.server));
  157. String ldapHost = ldapUrl.getHost();
  158. int ldapPort = ldapUrl.getPort();
  159. String bindUserName = settings.getString(Keys.realm.ldap.username, "");
  160. String bindPassword = settings.getString(Keys.realm.ldap.password, "");
  161. LDAPConnection conn;
  162. if (ldapUrl.getScheme().equalsIgnoreCase("ldaps")) {
  163. // SSL
  164. SSLUtil sslUtil = new SSLUtil(new TrustAllTrustManager());
  165. conn = new LDAPConnection(sslUtil.createSSLSocketFactory());
  166. if (ldapPort == -1) {
  167. ldapPort = 636;
  168. }
  169. } else if (ldapUrl.getScheme().equalsIgnoreCase("ldap") || ldapUrl.getScheme().equalsIgnoreCase("ldap+tls")) {
  170. // no encryption or StartTLS
  171. conn = new LDAPConnection();
  172. if (ldapPort == -1) {
  173. ldapPort = 389;
  174. }
  175. } else {
  176. logger.error("Unsupported LDAP URL scheme: " + ldapUrl.getScheme());
  177. return null;
  178. }
  179. conn.connect(ldapHost, ldapPort);
  180. if (ldapUrl.getScheme().equalsIgnoreCase("ldap+tls")) {
  181. SSLUtil sslUtil = new SSLUtil(new TrustAllTrustManager());
  182. ExtendedResult extendedResult = conn.processExtendedOperation(
  183. new StartTLSExtendedRequest(sslUtil.createSSLContext()));
  184. if (extendedResult.getResultCode() != ResultCode.SUCCESS) {
  185. throw new LDAPException(extendedResult.getResultCode());
  186. }
  187. }
  188. if (StringUtils.isEmpty(bindUserName) && StringUtils.isEmpty(bindPassword)) {
  189. // anonymous bind
  190. conn.bind(new SimpleBindRequest());
  191. } else {
  192. // authenticated bind
  193. conn.bind(new SimpleBindRequest(bindUserName, bindPassword));
  194. }
  195. return conn;
  196. } catch (URISyntaxException e) {
  197. logger.error("Bad LDAP URL, should be in the form: ldap(s|+tls)://<server>:<port>", e);
  198. } catch (GeneralSecurityException e) {
  199. logger.error("Unable to create SSL Connection", e);
  200. } catch (LDAPException e) {
  201. logger.error("Error Connecting to LDAP", e);
  202. }
  203. return null;
  204. }
  205. /**
  206. * Credentials are defined in the LDAP server and can not be manipulated
  207. * from Gitblit.
  208. *
  209. * @return false
  210. * @since 1.0.0
  211. */
  212. @Override
  213. public boolean supportsCredentialChanges() {
  214. return false;
  215. }
  216. /**
  217. * If no displayName pattern is defined then Gitblit can manage the display name.
  218. *
  219. * @return true if Gitblit can manage the user display name
  220. * @since 1.0.0
  221. */
  222. @Override
  223. public boolean supportsDisplayNameChanges() {
  224. return StringUtils.isEmpty(settings.getString(Keys.realm.ldap.displayName, ""));
  225. }
  226. /**
  227. * If no email pattern is defined then Gitblit can manage the email address.
  228. *
  229. * @return true if Gitblit can manage the user email address
  230. * @since 1.0.0
  231. */
  232. @Override
  233. public boolean supportsEmailAddressChanges() {
  234. return StringUtils.isEmpty(settings.getString(Keys.realm.ldap.email, ""));
  235. }
  236. /**
  237. * If the LDAP server will maintain team memberships then LdapUserService
  238. * will not allow team membership changes. In this scenario all team
  239. * changes must be made on the LDAP server by the LDAP administrator.
  240. *
  241. * @return true or false
  242. * @since 1.0.0
  243. */
  244. @Override
  245. public boolean supportsTeamMembershipChanges() {
  246. return !settings.getBoolean(Keys.realm.ldap.maintainTeams, false);
  247. }
  248. @Override
  249. public AccountType getAccountType() {
  250. return AccountType.LDAP;
  251. }
  252. @Override
  253. public UserModel authenticate(String username, char[] password) {
  254. String simpleUsername = getSimpleUsername(username);
  255. LDAPConnection ldapConnection = getLdapConnection();
  256. if (ldapConnection != null) {
  257. try {
  258. boolean alreadyAuthenticated = false;
  259. String bindPattern = settings.getString(Keys.realm.ldap.bindpattern, "");
  260. if (!StringUtils.isEmpty(bindPattern)) {
  261. try {
  262. String bindUser = StringUtils.replace(bindPattern, "${username}", simpleUsername);
  263. ldapConnection.bind(bindUser, new String(password));
  264. alreadyAuthenticated = true;
  265. } catch (LDAPException e) {
  266. return null;
  267. }
  268. }
  269. // Find the logging in user's DN
  270. String accountBase = settings.getString(Keys.realm.ldap.accountBase, "");
  271. String accountPattern = settings.getString(Keys.realm.ldap.accountPattern, "(&(objectClass=person)(sAMAccountName=${username}))");
  272. accountPattern = StringUtils.replace(accountPattern, "${username}", escapeLDAPSearchFilter(simpleUsername));
  273. SearchResult result = doSearch(ldapConnection, accountBase, accountPattern);
  274. if (result != null && result.getEntryCount() == 1) {
  275. SearchResultEntry loggingInUser = result.getSearchEntries().get(0);
  276. String loggingInUserDN = loggingInUser.getDN();
  277. if (alreadyAuthenticated || isAuthenticated(ldapConnection, loggingInUserDN, new String(password))) {
  278. logger.debug("LDAP authenticated: " + username);
  279. UserModel user = null;
  280. synchronized (this) {
  281. user = userManager.getUserModel(simpleUsername);
  282. if (user == null) {
  283. // create user object for new authenticated user
  284. user = new UserModel(simpleUsername);
  285. }
  286. // create a user cookie
  287. setCookie(user, password);
  288. if (!supportsTeamMembershipChanges()) {
  289. getTeamsFromLdap(ldapConnection, simpleUsername, loggingInUser, user);
  290. }
  291. // Get User Attributes
  292. setUserAttributes(user, loggingInUser);
  293. // Push the ldap looked up values to backing file
  294. updateUser(user);
  295. if (!supportsTeamMembershipChanges()) {
  296. for (TeamModel userTeam : user.teams) {
  297. updateTeam(userTeam);
  298. }
  299. }
  300. }
  301. return user;
  302. }
  303. }
  304. } finally {
  305. ldapConnection.close();
  306. }
  307. }
  308. return null;
  309. }
  310. /**
  311. * Set the admin attribute from team memberships retrieved from LDAP.
  312. * If we are not storing teams in LDAP and/or we have not defined any
  313. * administrator teams, then do not change the admin flag.
  314. *
  315. * @param user
  316. */
  317. private void setAdminAttribute(UserModel user) {
  318. if (!supportsTeamMembershipChanges()) {
  319. List<String> admins = settings.getStrings(Keys.realm.ldap.admins);
  320. // if we have defined administrative teams, then set admin flag
  321. // otherwise leave admin flag unchanged
  322. if (!ArrayUtils.isEmpty(admins)) {
  323. user.canAdmin = false;
  324. for (String admin : admins) {
  325. if (admin.startsWith("@") && user.isTeamMember(admin.substring(1))) {
  326. // admin team
  327. user.canAdmin = true;
  328. } else if (user.getName().equalsIgnoreCase(admin)) {
  329. // admin user
  330. user.canAdmin = true;
  331. }
  332. }
  333. }
  334. }
  335. }
  336. private void setUserAttributes(UserModel user, SearchResultEntry userEntry) {
  337. // Is this user an admin?
  338. setAdminAttribute(user);
  339. // Don't want visibility into the real password, make up a dummy
  340. user.password = Constants.EXTERNAL_ACCOUNT;
  341. user.accountType = getAccountType();
  342. // Get full name Attribute
  343. String displayName = settings.getString(Keys.realm.ldap.displayName, "");
  344. if (!StringUtils.isEmpty(displayName)) {
  345. // Replace embedded ${} with attributes
  346. if (displayName.contains("${")) {
  347. for (Attribute userAttribute : userEntry.getAttributes()) {
  348. displayName = StringUtils.replace(displayName, "${" + userAttribute.getName() + "}", userAttribute.getValue());
  349. }
  350. user.displayName = displayName;
  351. } else {
  352. Attribute attribute = userEntry.getAttribute(displayName);
  353. if (attribute != null && attribute.hasValue()) {
  354. user.displayName = attribute.getValue();
  355. }
  356. }
  357. }
  358. // Get email address Attribute
  359. String email = settings.getString(Keys.realm.ldap.email, "");
  360. if (!StringUtils.isEmpty(email)) {
  361. if (email.contains("${")) {
  362. for (Attribute userAttribute : userEntry.getAttributes()) {
  363. email = StringUtils.replace(email, "${" + userAttribute.getName() + "}", userAttribute.getValue());
  364. }
  365. user.emailAddress = email;
  366. } else {
  367. Attribute attribute = userEntry.getAttribute(email);
  368. if (attribute != null && attribute.hasValue()) {
  369. user.emailAddress = attribute.getValue();
  370. }
  371. }
  372. }
  373. }
  374. private void getTeamsFromLdap(LDAPConnection ldapConnection, String simpleUsername, SearchResultEntry loggingInUser, UserModel user) {
  375. String loggingInUserDN = loggingInUser.getDN();
  376. // Clear the users team memberships - we're going to get them from LDAP
  377. user.teams.clear();
  378. String groupBase = settings.getString(Keys.realm.ldap.groupBase, "");
  379. String groupMemberPattern = settings.getString(Keys.realm.ldap.groupMemberPattern, "(&(objectClass=group)(member=${dn}))");
  380. groupMemberPattern = StringUtils.replace(groupMemberPattern, "${dn}", escapeLDAPSearchFilter(loggingInUserDN));
  381. groupMemberPattern = StringUtils.replace(groupMemberPattern, "${username}", escapeLDAPSearchFilter(simpleUsername));
  382. // Fill in attributes into groupMemberPattern
  383. for (Attribute userAttribute : loggingInUser.getAttributes()) {
  384. groupMemberPattern = StringUtils.replace(groupMemberPattern, "${" + userAttribute.getName() + "}", escapeLDAPSearchFilter(userAttribute.getValue()));
  385. }
  386. SearchResult teamMembershipResult = doSearch(ldapConnection, groupBase, true, groupMemberPattern, Arrays.asList("cn"));
  387. if (teamMembershipResult != null && teamMembershipResult.getEntryCount() > 0) {
  388. for (int i = 0; i < teamMembershipResult.getEntryCount(); i++) {
  389. SearchResultEntry teamEntry = teamMembershipResult.getSearchEntries().get(i);
  390. String teamName = teamEntry.getAttribute("cn").getValue();
  391. TeamModel teamModel = userManager.getTeamModel(teamName);
  392. if (teamModel == null) {
  393. teamModel = createTeamFromLdap(teamEntry);
  394. }
  395. user.teams.add(teamModel);
  396. teamModel.addUser(user.getName());
  397. }
  398. }
  399. }
  400. private void getEmptyTeamsFromLdap(LDAPConnection ldapConnection) {
  401. logger.info("Start fetching empty teams from ldap.");
  402. String groupBase = settings.getString(Keys.realm.ldap.groupBase, "");
  403. String groupMemberPattern = settings.getString(Keys.realm.ldap.groupEmptyMemberPattern, "(&(objectClass=group)(!(member=*)))");
  404. SearchResult teamMembershipResult = doSearch(ldapConnection, groupBase, true, groupMemberPattern, null);
  405. if (teamMembershipResult != null && teamMembershipResult.getEntryCount() > 0) {
  406. for (int i = 0; i < teamMembershipResult.getEntryCount(); i++) {
  407. SearchResultEntry teamEntry = teamMembershipResult.getSearchEntries().get(i);
  408. if (!teamEntry.hasAttribute("member")) {
  409. String teamName = teamEntry.getAttribute("cn").getValue();
  410. TeamModel teamModel = userManager.getTeamModel(teamName);
  411. if (teamModel == null) {
  412. teamModel = createTeamFromLdap(teamEntry);
  413. userManager.updateTeamModel(teamModel);
  414. }
  415. }
  416. }
  417. }
  418. logger.info("Finished fetching empty teams from ldap.");
  419. }
  420. private TeamModel createTeamFromLdap(SearchResultEntry teamEntry) {
  421. TeamModel answer = new TeamModel(teamEntry.getAttributeValue("cn"));
  422. answer.accountType = getAccountType();
  423. // potentially retrieve other attributes here in the future
  424. return answer;
  425. }
  426. private SearchResult doSearch(LDAPConnection ldapConnection, String base, String filter) {
  427. try {
  428. return ldapConnection.search(base, SearchScope.SUB, filter);
  429. } catch (LDAPSearchException e) {
  430. logger.error("Problem Searching LDAP", e);
  431. return null;
  432. }
  433. }
  434. private SearchResult doSearch(LDAPConnection ldapConnection, String base, boolean dereferenceAliases, String filter, List<String> attributes) {
  435. try {
  436. SearchRequest searchRequest = new SearchRequest(base, SearchScope.SUB, filter);
  437. if (dereferenceAliases) {
  438. searchRequest.setDerefPolicy(DereferencePolicy.SEARCHING);
  439. }
  440. if (attributes != null) {
  441. searchRequest.setAttributes(attributes);
  442. }
  443. return ldapConnection.search(searchRequest);
  444. } catch (LDAPSearchException e) {
  445. logger.error("Problem Searching LDAP", e);
  446. return null;
  447. } catch (LDAPException e) {
  448. logger.error("Problem creating LDAP search", e);
  449. return null;
  450. }
  451. }
  452. private boolean isAuthenticated(LDAPConnection ldapConnection, String userDn, String password) {
  453. try {
  454. // Binding will stop any LDAP-Injection Attacks since the searched-for user needs to bind to that DN
  455. ldapConnection.bind(userDn, password);
  456. return true;
  457. } catch (LDAPException e) {
  458. logger.error("Error authenticating user", e);
  459. return false;
  460. }
  461. }
  462. /**
  463. * Returns a simple username without any domain prefixes.
  464. *
  465. * @param username
  466. * @return a simple username
  467. */
  468. protected String getSimpleUsername(String username) {
  469. int lastSlash = username.lastIndexOf('\\');
  470. if (lastSlash > -1) {
  471. username = username.substring(lastSlash + 1);
  472. }
  473. return username;
  474. }
  475. // From: https://www.owasp.org/index.php/Preventing_LDAP_Injection_in_Java
  476. public static final String escapeLDAPSearchFilter(String filter) {
  477. StringBuilder sb = new StringBuilder();
  478. for (int i = 0; i < filter.length(); i++) {
  479. char curChar = filter.charAt(i);
  480. switch (curChar) {
  481. case '\\':
  482. sb.append("\\5c");
  483. break;
  484. case '*':
  485. sb.append("\\2a");
  486. break;
  487. case '(':
  488. sb.append("\\28");
  489. break;
  490. case ')':
  491. sb.append("\\29");
  492. break;
  493. case '\u0000':
  494. sb.append("\\00");
  495. break;
  496. default:
  497. sb.append(curChar);
  498. }
  499. }
  500. return sb.toString();
  501. }
  502. private void configureSyncService() {
  503. LdapSyncService ldapSyncService = new LdapSyncService(settings, this);
  504. if (ldapSyncService.isReady()) {
  505. long ldapSyncPeriod = getSynchronizationPeriodInMilliseconds();
  506. int delay = 1;
  507. logger.info("Ldap sync service will update users and groups every {} minutes.", ldapSyncPeriod);
  508. scheduledExecutorService.scheduleAtFixedRate(ldapSyncService, delay, ldapSyncPeriod, TimeUnit.MILLISECONDS);
  509. } else {
  510. logger.info("Ldap sync service is disabled.");
  511. }
  512. }
  513. }