/*
* JBoss, Home of Professional Open Source
* Copyright 2005, JBoss Inc., and individual contributors as indicated
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.test.jmx.interceptors;

import java.security.Principal;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Map;
import javax.naming.InitialContext;
import javax.security.auth.Subject;

import org.jboss.mx.interceptor.AbstractInterceptor;
import org.jboss.mx.server.Invocation;
import org.jboss.mx.server.MBeanInvoker;
import org.jboss.logging.Logger;
import org.jboss.security.RealmMapping;
import org.jboss.security.SubjectSecurityManager;
import org.jboss.security.SimplePrincipal;
import org.jboss.security.SecurityAssociation;
import org.jboss.invocation.MarshalledInvocation;

/** A role based security interceptor that requries the caller of
 * any write operations to have a JNDIWriter role and the caller of any
 * read operations to have a JNDIReader role.
 *
 * @author Scott.Stark@jboss.org
 * @version $Revision$
 */
public final class JNDISecurity
   extends AbstractInterceptor
{
   private static Logger log = Logger.getLogger(JNDISecurity.class);
   private static final Principal READER_ROLE = new SimplePrincipal("JNDIReader");
   private static final Principal WRITER_ROLE = new SimplePrincipal("JNDIWriter");

   private String securityDomain;
   private SubjectSecurityManager authMgr;
   private RealmMapping roleMgr;
   private Map methodMap;

   public String getSecurityDomain()
   {
      return securityDomain;
   }
   public void setSecurityDomain(String securityDomain) throws Exception
   {
      log.info("setSecurityDomain: "+securityDomain);
      this.securityDomain = securityDomain;
      InitialContext ctx = new InitialContext();
      this.authMgr = (SubjectSecurityManager) ctx.lookup(securityDomain);
      this.roleMgr = (RealmMapping) ctx.lookup(securityDomain);
   }

   // Interceptor overrides -----------------------------------------
   public Object invoke(Invocation invocation) throws Throwable
   {
      String opName = invocation.getName();
      log.info("invoke, opName="+opName);

      // If this is not the invoke(Invocation) op just pass it along
      if( opName == null || opName.equals("invoke") == false )
         return invocation.nextInterceptor().invoke(invocation);

      Object[] args = invocation.getArgs();
      org.jboss.invocation.Invocation invokeInfo =
         (org.jboss.invocation.Invocation) args[0];
      // There must be a valid security manager
      if( authMgr == null || roleMgr == null )
      {
         String msg = "No security mgr configured, check securityDomain: "+securityDomain;
         throw new SecurityException(msg);
      }

      // Get the security context passed from the client
      Principal principal = invokeInfo.getPrincipal();
      Object credential = invokeInfo.getCredential();
      Subject subject = new Subject();
      if( authMgr.isValid(principal, credential, subject) == false )
      {
         String msg = "Failed to authenticate principal: "+principal;
         throw new SecurityException(msg);
      }
      SecurityAssociation.pushSubjectContext(subject, principal, credential);

      try
      {
         // See what operation is being attempted
         if( methodMap == null )
            initMethodMap(invocation);
         HashSet methodRoles = new HashSet();
         if( invokeInfo instanceof MarshalledInvocation )
         {
            MarshalledInvocation mi = (MarshalledInvocation) invokeInfo;
            mi.setMethodMap(methodMap);
         }
         Method method = invokeInfo.getMethod();
         boolean isRead = isReadMethod(method);
         if( isRead == true )
            methodRoles.add(READER_ROLE);
         else
            methodRoles.add(WRITER_ROLE);
         if( roleMgr.doesUserHaveRole(principal, methodRoles) == false )
         {
            String msg = "Failed to authorize subject: "+authMgr.getActiveSubject()
               + " principal: " + principal
               + " for access roles:" + methodRoles;
            throw new SecurityException(msg);
         }
   
         // Let the invocation go
         return invocation.nextInterceptor().invoke(invocation);
      }
      finally
      {
         SecurityAssociation.popSubjectContext();         
      }
   }

   private boolean isReadMethod(Method method)
   {
      boolean isRead = true;
      String name = method.getName();
      isRead = name.equals("lookup") || name.equals("list")
         || name.equals("listBindings");
      return isRead;
   }

   /**
    *
    */
   private void initMethodMap(Invocation invocation) throws Throwable
   {
      MBeanInvoker invoker = invocation.getInvoker();
      methodMap = (Map) invoker.getAttribute("MethodMap");
   }
}
