package examples.security.audit;


import java.net.Socket;
import java.security.Principal;
import java.security.acl.Acl;
import java.security.acl.Permission;
import java.util.Vector;
import weblogic.logging.LogOutputStream;
import weblogic.security.X509;
import weblogic.security.acl.DefaultUserInfoImpl;
import weblogic.security.acl.SSLUserInfo;
import weblogic.security.acl.User;
import weblogic.security.acl.UserInfo;
import weblogic.security.audit.AuditProvider;


/**
 * Simple implementation of the audit SPI.  This class just dumps the
 * pertinent information in the server log file.  It should be
 * straightforward to write an alternate implementation that logs
 * events to an RDBMS, for example.
 *
 * @author Copyright (c) 1999-2000 by BEA Systems, Inc. All Rights Reserved.
 */
public class LogAuditProvider
  implements AuditProvider
{
  protected LogOutputStream log;


  private int lastCount = 0;

  private String lastMessage = null;
  
  /**
   * No-arg constructor, as required by the audit SPI.
   */
  public LogAuditProvider()
  {
    log = new LogOutputStream("Audit");
    log.info("Security auditing started");
  }


  public void authenticateUser(String subsystem, UserInfo info, User result)
  {
    if (filterAuthenticateUser(subsystem, info, result))
    {
      String user = info.getName();
      String credential = null;

      if (info instanceof DefaultUserInfoImpl)
      {
	DefaultUserInfoImpl dinfo = (DefaultUserInfoImpl) info;
	Vector credentials = new Vector(1);
      
	// Indicate what kind of credentials we received.
	
	if (dinfo.hasPassword())
	{
	  credentials.addElement("password");
	}
	if (dinfo.hasCertificates())
	{
	  credentials.addElement("certificates");
	}
	if (dinfo instanceof SSLUserInfo)
	{
	  credentials.addElement("SSL certificates");
	}

	credential = credentials.size() > 0
	  ? (weblogic.utils.enumerations.EnumerationUtils.toString
	     (credentials.elements(), " + "))
	  : "nothing";
      } else {
	credential = "unknown";
      }
    
      dump(subsystem, "user auth",
	   "user=" + str(user) + ", credentials=" + credential + ", " +
	   (result != null ? "SUCCESS" : "FAILURE"));
    }
  }


  /**
   * You can override this method in a subclass if you want to
   * conditionally filter authenticateUser events.
   *
   * @return whether to log this event
   */
  protected boolean filterAuthenticateUser(String subsystem, UserInfo info,
					   User result)
  {
    return true;
  }

  
  public void checkPermission(String subsystem, Acl acl, Principal principal,
			      Permission permission, boolean result)
  {
    if (filterCheckPermission(subsystem, acl, principal, permission, result))
    {
      dump(subsystem, "check perm",
	   "acl=" + str(acl != null ? acl.getName() : null) +
	   ", principal=" + str(principal != null ? principal.getName() : null) +
	   ", permission=" + str(permission) + ", " +
	   (result ? "ALLOW" : "DISALLOW"));
    }
  }


  /**
   * You can override this method in a subclass if you want to
   * conditionally filter checkPermission events.
   *
   * @return whether to log this event
   */
  protected boolean filterCheckPermission(String subsystem, Acl acl,
					  Principal principal,
					  Permission permission, boolean result)
  {
    return true;
  }
  

  public void certificateInvalid(String subsystem, Object source, X509 cert)
  {
    if (filterCertificateInvalid(subsystem, source, cert))
    {
      String origin = "unknown";
      
      if (source instanceof Socket)
      {
	Socket sock = (Socket) source;

	origin = sock.getInetAddress().getHostName() + ":" +
	  sock.getPort();
      }
      else if (source instanceof String)
      {
	origin = (String) source;
      }
      
      String reason = cert != null ? cert.explain() : "no certificate presented";
      
      dump(subsystem, "invalid X.509 certificate", "source is " + origin + ", " +
	   (reason != null ? reason : "cause unknown"));
    }
  }
  

  /**
   * You can override this method in a subclass if you want to
   * conditionally filter certificateInvalid events.
   *
   * @return whether to log this event
   */
  protected boolean filterCertificateInvalid(String subsystem, Object source,
					     X509 cert)
  {
    return true;
  }

  
  public void rootCAInvalid(String subsystem, Object source, X509 cert)
  {
    if (filterRootCAInvalid(subsystem, source, cert))
    {
      String origin = "unknown";
      
      if (source instanceof Socket)
      {
	Socket sock = (Socket) source;

	origin = sock.getInetAddress().getHostName() + ":" +
	  sock.getPort();
      }
      else if (source instanceof String)
      {
	origin = (String) source;
      }
      
      dump(subsystem, "invalid X.509 root CA", "source is " + origin);
    }
  }
  

  /**
   * You can override this method in a subclass if you want to
   * conditionally filter rootCAInvalid events.
   *
   * @return whether to log this event
   */
  protected boolean filterRootCAInvalid(String subsystem, Object source,
					X509 cert)
  {
    return true;
  }

  
  /**
   * Wrap an object in double quotes, if its value is not null,
   * otherwise return the string "null".
   */
  private static final String str(Object thing)
  {
    return thing != null ? ("\"" + thing + "\"") : "null";
  }


  protected void dump(String subsystem, String op, String message)
  {
    String msg = "[" + subsystem + "] " + op + ": " + message;

    if (lastMessage == null)
    {
      log.info(msg);
      lastMessage = msg;
      lastCount = 0;
    }
    else if (lastMessage.equals(msg))
    {
      lastCount += 1;
    } else {
      if (lastCount == 1)
      {
	log.info(lastMessage);
      }
      else if (lastCount > 1)
      {
	log.info("(last audit message repeated " + + lastCount + " times)");
      }
      lastCount = 0;
      log.info(msg);
      lastMessage = msg;
    }
  }
}
