001package org.apache.archiva.redback.users.ldap.ctl; 002 003/* 004 * Licensed to the Apache Software Foundation (ASF) under one 005 * or more contributor license agreements. See the NOTICE file 006 * distributed with this work for additional information 007 * regarding copyright ownership. The ASF licenses this file 008 * to you under the Apache License, Version 2.0 (the 009 * "License"); you may not use this file except in compliance 010 * with the License. You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, 015 * software distributed under the License is distributed on an 016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 017 * KIND, either express or implied. See the License for the 018 * specific language governing permissions and limitations 019 * under the License. 020 */ 021 022import org.apache.archiva.redback.common.ldap.MappingException; 023import org.apache.archiva.redback.common.ldap.user.LdapUser; 024import org.apache.archiva.redback.common.ldap.user.LdapUserMapper; 025import org.apache.archiva.redback.common.ldap.user.UserMapper; 026import org.apache.archiva.redback.configuration.UserConfiguration; 027import org.apache.archiva.redback.configuration.UserConfigurationKeys; 028import org.apache.archiva.redback.policy.PasswordEncoder; 029import org.apache.archiva.redback.policy.encoders.SHA1PasswordEncoder; 030import org.apache.archiva.redback.users.User; 031import org.apache.archiva.redback.users.UserManager; 032import org.apache.archiva.redback.users.ldap.LdapUserQuery; 033import org.apache.commons.lang3.StringUtils; 034import org.slf4j.Logger; 035import org.slf4j.LoggerFactory; 036import org.springframework.stereotype.Service; 037 038import javax.annotation.PostConstruct; 039import javax.inject.Inject; 040import javax.inject.Named; 041import javax.naming.NamingEnumeration; 042import javax.naming.NamingException; 043import javax.naming.directory.Attribute; 044import javax.naming.directory.Attributes; 045import javax.naming.directory.BasicAttribute; 046import javax.naming.directory.BasicAttributes; 047import javax.naming.directory.DirContext; 048import javax.naming.directory.SearchControls; 049import javax.naming.directory.SearchResult; 050import java.util.Collection; 051import java.util.HashMap; 052import java.util.HashSet; 053import java.util.LinkedHashSet; 054import java.util.LinkedList; 055import java.util.List; 056import java.util.Map; 057import java.util.Set; 058 059/** 060 * @author jesse 061 */ 062@Service 063public class DefaultLdapController 064 implements LdapController 065{ 066 067 private Logger log = LoggerFactory.getLogger( getClass() ); 068 069 @Inject 070 @Named(value = "userMapper#ldap") 071 private UserMapper mapper; 072 073 @Inject 074 @Named( value = "userConfiguration#default" ) 075 private UserConfiguration userConf; 076 077 private boolean writableLdap = false; 078 079 private PasswordEncoder passwordEncoder; 080 081 private String baseDn; 082 083 private String groupsDn; 084 085 private String ldapGroupClass = "groupOfUniqueNames"; 086 087 @PostConstruct 088 public void initialize() 089 { 090 this.writableLdap = userConf.getBoolean( UserConfigurationKeys.LDAP_WRITABLE, this.writableLdap ); 091 this.baseDn = userConf.getConcatenatedList( UserConfigurationKeys.LDAP_BASEDN, null ); 092 this.passwordEncoder = new SHA1PasswordEncoder(); 093 this.groupsDn = userConf.getConcatenatedList( UserConfigurationKeys.LDAP_GROUPS_BASEDN, this.groupsDn ); 094 this.ldapGroupClass = userConf.getString( UserConfigurationKeys.LDAP_GROUPS_CLASS, this.ldapGroupClass ); 095 } 096 097 /** 098 * @see org.apache.archiva.redback.users.ldap.ctl.LdapController#removeUser(String, javax.naming.directory.DirContext) 099 */ 100 public void removeUser( String principal, DirContext context ) 101 throws LdapControllerException 102 { 103 // no op 104 } 105 106 /** 107 * @see org.apache.archiva.redback.users.ldap.ctl.LdapController#updateUser(org.apache.archiva.redback.users.User, javax.naming.directory.DirContext) 108 */ 109 public void updateUser( User user, DirContext context ) 110 throws LdapControllerException, MappingException 111 { 112 // no op 113 } 114 115 /** 116 * @see org.apache.archiva.redback.users.ldap.ctl.LdapController#userExists(String, javax.naming.directory.DirContext) 117 */ 118 public boolean userExists( String key, DirContext context ) 119 throws LdapControllerException 120 { 121 NamingEnumeration<SearchResult> results = null; 122 try 123 { 124 results = searchUsers( key, context ); 125 return results.hasMoreElements(); 126 } 127 catch ( NamingException e ) 128 { 129 throw new LdapControllerException( "Error searching for the existence of user: " + key, e ); 130 } 131 finally 132 { 133 if ( results != null ) 134 { 135 try 136 { 137 results.close(); 138 } 139 catch ( NamingException e ) 140 { 141 log.warn( "Error closing search results", e ); 142 } 143 } 144 } 145 } 146 147 protected NamingEnumeration<SearchResult> searchUsers( String key, DirContext context ) 148 throws NamingException 149 { 150 LdapUserQuery query = new LdapUserQuery(); 151 query.setUsername( key ); 152 return searchUsers( context, null, query ); 153 } 154 155 protected NamingEnumeration<SearchResult> searchUsers( DirContext context ) 156 throws NamingException 157 { 158 return searchUsers( context, null, null ); 159 } 160 161 protected NamingEnumeration<SearchResult> searchUsers( DirContext context, String[] returnAttributes ) 162 throws NamingException 163 { 164 return searchUsers( context, returnAttributes, null ); 165 } 166 167 protected NamingEnumeration<SearchResult> searchUsers( DirContext context, String[] returnAttributes, 168 LdapUserQuery query ) 169 throws NamingException 170 { 171 if ( query == null ) 172 { 173 query = new LdapUserQuery(); 174 } 175 SearchControls ctls = new SearchControls(); 176 177 ctls.setDerefLinkFlag( true ); 178 ctls.setSearchScope( SearchControls.SUBTREE_SCOPE ); 179 ctls.setReturningAttributes( mapper.getReturningAttributes() ); 180 ctls.setCountLimit( ( (LdapUserMapper) mapper ).getMaxResultCount() ); 181 182 String finalFilter = new StringBuilder( "(&(objectClass=" + mapper.getUserObjectClass() + ")" ).append( 183 ( mapper.getUserFilter() != null ? mapper.getUserFilter() : "" ) ).append( 184 query.getLdapFilter( mapper ) + ")" ).toString(); 185 186 log.debug( "Searching for users with filter: '{}' from base dn: {}", finalFilter, mapper.getUserBaseDn() ); 187 188 return context.search( mapper.getUserBaseDn(), finalFilter, ctls ); 189 } 190 191 /** 192 * @see org.apache.archiva.redback.users.ldap.ctl.LdapController#getUsers(javax.naming.directory.DirContext) 193 */ 194 public Collection<User> getUsers( DirContext context ) 195 throws LdapControllerException, MappingException 196 { 197 NamingEnumeration<SearchResult> results = null; 198 try 199 { 200 results = searchUsers( context, null, null ); 201 Set<User> users = new LinkedHashSet<User>(); 202 203 while ( results.hasMoreElements() ) 204 { 205 SearchResult result = results.nextElement(); 206 207 users.add( mapper.getUser( result.getNameInNamespace(), result.getAttributes() ) ); 208 } 209 210 return users; 211 } 212 catch ( NamingException e ) 213 { 214 String message = "Failed to retrieve ldap information for users."; 215 216 throw new LdapControllerException( message, e ); 217 } 218 finally 219 { 220 if ( results != null ) 221 { 222 try 223 { 224 results.close(); 225 } 226 catch ( NamingException e ) 227 { 228 log.warn( "failed to close search results", e ); 229 } 230 } 231 } 232 } 233 234 /** 235 * @see org.apache.archiva.redback.users.ldap.ctl.LdapController#getUsersByQuery(org.apache.archiva.redback.users.ldap.LdapUserQuery, javax.naming.directory.DirContext) 236 */ 237 public List<User> getUsersByQuery( LdapUserQuery query, DirContext context ) 238 throws LdapControllerException, MappingException 239 { 240 NamingEnumeration<SearchResult> results = null; 241 try 242 { 243 results = searchUsers( context, null, query ); 244 List<User> users = new LinkedList<User>(); 245 246 while ( results.hasMoreElements() ) 247 { 248 SearchResult result = results.nextElement(); 249 250 users.add( mapper.getUser( result.getNameInNamespace(), result.getAttributes() ) ); 251 } 252 253 return users; 254 } 255 catch ( NamingException e ) 256 { 257 String message = "Failed to retrieve ldap information for users."; 258 259 throw new LdapControllerException( message, e ); 260 } 261 finally 262 { 263 if ( results != null ) 264 { 265 try 266 { 267 results.close(); 268 } 269 catch ( NamingException e ) 270 { 271 log.warn( "failed to close search results", e ); 272 } 273 } 274 } 275 } 276 277 /** 278 * @see org.apache.archiva.redback.users.ldap.ctl.LdapController#createUser(org.apache.archiva.redback.users.User, javax.naming.directory.DirContext, boolean) 279 */ 280 public void createUser( User user, DirContext context, boolean encodePasswordIfChanged ) 281 throws LdapControllerException, MappingException 282 { 283 if ( user == null ) 284 { 285 return; 286 } 287 if ( user.getUsername().equals( UserManager.GUEST_USERNAME ) ) 288 { 289 log.warn( "skip user '{}' creation" ); 290 //We don't store guest 291 return; 292 } 293 boolean userExists = userExists( user.getUsername(), context ); 294 if ( userExists ) 295 { 296 log.debug( "user '{}' exists skip creation", user.getUsername() ); 297 return; 298 } 299 if ( writableLdap ) 300 { 301 try 302 { 303 bindUserObject( context, user ); 304 log.info( "user {} created in ldap", user.getUsername() ); 305 } 306 catch ( NamingException e ) 307 { 308 throw new LdapControllerException( e.getMessage(), e ); 309 } 310 } 311 } 312 313 314 private void bindUserObject( DirContext context, User user ) 315 throws NamingException 316 { 317 Attributes attributes = new BasicAttributes( true ); 318 BasicAttribute objectClass = new BasicAttribute( "objectClass" ); 319 objectClass.add( "top" ); 320 objectClass.add( "inetOrgPerson" ); 321 objectClass.add( "person" ); 322 objectClass.add( "organizationalperson" ); 323 attributes.put( objectClass ); 324 attributes.put( "cn", user.getUsername() ); 325 attributes.put( "sn", "foo" ); 326 if ( StringUtils.isNotEmpty( user.getEmail() ) ) 327 { 328 attributes.put( "mail", user.getEmail() ); 329 } 330 331 if ( userConf.getBoolean( UserConfigurationKeys.LDAP_BIND_AUTHENTICATOR_ALLOW_EMPTY_PASSWORDS, false ) 332 && StringUtils.isNotEmpty( user.getPassword() ) ) 333 { 334 attributes.put( "userPassword", passwordEncoder.encodePassword( user.getPassword() ) ); 335 } 336 attributes.put( "givenName", "foo" ); 337 context.createSubcontext( "cn=" + user.getUsername() + "," + this.getBaseDn(), attributes ); 338 } 339 340 /** 341 * @see org.apache.archiva.redback.users.ldap.ctl.LdapController#getUser(String, javax.naming.directory.DirContext) 342 */ 343 public LdapUser getUser( String username, DirContext context ) 344 throws LdapControllerException, MappingException 345 { 346 347 log.debug( "Searching for user: {}", username ); 348 349 LdapUserQuery query = new LdapUserQuery(); 350 query.setUsername( username ); 351 352 NamingEnumeration<SearchResult> result = null; 353 try 354 { 355 result = searchUsers( context, null, query ); 356 357 if ( result.hasMoreElements() ) 358 { 359 SearchResult next = result.nextElement(); 360 361 log.info( "Found user: {}", username ); 362 363 return mapper.getUser( next.getNameInNamespace(), next.getAttributes() ); 364 } 365 else 366 { 367 return null; 368 } 369 } 370 catch ( NamingException e ) 371 { 372 String message = "Failed to retrieve information for user: " + username; 373 374 throw new LdapControllerException( message, e ); 375 } 376 finally 377 { 378 if ( result != null ) 379 { 380 try 381 { 382 result.close(); 383 } 384 catch ( NamingException e ) 385 { 386 log.warn( "failed to close search results", e ); 387 } 388 } 389 } 390 } 391 392 public Map<String, Collection<String>> findUsersWithRoles( DirContext dirContext ) 393 throws LdapControllerException 394 { 395 Map<String, Collection<String>> usersWithRoles = new HashMap<String, Collection<String>>(); 396 397 NamingEnumeration<SearchResult> namingEnumeration = null; 398 try 399 { 400 401 SearchControls searchControls = new SearchControls(); 402 403 searchControls.setDerefLinkFlag( true ); 404 searchControls.setSearchScope( SearchControls.SUBTREE_SCOPE ); 405 406 String filter = "objectClass=" + getLdapGroupClass(); 407 408 namingEnumeration = dirContext.search( getGroupsDn(), filter, searchControls ); 409 410 while ( namingEnumeration.hasMore() ) 411 { 412 SearchResult searchResult = namingEnumeration.next(); 413 414 String groupName = searchResult.getName(); 415 // cn=blabla we only want bla bla 416 groupName = StringUtils.substringAfter( groupName, "=" ); 417 418 Attribute uniqueMemberAttr = searchResult.getAttributes().get( "uniquemember" ); 419 420 if ( uniqueMemberAttr != null ) 421 { 422 NamingEnumeration<?> allMembersEnum = uniqueMemberAttr.getAll(); 423 while ( allMembersEnum.hasMore() ) 424 { 425 String userName = allMembersEnum.next().toString(); 426 // uid=blabla we only want bla bla 427 userName = StringUtils.substringAfter( userName, "=" ); 428 userName = StringUtils.substringBefore( userName, "," ); 429 Collection<String> roles = usersWithRoles.get( userName ); 430 if ( roles == null ) 431 { 432 roles = new HashSet<String>(); 433 } 434 435 roles.add( groupName ); 436 437 usersWithRoles.put( userName, roles ); 438 439 } 440 } 441 442 log.debug( "found groupName: '{}' with users: {}", groupName ); 443 444 } 445 446 return usersWithRoles; 447 } 448 catch ( NamingException e ) 449 { 450 throw new LdapControllerException( e.getMessage(), e ); 451 } 452 453 finally 454 { 455 456 if ( namingEnumeration != null ) 457 { 458 try 459 { 460 namingEnumeration.close(); 461 } 462 catch ( NamingException e ) 463 { 464 log.warn( "failed to close search results", e ); 465 } 466 } 467 } 468 } 469 470 //----------------------------- 471 // setters/getters 472 //----------------------------- 473 public UserMapper getMapper() 474 { 475 return mapper; 476 } 477 478 public void setMapper( UserMapper mapper ) 479 { 480 this.mapper = mapper; 481 } 482 483 public UserConfiguration getUserConf() 484 { 485 return userConf; 486 } 487 488 public void setUserConf( UserConfiguration userConf ) 489 { 490 this.userConf = userConf; 491 } 492 493 public boolean isWritableLdap() 494 { 495 return writableLdap; 496 } 497 498 public void setWritableLdap( boolean writableLdap ) 499 { 500 this.writableLdap = writableLdap; 501 } 502 503 public PasswordEncoder getPasswordEncoder() 504 { 505 return passwordEncoder; 506 } 507 508 public void setPasswordEncoder( PasswordEncoder passwordEncoder ) 509 { 510 this.passwordEncoder = passwordEncoder; 511 } 512 513 public String getBaseDn() 514 { 515 return baseDn; 516 } 517 518 public void setBaseDn( String baseDn ) 519 { 520 this.baseDn = baseDn; 521 } 522 523 public String getGroupsDn() 524 { 525 return groupsDn; 526 } 527 528 public void setGroupsDn( String groupsDn ) 529 { 530 this.groupsDn = groupsDn; 531 } 532 533 public String getLdapGroupClass() 534 { 535 return ldapGroupClass; 536 } 537 538 public void setLdapGroupClass( String ldapGroupClass ) 539 { 540 this.ldapGroupClass = ldapGroupClass; 541 } 542}