001 //$HeadURL: svn+ssh://mschneider@svn.wald.intevation.org/deegree/base/trunk/src/org/deegree/tools/security/ActiveDirectoryImporter.java $ 002 /*---------------------------------------------------------------------------- 003 This file is part of deegree, http://deegree.org/ 004 Copyright (C) 2001-2009 by: 005 Department of Geography, University of Bonn 006 and 007 lat/lon GmbH 008 009 This library is free software; you can redistribute it and/or modify it under 010 the terms of the GNU Lesser General Public License as published by the Free 011 Software Foundation; either version 2.1 of the License, or (at your option) 012 any later version. 013 This library is distributed in the hope that it will be useful, but WITHOUT 014 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 015 FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 016 details. 017 You should have received a copy of the GNU Lesser General Public License 018 along with this library; if not, write to the Free Software Foundation, Inc., 019 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 020 021 Contact information: 022 023 lat/lon GmbH 024 Aennchenstr. 19, 53177 Bonn 025 Germany 026 http://lat-lon.de/ 027 028 Department of Geography, University of Bonn 029 Prof. Dr. Klaus Greve 030 Postfach 1147, 53001 Bonn 031 Germany 032 http://www.geographie.uni-bonn.de/deegree/ 033 034 e-mail: info@deegree.org 035 ----------------------------------------------------------------------------*/ 036 package org.deegree.tools.security; 037 038 import java.io.FileInputStream; 039 import java.io.IOException; 040 import java.io.PrintWriter; 041 import java.io.StringWriter; 042 import java.util.HashMap; 043 import java.util.Hashtable; 044 import java.util.Properties; 045 046 import javax.naming.Context; 047 import javax.naming.NamingEnumeration; 048 import javax.naming.NamingException; 049 import javax.naming.directory.Attribute; 050 import javax.naming.directory.Attributes; 051 import javax.naming.directory.SearchControls; 052 import javax.naming.directory.SearchResult; 053 import javax.naming.ldap.Control; 054 import javax.naming.ldap.InitialLdapContext; 055 import javax.naming.ldap.LdapContext; 056 import javax.naming.ldap.PagedResultsControl; 057 import javax.naming.ldap.PagedResultsResponseControl; 058 059 import org.deegree.framework.mail.EMailMessage; 060 import org.deegree.framework.mail.MailHelper; 061 import org.deegree.framework.mail.SendMailException; 062 import org.deegree.security.GeneralSecurityException; 063 import org.deegree.security.UnauthorizedException; 064 import org.deegree.security.drm.SecurityAccess; 065 import org.deegree.security.drm.SecurityAccessManager; 066 import org.deegree.security.drm.SecurityTransaction; 067 import org.deegree.security.drm.UnknownException; 068 import org.deegree.security.drm.model.Group; 069 import org.deegree.security.drm.model.User; 070 071 /** 072 * This class provides the functionality to synchronize the <code>User</code> instances stored in a 073 * <code>SecurityManager</code> with a generic LDAP-Server (such as OpenLDAP). 074 * <p> 075 * Changes are committed after all steps succeeded. If an error occurs, changes in the <code>SecurityManager</code> are 076 * undone. 077 * <p> 078 * 079 * @author <a href="mailto:schneider@lat-lon.de">Markus Schneider</a> 080 * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a> 081 * @author last edited by: $Author: apoth $ 082 * 083 * @version $Revision: 10660 $, $Date: 2008-03-24 21:39:54 +0000 (Mo, 24 Mrz 2008) $ 084 */ 085 public class LDAPImporter { 086 087 private SecurityAccessManager manager; 088 089 private SecurityAccess access; 090 091 private SecurityTransaction trans; 092 093 private User admin; 094 095 private Hashtable<String, String> env; 096 097 private LdapContext ctx; 098 099 private Properties config; 100 101 // mail configuration 102 private static String mailSender; 103 104 private static String mailRcpt; 105 106 private static String mailHost; 107 108 private static boolean mailLog; 109 110 // number of results to fetch in one batch 111 private int pageSize = 500; 112 113 private StringBuffer logBuffer = new StringBuffer( 1000 ); 114 115 /** 116 * Constructs a new <code>ADExporter</code> -instance. 117 * <p> 118 * 119 * @param config 120 * @throws NamingException 121 * @throws GeneralSecurityException 122 */ 123 LDAPImporter( Properties config ) throws NamingException, GeneralSecurityException { 124 125 this.config = config; 126 127 // retrieve mail configuration first 128 mailSender = getPropertySafe( "mailSender" ); 129 mailRcpt = getPropertySafe( "mailRcpt" ); 130 mailHost = getPropertySafe( "mailHost" ); 131 mailLog = getPropertySafe( "mailLog" ).equals( "true" ) || getPropertySafe( "mailLog" ).equals( "yes" ) ? true 132 : false; 133 134 // get a SecurityManager (with an SQLRegistry) 135 Properties registryProperties = new Properties(); 136 registryProperties.put( "driver", getPropertySafe( "sqlDriver" ) ); 137 registryProperties.put( "url", getPropertySafe( "sqlLogon" ) ); 138 registryProperties.put( "user", getPropertySafe( "sqlUser" ) ); 139 registryProperties.put( "password", getPropertySafe( "sqlPass" ) ); 140 141 // default timeout: 20 min 142 long timeout = 1200000; 143 try { 144 timeout = Long.parseLong( getPropertySafe( "timeout" ) ); 145 } catch ( NumberFormatException e ) { 146 logBuffer.append( "Specified property value for timeout invalid. " + "Defaulting to 1200 (secs)." ); 147 } 148 149 if ( !SecurityAccessManager.isInitialized() ) { 150 SecurityAccessManager.initialize( "org.deegree.security.drm.SQLRegistry", registryProperties, timeout ); 151 } 152 153 manager = SecurityAccessManager.getInstance(); 154 admin = manager.getUserByName( getPropertySafe( "u3rAdminName" ) ); 155 admin.authenticate( getPropertySafe( "u3rAdminPassword" ) ); 156 157 // prepare LDAP connection 158 String jndiURL = "ldap://" + getPropertySafe( "ldapHost" ) + ":389/"; 159 String initialContextFactory = "com.sun.jndi.ldap.LdapCtxFactory"; 160 String authenticationMode = "simple"; 161 String contextReferral = "ignore"; 162 env = new Hashtable<String, String>(); 163 env.put( Context.INITIAL_CONTEXT_FACTORY, initialContextFactory ); 164 env.put( Context.PROVIDER_URL, jndiURL ); 165 env.put( Context.SECURITY_AUTHENTICATION, authenticationMode ); 166 env.put( Context.SECURITY_PRINCIPAL, getPropertySafe( "ldapUser" ) ); 167 env.put( Context.SECURITY_CREDENTIALS, getPropertySafe( "ldapPass" ) ); 168 env.put( Context.REFERRAL, contextReferral ); 169 170 access = manager.acquireAccess( admin ); 171 trans = manager.acquireTransaction( admin ); 172 ctx = new InitialLdapContext( env, null ); 173 174 } 175 176 /** 177 * Returns a configuration property. If it is not defined, an exception is thrown. 178 * <p> 179 * 180 * @param name 181 * @return a configuration property. If it is not defined, an exception is thrown. 182 */ 183 private String getPropertySafe( String name ) { 184 185 String value = config.getProperty( name ); 186 if ( value == null ) { 187 throw new RuntimeException( "Configuration does not define needed property '" + name + "'." ); 188 } 189 return value; 190 } 191 192 /** 193 * Synchronizes the LDAP's user objects with the SecurityManager's user objects. 194 * <p> 195 * 196 * @throws NamingException 197 * @throws IOException 198 * @throws GeneralSecurityException 199 * @throws UnauthorizedException 200 * 201 */ 202 void synchronizeUsers() 203 throws NamingException, IOException, UnauthorizedException, GeneralSecurityException { 204 205 // keys are names (Strings), values are User-objects 206 HashMap<String, User> userMap = new HashMap<String, User>( 20 ); 207 208 byte[] cookie = null; 209 210 // specify the ids of the attributes to return 211 String[] attrIDs = { getPropertySafe( "userName" ), getPropertySafe( "userTitle" ), 212 getPropertySafe( "userFirstName" ), getPropertySafe( "userLastName" ), 213 getPropertySafe( "userMail" ) }; 214 215 SearchControls ctls = new SearchControls(); 216 ctls.setReturningAttributes( attrIDs ); 217 ctls.setSearchScope( SearchControls.SUBTREE_SCOPE ); 218 219 // specify the search filter to match 220 String filter = getPropertySafe( "userFilter" ); 221 String context = getPropertySafe( "userContext" ); 222 223 // create initial PagedResultsControl 224 ctx.setRequestControls( new Control[] { new PagedResultsControl( pageSize, false ) } ); 225 226 // phase 1: make sure that all users from LDAP are present in the 227 // SecurityManager 228 // (register them to the SecurityManager if necessary) 229 do { 230 // perform the search 231 NamingEnumeration<?> answer = ctx.search( context, filter, ctls ); 232 233 // process results of the last batch 234 while ( answer.hasMoreElements() ) { 235 SearchResult result = (SearchResult) answer.nextElement(); 236 237 Attributes atts = result.getAttributes(); 238 Attribute nameAtt = atts.get( getPropertySafe( "userName" ) ); 239 Attribute firstNameAtt = atts.get( getPropertySafe( "userFirstName" ) ); 240 Attribute lastNameAtt = atts.get( getPropertySafe( "userLastName" ) ); 241 Attribute mailAtt = atts.get( getPropertySafe( "userMail" ) ); 242 243 String name = (String) nameAtt.get(); 244 String firstName = firstNameAtt != null ? (String) firstNameAtt.get() : "" + ""; 245 String lastName = lastNameAtt != null ? (String) lastNameAtt.get() : "" + ""; 246 String mail = mailAtt != null ? (String) mailAtt.get() : ""; 247 248 // check if user is already registered 249 User user = null; 250 try { 251 user = access.getUserByName( name ); 252 } catch ( UnknownException e ) { 253 // no -> register user 254 logBuffer.append( "Registering user: " + name + "\n" ); 255 user = trans.registerUser( name, null, lastName, firstName, mail ); 256 } 257 userMap.put( name, user ); 258 } 259 260 // examine the paged results control response 261 Control[] controls = ctx.getResponseControls(); 262 if ( controls != null ) { 263 for ( int i = 0; i < controls.length; i++ ) { 264 if ( controls[i] instanceof PagedResultsResponseControl ) { 265 PagedResultsResponseControl prrc = (PagedResultsResponseControl) controls[i]; 266 // total = prrc.getResultSize(); 267 cookie = prrc.getCookie(); 268 } 269 } 270 } 271 272 if ( cookie != null ) { 273 // re-activate paged results 274 ctx.setRequestControls( new Control[] { new PagedResultsControl( pageSize, cookie, Control.CRITICAL ) } ); 275 } 276 } while ( cookie != null ); 277 278 // phase 2: make sure that all users from the SecurityManager are known 279 // to the LDAP (deregister them from the SecurityManager if necessary) 280 User[] sMUsers = access.getAllUsers(); 281 for ( int i = 0; i < sMUsers.length; i++ ) { 282 if ( userMap.get( sMUsers[i].getName() ) == null && sMUsers[i].getID() != User.ID_SEC_ADMIN ) { 283 logBuffer.append( "Deregistering user: " + sMUsers[i].getName() + "\n" ); 284 trans.deregisterUser( sMUsers[i] ); 285 } 286 } 287 } 288 289 /** 290 * Updates the special group "SEC_ALL" (contains all users). 291 * <p> 292 * 293 * @throws GeneralSecurityException 294 */ 295 void updateSecAll() 296 throws GeneralSecurityException { 297 Group secAll = null; 298 299 // phase1: make sure that group "SEC_ALL" exists 300 // (register it if necessary) 301 try { 302 secAll = access.getGroupByName( "SEC_ALL" ); 303 } catch ( UnknownException e ) { 304 secAll = trans.registerGroup( "SEC_ALL", "SEC_ALL" ); 305 } 306 307 // phase2: set all users to be members of this group 308 User[] allUsers = access.getAllUsers(); 309 trans.setUsersInGroup( secAll, allUsers ); 310 } 311 312 /** 313 * Aborts the synchronization process and undoes all changes. 314 */ 315 public void abortChanges() { 316 if ( manager != null && trans != null ) { 317 try { 318 manager.abortTransaction( trans ); 319 } catch ( GeneralSecurityException e ) { 320 e.printStackTrace(); 321 } 322 } 323 if ( ctx != null ) { 324 try { 325 ctx.close(); 326 } catch ( NamingException e ) { 327 e.printStackTrace(); 328 } 329 } 330 } 331 332 /** 333 * Ends the synchronization process and commits all changes. 334 */ 335 public void commitChanges() { 336 if ( manager != null && trans != null ) { 337 try { 338 manager.commitTransaction( trans ); 339 } catch ( GeneralSecurityException e ) { 340 e.printStackTrace(); 341 } 342 } 343 if ( ctx != null ) { 344 try { 345 ctx.close(); 346 } catch ( NamingException e ) { 347 e.printStackTrace(); 348 } 349 } 350 } 351 352 /** 353 * Sends an eMail to inform the admin that something went wrong. 354 * <p> 355 * NOTE: This is static, because it must be usable even when the construction of the ADExporter failed. 356 * <p> 357 * 358 * @param e 359 */ 360 public static void sendError( Exception e ) { 361 362 try { 363 String mailMessage = "Beim Synchronisieren der LDAP-Datenbank mit der deegree-" 364 + "Sicherheitsdatenbank ist ein Fehler aufgetreten.\n" 365 + "Die Synchronisierung wurde NICHT durchgeführt, der letzte " 366 + "Stand wurde wiederhergestellt.\n"; 367 StringWriter sw = new StringWriter(); 368 PrintWriter writer = new PrintWriter( sw ); 369 e.printStackTrace( writer ); 370 mailMessage += "\n\nDie Java-Fehlermeldung lautet:\n" + sw.getBuffer(); 371 372 mailMessage += "\n\nMit freundlichem Gruss,\nIhr LDAPImporter"; 373 MailHelper.createAndSendMail( 374 new EMailMessage( mailSender, mailRcpt, "Fehler im LDAPImporter", mailMessage ), 375 mailHost ); 376 } catch ( SendMailException ex ) { 377 ex.printStackTrace(); 378 } 379 380 } 381 382 /** 383 * Sends an eMail with a log of the transaction. 384 * <p> 385 */ 386 public void sendLog() { 387 388 try { 389 String mailMessage = "Die Synchronisierung der deegree Sicherheitsdatenbank mit " 390 + "der LDAP-Datenbank wurde erfolgreich durchgeführt:\n\n"; 391 if ( logBuffer.length() == 0 ) { 392 mailMessage += "Keine Änderungen."; 393 } else { 394 mailMessage += logBuffer.toString(); 395 } 396 mailMessage += "\n\nMit freundlichem Gruss,\nIhr LDAPImporter"; 397 EMailMessage emm = new EMailMessage( mailSender, mailRcpt, "LDAP Sychronisierung durchgeführt", mailMessage ); 398 MailHelper.createAndSendMail( emm, mailHost ); 399 } catch ( SendMailException ex ) { 400 ex.printStackTrace(); 401 } 402 403 } 404 405 /** 406 * @param args 407 * @throws Exception 408 */ 409 public static void main( String[] args ) 410 throws Exception { 411 412 if ( args.length != 1 ) { 413 System.out.println( "USAGE: LDAPImporter configfile" ); 414 System.exit( 0 ); 415 } 416 417 long begin = System.currentTimeMillis(); 418 System.out.println( "Beginning synchronisation..." ); 419 420 LDAPImporter exporter = null; 421 try { 422 Properties config = new Properties(); 423 config.load( new FileInputStream( args[0] ) ); 424 exporter = new LDAPImporter( config ); 425 426 exporter.synchronizeUsers(); 427 exporter.updateSecAll(); 428 exporter.commitChanges(); 429 } catch ( Exception e ) { 430 if ( exporter != null ) { 431 exporter.abortChanges(); 432 } 433 sendError( e ); 434 System.err.println( "Synchronisation has been aborted. Error message: " ); 435 e.printStackTrace(); 436 System.exit( 0 ); 437 } 438 439 if ( mailLog ) { 440 exporter.sendLog(); 441 } 442 443 System.out.println( "Synchronisation took " + ( System.currentTimeMillis() - begin ) + " milliseconds." ); 444 System.exit( 0 ); 445 } 446 }