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.

LdapUserService.java 9.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  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;
  18. import java.io.File;
  19. import java.net.URI;
  20. import java.net.URISyntaxException;
  21. import java.security.GeneralSecurityException;
  22. import org.slf4j.Logger;
  23. import org.slf4j.LoggerFactory;
  24. import com.gitblit.models.TeamModel;
  25. import com.gitblit.models.UserModel;
  26. import com.gitblit.utils.StringUtils;
  27. import com.unboundid.ldap.sdk.Attribute;
  28. import com.unboundid.ldap.sdk.LDAPConnection;
  29. import com.unboundid.ldap.sdk.LDAPException;
  30. import com.unboundid.ldap.sdk.LDAPSearchException;
  31. import com.unboundid.ldap.sdk.SearchResult;
  32. import com.unboundid.ldap.sdk.SearchResultEntry;
  33. import com.unboundid.ldap.sdk.SearchScope;
  34. import com.unboundid.util.ssl.SSLUtil;
  35. import com.unboundid.util.ssl.TrustAllTrustManager;
  36. /**
  37. * Implementation of an LDAP user service.
  38. *
  39. * @author John Crygier
  40. */
  41. public class LdapUserService extends GitblitUserService {
  42. public static final Logger logger = LoggerFactory.getLogger(LdapUserService.class);
  43. private IStoredSettings settings;
  44. public LdapUserService() {
  45. super();
  46. }
  47. @Override
  48. public void setup(IStoredSettings settings) {
  49. this.settings = settings;
  50. String file = settings.getString(Keys.realm.ldap_backingUserService, "users.conf");
  51. File realmFile = GitBlit.getFileOrFolder(file);
  52. serviceImpl = createUserService(realmFile);
  53. logger.info("LDAP User Service backed by " + serviceImpl.toString());
  54. }
  55. private LDAPConnection getLdapConnection() {
  56. try {
  57. URI ldapUrl = new URI(settings.getRequiredString(Keys.realm.ldap_server));
  58. String bindUserName = settings.getString(Keys.realm.ldap_username, "");
  59. String bindPassword = settings.getString(Keys.realm.ldap_password, "");
  60. int ldapPort = ldapUrl.getPort();
  61. if (ldapUrl.getScheme().equalsIgnoreCase("ldaps")) { // SSL
  62. if (ldapPort == -1) // Default Port
  63. ldapPort = 636;
  64. SSLUtil sslUtil = new SSLUtil(new TrustAllTrustManager());
  65. return new LDAPConnection(sslUtil.createSSLSocketFactory(), ldapUrl.getHost(), ldapPort, bindUserName, bindPassword);
  66. } else {
  67. if (ldapPort == -1) // Default Port
  68. ldapPort = 389;
  69. return new LDAPConnection(ldapUrl.getHost(), ldapPort, bindUserName, bindPassword);
  70. }
  71. } catch (URISyntaxException e) {
  72. logger.error("Bad LDAP URL, should be in the form: ldap(s)://<server>:<port>", e);
  73. } catch (GeneralSecurityException e) {
  74. logger.error("Unable to create SSL Connection", e);
  75. } catch (LDAPException e) {
  76. logger.error("Error Connecting to LDAP", e);
  77. }
  78. return null;
  79. }
  80. /**
  81. * Credentials are defined in the LDAP server and can not be manipulated
  82. * from Gitblit.
  83. *
  84. * @return false
  85. * @since 1.0.0
  86. */
  87. @Override
  88. public boolean supportsCredentialChanges() {
  89. return false;
  90. }
  91. /**
  92. * If the LDAP server will maintain team memberships then LdapUserService
  93. * will not allow team membership changes. In this scenario all team
  94. * changes must be made on the LDAP server by the LDAP administrator.
  95. *
  96. * @return true or false
  97. * @since 1.0.0
  98. */
  99. public boolean supportsTeamMembershipChanges() {
  100. return !settings.getBoolean(Keys.realm.ldap_maintainTeams, false);
  101. }
  102. /**
  103. * Does the user service support cookie authentication?
  104. *
  105. * @return true or false
  106. */
  107. @Override
  108. public boolean supportsCookies() {
  109. // TODO cookies need to be reviewed
  110. return false;
  111. }
  112. @Override
  113. public UserModel authenticate(String username, char[] password) {
  114. String simpleUsername = getSimpleUsername(username);
  115. LDAPConnection ldapConnection = getLdapConnection();
  116. if (ldapConnection != null) {
  117. // Find the logging in user's DN
  118. String accountBase = settings.getString(Keys.realm.ldap_accountBase, "");
  119. String accountPattern = settings.getString(Keys.realm.ldap_accountPattern, "(&(objectClass=person)(sAMAccountName=${username}))");
  120. accountPattern = StringUtils.replace(accountPattern, "${username}", simpleUsername);
  121. SearchResult result = doSearch(ldapConnection, accountBase, accountPattern);
  122. if (result != null && result.getEntryCount() == 1) {
  123. SearchResultEntry loggingInUser = result.getSearchEntries().get(0);
  124. String loggingInUserDN = loggingInUser.getDN();
  125. if (isAuthenticated(ldapConnection, loggingInUserDN, new String(password))) {
  126. logger.debug("Authenitcated: " + username);
  127. UserModel user = getUserModel(simpleUsername);
  128. if (user == null) // create user object for new authenticated user
  129. user = createUserFromLdap(loggingInUser);
  130. user.password = "StoredInLDAP";
  131. if (!supportsTeamMembershipChanges())
  132. getTeamsFromLdap(ldapConnection, simpleUsername, loggingInUser, user);
  133. // Get Admin Attributes
  134. setAdminAttribute(user);
  135. // Push the ldap looked up values to backing file
  136. super.updateUserModel(user);
  137. if (!supportsTeamMembershipChanges()) {
  138. for (TeamModel userTeam : user.teams)
  139. updateTeamModel(userTeam);
  140. }
  141. return user;
  142. }
  143. }
  144. }
  145. return null;
  146. }
  147. private void setAdminAttribute(UserModel user) {
  148. String adminString = settings.getString(Keys.realm.ldap_admins, "");
  149. String[] admins = adminString.split(" ");
  150. user.canAdmin = false;
  151. for (String admin : admins) {
  152. if (admin.startsWith("@")) { // Team
  153. if (user.getTeam(admin.substring(1)) != null)
  154. user.canAdmin = true;
  155. } else
  156. if (user.getName().equalsIgnoreCase(admin))
  157. user.canAdmin = true;
  158. }
  159. }
  160. private void getTeamsFromLdap(LDAPConnection ldapConnection, String simpleUsername, SearchResultEntry loggingInUser, UserModel user) {
  161. String loggingInUserDN = loggingInUser.getDN();
  162. user.teams.clear(); // Clear the users team memberships - we're going to get them from LDAP
  163. String groupBase = settings.getString(Keys.realm.ldap_groupBase, "");
  164. String groupMemberPattern = settings.getString(Keys.realm.ldap_groupMemberPattern, "(&(objectClass=group)(member=${dn}))");
  165. groupMemberPattern = StringUtils.replace(groupMemberPattern, "${dn}", loggingInUserDN);
  166. groupMemberPattern = StringUtils.replace(groupMemberPattern, "${username}", simpleUsername);
  167. // Fill in attributes into groupMemberPattern
  168. for (Attribute userAttribute : loggingInUser.getAttributes())
  169. groupMemberPattern = StringUtils.replace(groupMemberPattern, "${" + userAttribute.getName() + "}", userAttribute.getValue());
  170. SearchResult teamMembershipResult = doSearch(ldapConnection, groupBase, groupMemberPattern);
  171. if (teamMembershipResult != null && teamMembershipResult.getEntryCount() > 0) {
  172. for (int i = 0; i < teamMembershipResult.getEntryCount(); i++) {
  173. SearchResultEntry teamEntry = teamMembershipResult.getSearchEntries().get(i);
  174. String teamName = teamEntry.getAttribute("cn").getValue();
  175. TeamModel teamModel = getTeamModel(teamName);
  176. if (teamModel == null)
  177. teamModel = createTeamFromLdap(teamEntry);
  178. user.teams.add(teamModel);
  179. teamModel.addUser(user.getName());
  180. }
  181. }
  182. }
  183. private TeamModel createTeamFromLdap(SearchResultEntry teamEntry) {
  184. TeamModel answer = new TeamModel(teamEntry.getAttributeValue("cn"));
  185. // If attributes other than team name ever from from LDAP, this is where to get them
  186. return answer;
  187. }
  188. private UserModel createUserFromLdap(SearchResultEntry userEntry) {
  189. UserModel answer = new UserModel(userEntry.getAttributeValue("cn"));
  190. //If attributes other than user name ever from from LDAP, this is where to get them
  191. return answer;
  192. }
  193. private SearchResult doSearch(LDAPConnection ldapConnection, String base, String filter) {
  194. try {
  195. return ldapConnection.search(base, SearchScope.SUB, filter);
  196. } catch (LDAPSearchException e) {
  197. logger.error("Problem Searching LDAP", e);
  198. return null;
  199. }
  200. }
  201. private boolean isAuthenticated(LDAPConnection ldapConnection, String userDn, String password) {
  202. try {
  203. ldapConnection.bind(userDn, password);
  204. return true;
  205. } catch (LDAPException e) {
  206. logger.error("Error authenitcating user", e);
  207. return false;
  208. }
  209. }
  210. /**
  211. * Returns a simple username without any domain prefixes.
  212. *
  213. * @param username
  214. * @return a simple username
  215. */
  216. protected String getSimpleUsername(String username) {
  217. int lastSlash = username.lastIndexOf('\\');
  218. if (lastSlash > -1) {
  219. username = username.substring(lastSlash + 1);
  220. }
  221. return username;
  222. }
  223. }