001    /**
002     *  Licensed to the Apache Software Foundation (ASF) under one or more
003     *  contributor license agreements.  See the NOTICE file distributed with
004     *  this work for additional information regarding copyright ownership.
005     *  The ASF licenses this file to You under the Apache License, Version 2.0
006     *  (the "License"); you may not use this file except in compliance with
007     *  the License.  You may obtain a copy of the License at
008     *
009     *     http://www.apache.org/licenses/LICENSE-2.0
010     *
011     *  Unless required by applicable law or agreed to in writing, software
012     *  distributed under the License is distributed on an "AS IS" BASIS,
013     *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     *  See the License for the specific language governing permissions and
015     *  limitations under the License.
016     */
017    package org.apache.geronimo.security.realm.providers;
018    
019    import java.io.File;
020    import java.io.FileOutputStream;
021    import java.io.IOException;
022    import java.io.PrintWriter;
023    import java.nio.channels.FileChannel;
024    import java.nio.channels.FileLock;
025    import java.text.DateFormat;
026    import java.text.SimpleDateFormat;
027    import java.util.Arrays;
028    import java.util.Collections;
029    import java.util.Date;
030    import java.util.List;
031    import java.util.Map;
032    import javax.security.auth.Subject;
033    import javax.security.auth.callback.Callback;
034    import javax.security.auth.callback.CallbackHandler;
035    import javax.security.auth.callback.NameCallback;
036    import javax.security.auth.login.LoginException;
037    import javax.security.auth.spi.LoginModule;
038    
039    import org.apache.commons.logging.Log;
040    import org.apache.commons.logging.LogFactory;
041    import org.apache.geronimo.security.jaas.JaasLoginModuleUse;
042    import org.apache.geronimo.security.jaas.WrappingLoginModule;
043    import org.apache.geronimo.system.serverinfo.ServerInfo;
044    
045    /**
046     * Writes audit records to a file for all authentication activity.  Currently
047     * doesn't perform too well; perhaps the file management should be centralized
048     * and the IO objects kept open across many requests.  It would also be nice
049     * to write in a more convenient XML format.
050     *
051     * This module does not write any Principals into the Subject.
052     *
053     * To enable this login module, set your primary login module to REQUIRED or
054     * OPTIONAL, and list this module after it (with any setting).
055     *
056     * This login module does not check credentials so it should never be able to cause a login to succeed.
057     * Therefore the lifecycle methods must return false to indicate success or throw a LoginException to indicate failure.
058     *
059     * @version $Rev: 706640 $ $Date: 2008-10-21 14:44:05 +0000 (Tue, 21 Oct 2008) $
060     */
061    public class FileAuditLoginModule implements LoginModule {
062        private static Log log = LogFactory.getLog(FileAuditLoginModule.class);
063    
064        public static final String LOG_FILE_OPTION = "file";
065        public final static List<String> supportedOptions = Collections.unmodifiableList(Arrays.asList(LOG_FILE_OPTION));
066    
067        private final static DateFormat DATE_FORMAT = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss");
068        private File logFile;
069        private CallbackHandler handler;
070        private String username;
071    
072        public void initialize(Subject subject, CallbackHandler callbackHandler,
073                               Map sharedState, Map options) {
074            for(Object option: options.keySet()) {
075                if(!supportedOptions.contains(option) && !JaasLoginModuleUse.supportedOptions.contains(option)
076                        && !WrappingLoginModule.supportedOptions.contains(option)) {
077                    log.warn("Ignoring option: "+option+". Not supported.");
078                }
079            }
080            String name = (String) options.get(LOG_FILE_OPTION);
081            ServerInfo info = (ServerInfo) options.get(JaasLoginModuleUse.SERVERINFO_LM_OPTION);
082            logFile = info.resolve(name);
083            handler = callbackHandler;
084        }
085    
086        public boolean login() throws LoginException {
087            NameCallback user = new NameCallback("User name:");
088            Callback[] callbacks = new Callback[]{user};
089            try {
090                handler.handle(callbacks);
091            } catch (Exception e) {
092                throw (LoginException)new LoginException("Unable to process callback: "+e.getMessage()).initCause(e);
093            }
094            if(callbacks.length != 1) {
095                throw new IllegalStateException("Number of callbacks changed by server!");
096            }
097            user = (NameCallback) callbacks[0];
098            username = user.getName();
099            writeToFile("Authentication attempt");
100    
101            return false;
102        }
103    
104        private synchronized void writeToFile(String action) {
105            Date date = new Date();
106            try {
107                FileOutputStream out = new FileOutputStream(logFile, true);
108                FileChannel channel = out.getChannel();
109                FileLock lock = channel.lock(0, Long.MAX_VALUE, false);
110                PrintWriter writer = new PrintWriter(out, false);
111                writer.println(DATE_FORMAT.format(date)+" - "+action+" - "+username);
112                writer.flush();
113                writer.close();
114                if(lock.isValid()) {
115                    lock.release();
116                }
117            } catch (IOException e) {
118                throw new RuntimeException("Unable to write to authentication log file", e);
119            }
120        }
121    
122        public boolean commit() throws LoginException {
123            if(username != null) {
124                writeToFile("Authentication succeeded");
125            }
126            return false;
127        }
128    
129        public boolean abort() throws LoginException {
130            if(username != null) { //work around initial "fake" login
131                writeToFile("Authentication failed");
132                username = null;
133            }
134            return false;
135        }
136    
137        public boolean logout() throws LoginException {
138            if(username != null) {
139                writeToFile("Explicit logout");
140                username = null;
141            }
142            return false;
143        }
144    }