Package org.xmlBlaster.authentication.plugins.htpasswd

Source Code of org.xmlBlaster.authentication.plugins.htpasswd.HtPasswd$Container

/*
        HtPasswd.java

        16/11/01 19:27 cyrille@ktaland.com

*/
package org.xmlBlaster.authentication.plugins.htpasswd;

import org.xmlBlaster.authentication.SessionInfo;
import org.xmlBlaster.authentication.plugins.DataHolder;
import org.xmlBlaster.authentication.plugins.SessionHolder;
import org.xmlBlaster.util.FileLocator;
import org.xmlBlaster.util.SessionName;
import org.xmlBlaster.util.StringPairTokenizer;
import org.xmlBlaster.util.XmlBlasterException;
import org.xmlBlaster.util.Global;
import org.xmlBlaster.util.def.Constants;
import org.xmlBlaster.util.def.ErrorCode;
import org.xmlBlaster.util.def.MethodName;

import java.util.logging.Logger;
import java.util.logging.Level;

import java.io.* ;
import java.util.HashSet;
import java.util.Hashtable ;
import java.util.Enumeration ;
import java.util.Map;
import java.util.Set;
import java.util.Vector;
//import org.xmlBlaster.authentication.plugins.htpasswd.jcrypt;

/**
* In xmlBlaster.properties add :<br>
* Security.Server.Plugin.htpasswd.secretfile=${user.home}${file.separator}xmlBlaster.htpasswd
* <p />
* Changes: astelzl@avitech.de<br />
* There can be three cases for authentication:
* <ol>
*   <li>in xmlBlaster.properties the property Security.Server.Plugin.htpasswd.allowPartialUsername is true ->
*       the user is authenticated with the right password and an username which begins with the specified username
*   </li>
*   <li>allowPartialUsername is false ->
*        the user is just authenticated if the username and password in the password file
*        exactly equals the specifications at connection to the xmlBlaster
*   </li>it is possible that the password file just contains a * instead
*        of (username,password) tuples -> any username and password combination is authenticated
*        Same if Security.Server.Plugin.htpasswd.secretfile=NONE
*   </li>
</ol>
* <p />
* Changes: mr@marcelruff.info<br />
* Added simple authorization support.
* <p />
* NOTE: Currently the htpasswd file is reread every time a client logs in (see Session.java new HtPasswd())
*       we should change this to check the timestamp of the file.
*       On client - reconnect there is no reload forced.
* <p />
* Switch on logging with: -logging/org.xmlBlaster.authentication.plugins.htpasswd FINEST
* @author <a href="mailto:cyrille@ktaland.com">Cyrille Giquello</a> 16/11/01 09:06
* @see <a href="http://www.xmlBlaster.org/xmlBlaster/doc/requirements/security.htpasswd.html">The security.htpasswd requirement</a>
*/
public class HtPasswd {

   private static final String ME = "HtPasswd";
  
   private static boolean firstRead = true;

   protected Global glob;
   private static Logger log = Logger.getLogger(HtPasswd.class.getName());

   protected final int ALLOW_PARTIAL_USERNAME = 1;
   protected final int FULL_USERNAME = 2;
   protected final int SWITCH_OFF = 3;

   protected int useFullUsername = ALLOW_PARTIAL_USERNAME;
   protected String htpasswdFilename = null ;
   /* Key is user name, values is the Container with encrypted password */
   protected Hashtable htpasswdMap = null ;
  
   protected Hashtable containerCache = new Hashtable();
   protected long lastModified = -1L;

   private static boolean first = true;
   private static boolean firstWild = true;
  
   private class Container {
      public String userName="";
      public String password="";
      public Map allowedMethodNames; // contains MethodNames for authorization as key, set of topic oids as value
      public Container(String user) {
         this.userName = user;
      }
      public String toString() {
         String ret = "user='" + userName + "' passwd='" + password + "'";
         if (this.allowedMethodNames != null)
            ret += " allowedMethods=" + MethodName.toString((MethodName[])this.allowedMethodNames.keySet().toArray(new MethodName[this.allowedMethodNames.size()]));
         return ret;
      }
      public boolean isAllowed(MethodName methodName, String topicOid) {
         if (this.allowedMethodNames == null) return true;
         Set oidSet = (Set)this.allowedMethodNames.get(methodName);
         if (oidSet == null) return false;
         if (topicOid == null) return true;
         if (oidSet.size() == 0) return true;
         return oidSet.contains(topicOid.trim());
      }
   }

