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 }