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 }