   /**
    * Check password
    * 16/11/01 19:36 mad@ktaland.com
    */
   public HtPasswd(Global glob) throws XmlBlasterException {
      this.glob = glob;

      htpasswdFilename = glob.getProperty().get("Security.Server.Plugin.htpasswd.secretfile", "NONE" );
      boolean allowPartialUsername = glob.getProperty().get("Security.Server.Plugin.htpasswd.allowPartialUsername", false);
      if ( allowPartialUsername ) {
         useFullUsername = ALLOW_PARTIAL_USERNAME;
         if (log.isLoggable(Level.FINE)) log.fine("Login names are searched with 'startswith' mode in '" + htpasswdFilename + "'.");
      }
      else {
          useFullUsername = FULL_USERNAME;
          if (log.isLoggable(Level.FINE)) log.fine( "contructor(" + htpasswdFilename + ") allowPartialUsername=false" );
      }
      if (htpasswdFilename != null && htpasswdFilename.equals("NONE")) {
          useFullUsername = SWITCH_OFF;
          if (first) {
             log.warning("Security risk, no access control: The passwd file is switched off with 'Security.Server.Plugin.htpasswd.secretfile=NONE'");
             first = false;
          }
          return;
      }


      if (log.isLoggable(Level.FINE)) log.fine( "contructor(" + htpasswdFilename + ") " );

      if( readHtpasswordFile( htpasswdFilename ) ){
      }

   }//HtPassWd

   /**
    * Helper class for checkPassword in the case of startWith(username) ->
    * here more usernames of the hashtable can be right
    * @param userPassword password in plaintext
    * @param fileEncodedPass vector of passwords where usernames match with the specified beginning of an username
    * @return true if any one matches
    */
   private boolean checkDetailed(String userPassword, Vector fileEncodedPass)
   {
     if (log.isLoggable(Level.FINE)) log.fine("Comparing '" + userPassword + "' in " + fileEncodedPass.size() + " possibilities");
     String encoded = null,salt,userEncoded;
     for (Enumeration e = fileEncodedPass.elements();e.hasMoreElements();)
     {
       Container container = (Container)e.nextElement();
       encoded = container.password;
       if (encoded != null && encoded.length() == 0) return true; // empty password "joe::"
       if (encoded != null && encoded.length() > 2)
       {  salt = encoded.substring(0,2);
          userEncoded = jcrypt.crypt(salt,userPassword);
          if (log.isLoggable(Level.FINE)) log.fine("Comparing '" + userEncoded + "' with passwd entry '" + encoded + "'");
          if ( userEncoded.trim().equals(encoded.trim()) )
            return true;    
       }
     }
     return false;
   }
    

   /**
    * Check password
    * @param password The clear text password
    * @return true The password is valid
    */
   public boolean checkPassword( String userName, String userPassword )
                throws XmlBlasterException {

      //if (log.TRACE && htpasswd!=null) log.trace(ME, "Checking userName=" + userName + " passwd='" + userPassword + "' in " + htpasswd.size() + " entries, mode=" + useFullUsername + " ...");
      if ( useFullUsername == SWITCH_OFF ) {
        return true;
      }
      if( this.htpasswdMap != null && userName!=null && userPassword!=null ){
         //find user in Hashtable htpasswd
         Vector pws = lookup(userName);
         return checkDetailed(userPassword,pws);
      }
      return false;
   }//checkPassword


   /**
    * Lookup userName in password file
    * @param userName
    * @return A list containing Container instances (matching the userName)
    */
   private Vector lookup(String userName) {
      Vector pws = new Vector();
      if (userName == null) return pws;
      //find user in Hashtable htpasswd
      String key;
      boolean found = false;
    if ( useFullUsername == FULL_USERNAME ) {
       Container container = (Container)this.htpasswdMap.get(userName);
       if (container != null) {
          pws.addElement(container);
          found = true;
       }
    }
    else { // ALLOW_PARTIAL_USERNAME
         for (Enumeration e = this.htpasswdMap.keys();e.hasMoreElements() ; ) {
       key = (String)e.nextElement();
       if (log.isLoggable(Level.FINE)) log.fine("Checking userName=" + userName + " with key='" + key + "'");
          if (userName.startsWith(key) || userName.endsWith(key)) {
             Container container = (Container)this.htpasswdMap.get(key);
             pws.addElement(container);
             found = true;
        }
     }
    }
 
      if (!found) { // allow wildcard entry, for example "*:ad9dfjhf0"
      for (Enumeration e = this.htpasswdMap.keys();e.hasMoreElements() ; ) {
        key = (String)e.nextElement();
        if (key.equals("*")) {
           Container container = (Container)this.htpasswdMap.get(key);
           pws.addElement(container);
        }
      }
    }
      return pws;
   }
  
