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 }