    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    }