   /**
    * Check of MethodName is allowed to be invoked by user.
    *
    * @param sessionHolder The user
    * @param dataHolder The method called
    * @return true if is authorized, false if no access
    */
   public boolean isAuthorized(SessionHolder sessionHolder, DataHolder dataHolder) {
      if (this.htpasswdMap == null) return true;
      SessionInfo sessionInfo = sessionHolder.getSessionInfo();
      if (sessionInfo == null) {
         log.warning("sessionInfo is null, will not authorize");
         return false;
      }
      SessionName sessionName = sessionInfo.getSessionName();
      if (sessionName == null) {
         log.warning("sessionName for '" + sessionInfo.toXml() + "' is null, will not authorize");
         return false;
      }
      String loginName = sessionName.getLoginName();
      if (loginName == null) {
         log.warning("loginName for '" + sessionName.toXml() + "' is null, will not authorize");
         return false;
      }
     
      Container container = (Container)this.containerCache.get(loginName);
      if (container == null) {
        Vector pws = lookup(loginName);
        if (pws.size() > 0) {
          container = (Container)pws.elementAt(0);
          this.containerCache.put(loginName, container);
        }
      }

      if (container == null) {
         StringBuffer buf = new StringBuffer(1024);
         Object[] keys = this.htpasswdMap.keySet().toArray();
         for (int i=0; i < keys.length; i++)
            buf.append("'").append(keys[i]).append("' ");
         log.severe("The login entry '" + loginName + "' has not been found in '" + this.htpasswdFilename + "'. Found entries are : " + buf.toString());
         return false;
      }
      if (container.allowedMethodNames == null) return true;
      if (dataHolder.getMsgUnit() == null || dataHolder.getMsgUnit().getKeyData() == null)
         return false;
      return container.isAllowed(dataHolder.getAction(),
             dataHolder.getKeyUrl());
   }

