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 }