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.Date;
028    import java.util.Map;
029    import javax.security.auth.Subject;
030    import javax.security.auth.callback.Callback;
031    import javax.security.auth.callback.CallbackHandler;
032    import javax.security.auth.callback.NameCallback;
033    import javax.security.auth.login.LoginException;
034    import javax.security.auth.spi.LoginModule;
035    
036    import org.apache.geronimo.security.jaas.JaasLoginModuleUse;
037    import org.apache.geronimo.system.serverinfo.ServerInfo;
038    
039    /**
040     * Writes audit records to a file for all authentication activity.  Currently
041     * doesn't perform too well; perhaps the file management should be centralized
042     * and the IO objects kept open across many requests.  It would also be nice
043     * to write in a more convenient XML format.
044     *
045     * This module does not write any Principals into the Subject.
046     *
047     * To enable this login module, set your primary login module to REQUIRED or
048     * OPTIONAL, and list this module after it (with any setting).
049     *
050     * This login module does not check credentials so it should never be able to cause a login to succeed.
051     * Therefore the lifecycle methods must return false to indicate success or throw a LoginException to indicate failure.
052     *
053     * @version $Rev: 565912 $ $Date: 2007-08-14 17:03:11 -0400 (Tue, 14 Aug 2007) $
054     */
055    public class FileAuditLoginModule implements LoginModule {
056        public static final String LOG_FILE_OPTION = "file";
057        private final static DateFormat DATE_FORMAT = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss");
058        private File logFile;
059        private CallbackHandler handler;
060        private String username;
061    
062        public void initialize(Subject subject, CallbackHandler callbackHandler,
063                               Map sharedState, Map options) {
064            String name = (String) options.get(LOG_FILE_OPTION);
065            ServerInfo info = (ServerInfo) options.get(JaasLoginModuleUse.SERVERINFO_LM_OPTION);
066            logFile = info.resolve(name);
067            handler = callbackHandler;
068        }
069    
070        public boolean login() throws LoginException {
071            NameCallback user = new NameCallback("User name:");
072            Callback[] callbacks = new Callback[]{user};
073            try {
074                handler.handle(callbacks);
075            } catch (Exception e) {
076                throw (LoginException)new LoginException("Unable to process callback: "+e.getMessage()).initCause(e);
077            }
078            if(callbacks.length != 1) {
079                throw new IllegalStateException("Number of callbacks changed by server!");
080            }
081            user = (NameCallback) callbacks[0];
082            username = user.getName();
083            writeToFile("Authentication attempt");
084    
085            return false;
086        }
087    
088        private synchronized void writeToFile(String action) {
089            Date date = new Date();
090            try {
091                FileOutputStream out = new FileOutputStream(logFile, true);
092                FileChannel channel = out.getChannel();
093                FileLock lock = channel.lock(0, Long.MAX_VALUE, false);
094                PrintWriter writer = new PrintWriter(out, false);
095                writer.println(DATE_FORMAT.format(date)+" - "+action+" - "+username);
096                writer.flush();
097                writer.close();
098                if(lock.isValid()) {
099                    lock.release();
100                }
101            } catch (IOException e) {
102                throw new RuntimeException("Unable to write to authentication log file", e);
103            }
104        }
105    
106        public boolean commit() throws LoginException {
107            writeToFile("Authentication succeeded");
108            return false;
109        }
110    
111        public boolean abort() throws LoginException {
112            if(username != null) { //work around initial "fake" login
113                writeToFile("Authentication failed");
114                username = null;
115            }
116            return false;
117        }
118    
119        public boolean logout() throws LoginException {
120            writeToFile("Explicit logout");
121            username = null;
122            return false;
123        }
124    }