   /**
    * Read passwords file
    * 16/11/01 20:42 mad@ktaland.com
    * @param the password filename
    * @return true if file all readed & well formated
    */
   boolean readHtpasswordFile( String htpasswdFilename )
        throws XmlBlasterException {

      if (log.isLoggable(Level.FINER)) log.finer(htpasswdFilename);
      File            htpasswdFile ;

      if( htpasswdFilename == null)
      throw new XmlBlasterException(glob, ErrorCode.RESOURCE_CONFIGURATION, ME, "missing property Security.Server.Plugin.htpasswd.secretfile" );

      htpasswdFile =new File(htpasswdFilename) ;

      if( ! htpasswdFile.exists() ) {
         log.severe( "Secret file doesn't exist : "+htpasswdFilename + ", please check your 'Security.Server.Plugin.htpasswd.secretfile' setting.");
         throw new XmlBlasterException(glob, ErrorCode.RESOURCE_CONFIGURATION, ME, "secret file doesn't exist : "+htpasswdFilename );
      }
      if( ! htpasswdFile.canRead() ) {
         log.severe( "Secret file '"+htpasswdFilename + "' has no read permission");
         throw new XmlBlasterException(glob, ErrorCode.RESOURCE_CONFIGURATION, ME, "no read access on file : "+htpasswdFilename );
      }
     
      if(firstRead) {
        firstRead = false;
          log.info("Using secret file '"+htpasswdFilename + "' for authentication");
      }
     
      long curr = htpasswdFile.lastModified();
      if (this.lastModified == curr)
        return true;
      this.lastModified = curr;
     
      this.containerCache.clear();

      try {
         String rawString = FileLocator.readAsciiFile(htpasswdFilename);
         java.util.Map map = org.xmlBlaster.util.StringPairTokenizer.parseToStringStringPairs(rawString, "\n", ":");
         java.util.Iterator it = map.keySet().iterator();
         while (it.hasNext()) {
            String user = (String)it.next();
            user = user.trim();
            if (user.startsWith("#" ) || user.length() < 1) {
               continue;
            }
            String tail = (String)map.get(user); // joe:secret:CONNECT,PUBLISH,ERASE:other stuff in future
            String[] tokens = StringPairTokenizer.parseLine(tail, ':', StringPairTokenizer.DEFAULT_QUOTE_CHARACTER, false, true);
            Container container = new Container(user);
            if (tokens.length > 0)
               container.password = tokens[0].trim();
            if (tokens.length > 1) {
               // parse "!SUBSCRIBE,ERASE" or 'CONNECT,DISCONNECT,PUBLISH("xpath://key"),subscribe("exact:hello")'
               // joe:079cv::  allows all methods
               String methodNames = tokens[1].trim();
               if (methodNames != null && methodNames.length() > 0) {
                  boolean positiveList = !methodNames.startsWith("!");
                  container.allowedMethodNames = new java.util.HashMap();
                  if (positiveList) {
                     String[] nameArr = org.xmlBlaster.util.StringPairTokenizer.parseLine(methodNames, ',', StringPairTokenizer.DEFAULT_QUOTE_CHARACTER, false);
                     for (int j=0; j<nameArr.length; j++) {
                        String name = nameArr[j].trim();
                        HashSet set = new HashSet();
                        int start = name.indexOf('(');
                        if (start != -1) {
                           int end = name.lastIndexOf(')');
                           if (end != -1) {
                              String topics = name.substring(start+1, end);
                              String[] topicArr = org.xmlBlaster.util.StringPairTokenizer.parseLine(topics, ';');
                              for (int n=0; n<topicArr.length; n++) {
                                 String url = topicArr[n].trim(); // expecting: "hello" or "exact:hello" or "xpath://key" or "domain:sport"
                                 if (url.length() == 0) continue;
                                 if (url.indexOf(":") == -1) {
                                    url = Constants.EXACT_URL_PREFIX+url;
                                 }
                                 set.add(url.trim());
                              }
                           }
                           name = name.substring(0, start);
                        }
                        try {
                           MethodName methodName = MethodName.toMethodName(name);
                           container.allowedMethodNames.put(methodName, set);
                        }
                        catch (IllegalArgumentException e) {
                           log.severe("Ignoring authorization method name, please check your configuration in '" + htpasswdFilename + "': " + e.toString());
                        }
                     }
                  }
                  else {
                     MethodName[] all = MethodName.getAll();
                     HashSet set = new HashSet();
                     for (int k=0; k<all.length; k++) container.allowedMethodNames.put(all[k], set);
                     String[] nameArr = org.xmlBlaster.util.StringPairTokenizer.parseLine(methodNames.substring(1), ',');
                     for (int j=0; j<nameArr.length; j++) {
                        try {
                           MethodName methodName = MethodName.toMethodName(nameArr[j].trim());
                           container.allowedMethodNames.remove(methodName);
                        }
                        catch (IllegalArgumentException e) {
                           log.severe("Ignoring authorization method name, please check your configuration in '" + htpasswdFilename + "': " + e.toString());
                        }
                     }
                    
                  }
               }
            }
            if (this.htpasswdMap == null) this.htpasswdMap = new Hashtable();
            this.htpasswdMap.put(user, container);
            if (user.equals("*") && container.password.length() < 1) {
               //This is the third case I mentioned above -> the password-file just contains a '*' -> all connection requests are authenticated
               useFullUsername = SWITCH_OFF;
               if (firstWild) {
                  log.warning("Security risk, no access control: '" + htpasswdFile + "' contains '*'");
                  firstWild = false;
               }
            }
         }


         // Dump it:
         if (log.isLoggable(Level.FINEST)) {
            if (this.htpasswdMap != null) {
               java.util.Iterator i = this.htpasswdMap.values().iterator();
               System.out.println("========================================");
               while (i.hasNext()) {
                  Container container = (Container)i.next();
                  System.out.println(container.toString());
               }
               System.out.println("========================================");
            }
            else {
               System.out.println("======NO PASSWD ENTRY==================================");
            }
         }
        
         //log.info("Successfully read " + htpasswdFilename + " with " + this.htpasswdMap.size() + " entries");

         return true;
      }
      catch(Exception ex) {
         this.htpasswdMap = null ;
         throw new XmlBlasterException(glob, ErrorCode.RESOURCE_CONFIGURATION, ME, "Problem when reading password file '"+htpasswdFilename+"'", ex);
      }
   }//readHtpasswordFile

   public String getPasswdFileName() {
      return htpasswdFilename;
   }

   public void reset() {
     this.containerCache.clear();
     this.htpasswdMap.clear();
     this.lastModified = -1L;
   }
  
   /**
    * Helper class for checkPassword in the case of startWith(username) ->
    * here more usernames of the hashtable can be right
    * @param userPassword password in plaintext
    * @param fileEncodedPass vector of passwords where usernames match with the specified beginning of an username
    * @return true if any one matches
    */
   private static boolean isSamePwd(String userPassword, String encoded) {
      if (encoded != null && encoded.length() == 0) return true; // empty password "joe::"
      if (encoded != null && encoded.length() > 2) { 
         String salt = encoded.substring(0,2);
         log.info("The Salt used is '" + salt + "'");
         String userEncoded = jcrypt.crypt(salt,userPassword);
         return userEncoded.trim().equals(encoded.trim());
      }
      return false;
   }
    

   public static void main(String[] args) {
      if (args.length < 2) {
         System.err.println("usage: " + HtPasswd.class.getName() + " userPwd encodedPwd");
         System.exit(-1);
      }
      String userPwd = args[0];
      String encoded = args[1];
      if (HtPasswd.isSamePwd(userPwd, encoded))
         log.info("The password was OK");
      else
         log.severe("The password was NOT OK");
   }
  
}//class HtAccess
TOP

Related Classes of org.xmlBlaster.authentication.plugins.htpasswd.HtPasswd$Container

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.