View Javadoc

    1   // ========================================================================
    2   // Copyright 1996-2005 Mort Bay Consulting Pty. Ltd.
    3   // ------------------------------------------------------------------------
    4   // Licensed under the Apache License, Version 2.0 (the "License");
    5   // you may not use this file except in compliance with the License.
    6   // You may obtain a copy of the License at
    7   // http://www.apache.org/licenses/LICENSE-2.0
    8   // Unless required by applicable law or agreed to in writing, software
    9   // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  // ========================================================================
    14
    15  package org.mortbay.jetty.security;
    16
    17  import java.io.File;
    18  import java.io.FilenameFilter;
    19  import java.io.IOException;
    20  import java.io.PrintStream;
    21  import java.security.Principal;
    22  import java.util.ArrayList;
    23  import java.util.HashMap;
    24  import java.util.HashSet;
    25  import java.util.Iterator;
    26  import java.util.List;
    27  import java.util.Map;
    28  import java.util.Properties;
    29  import java.util.StringTokenizer;
    30
    31  import org.mortbay.component.AbstractLifeCycle;
    32  import org.mortbay.jetty.Request;
    33  import org.mortbay.jetty.Response;
    34  import org.mortbay.log.Log;
    35  import org.mortbay.resource.Resource;
    36  import org.mortbay.util.Scanner;
    37  import org.mortbay.util.Scanner.BulkListener;
    38
    39  /* ------------------------------------------------------------ */
    40  /** HashMapped User Realm.
    41   *
    42   * An implementation of UserRealm that stores users and roles in-memory in
    43   * HashMaps.
    44   * <P>
    45   * Typically these maps are populated by calling the load() method or passing
    46   * a properties resource to the constructor. The format of the properties
    47   * file is: <PRE>
    48   *  username: password [,rolename ...]
    49   * </PRE>
    50   * Passwords may be clear text, obfuscated or checksummed.  The class
    51   * com.mortbay.Util.Password should be used to generate obfuscated
    52   * passwords or password checksums.
    53   *
    54   * If DIGEST Authentication is used, the password must be in a recoverable
    55   * format, either plain text or OBF:.
    56   *
    57   * The HashUserRealm also implements SSORealm but provides no implementation
    58   * of SSORealm. Instead setSSORealm may be used to provide a delegate
    59   * SSORealm implementation.
    60   *
    61   * @see Password
    62   * @author Greg Wilkins (gregw)
    63   */
    64  public class HashUserRealm extends AbstractLifeCycle implements UserRealm, SSORealm
    65  {
    66
    67      /** HttpContext Attribute to set to activate SSO.
    68       */
    69      public static final String __SSO = "org.mortbay.http.SSO";
    70
    71      /* ------------------------------------------------------------ */
    72      private String _realmName;
    73      private String _config;
    74      private Resource _configResource;
    75      protected HashMap _users=new HashMap();
    76      protected HashMap _roles=new HashMap(7);
    77      private SSORealm _ssoRealm;
    78      private Scanner _scanner;
    79      private int _refreshInterval=0;//default is not to reload
    80
    81
    82      /* ------------------------------------------------------------ */
    83      /** Constructor.
    84       */
    85      public HashUserRealm()
    86      {}
    87
    88      /* ------------------------------------------------------------ */
    89      /** Constructor.
    90       * @param name Realm Name
    91       */
    92      public HashUserRealm(String name)
    93      {
    94          _realmName=name;
    95      }
    96
    97      /* ------------------------------------------------------------ */
    98      /** Constructor.
    99       * @param name Realm name
    100      * @param config Filename or url of user properties file.
    101      */
    102     public HashUserRealm(String name, String config)
    103         throws IOException
    104     {
    105         _realmName=name;
    106         setConfig(config);
    107     }
    108
    109     public String getConfig()
    110     {
    111         return _config;
    112     }
    113
    114     public Resource getConfigResource()
    115     {
    116         return _configResource;
    117     }
    118
    119     /* ------------------------------------------------------------ */
    120     /** Load realm users from properties file.
    121      * The property file maps usernames to password specs followed by
    122      * an optional comma separated list of role names.
    123      *
    124      * @param config Filename or url of user properties file.
    125      * @exception IOException
    126      */
    127     public void setConfig(String config)
    128         throws IOException
    129     {
    130         _config=config;
    131         _configResource=Resource.newResource(_config);
    132        loadConfig();
    133
    134     }
    135
    136
    137     public void setRefreshInterval (int msec)
    138     {
    139         _refreshInterval=msec;
    140     }
    141
    142     public int getRefreshInterval()
    143     {
    144         return _refreshInterval;
    145     }
    146
    147     protected void loadConfig ()
    148     throws IOException
    149     {
    150         synchronized (this)
    151         {
    152             _users.clear();
    153             _roles.clear();
    154
    155             if(Log.isDebugEnabled())Log.debug("Load "+this+" from "+_config);
    156             Properties properties = new Properties();
    157             properties.load(_configResource.getInputStream());
    158
    159             Iterator iter = properties.entrySet().iterator();
    160             while(iter.hasNext())
    161             {
    162                 Map.Entry entry = (Map.Entry)iter.next();
    163
    164                 String username=entry.getKey().toString().trim();
    165                 String credentials=entry.getValue().toString().trim();
    166                 String roles=null;
    167                 int c=credentials.indexOf(',');
    168                 if (c>0)
    169                 {
    170                     roles=credentials.substring(c+1).trim();
    171                     credentials=credentials.substring(0,c).trim();
    172                 }
    173
    174                 if (username!=null && username.length()>0 &&
    175                         credentials!=null && credentials.length()>0)
    176                 {
    177                     put(username,credentials);
    178                     if(roles!=null && roles.length()>0)
    179                     {
    180                         StringTokenizer tok = new StringTokenizer(roles,", ");
    181                         while (tok.hasMoreTokens())
    182                             addUserToRole(username,tok.nextToken());
    183                     }
    184                 }
    185             }
    186         }
    187     }
    188
    189     /* ------------------------------------------------------------ */
    190     /**
    191      * @param name The realm name
    192      */
    193     public void setName(String name)
    194     {
    195         _realmName=name;
    196     }
    197
    198     /* ------------------------------------------------------------ */
    199     /**
    200      * @return The realm name.
    201      */
    202     public String getName()
    203     {
    204         return _realmName;
    205     }
    206
    207     /* ------------------------------------------------------------ */
    208     public Principal getPrincipal(String username)
    209     {
    210         return (Principal)_users.get(username);
    211     }
    212
    213     /* ------------------------------------------------------------ */
    214     public Principal authenticate(String username,Object credentials,Request request)
    215     {
    216         KnownUser user;
    217         synchronized (this)
    218         {
    219             user = (KnownUser)_users.get(username);
    220         }
    221         if (user==null)
    222             return null;
    223
    224         if (user.authenticate(credentials))
    225             return user;
    226
    227         return null;
    228     }
    229
    230     /* ------------------------------------------------------------ */
    231     public void disassociate(Principal user)
    232     {
    233     }
    234
    235     /* ------------------------------------------------------------ */
    236     public Principal pushRole(Principal user, String role)
    237     {
    238         if (user==null)
    239             user=new User();
    240
    241         return new WrappedUser(user,role);
    242     }
    243
    244     /* ------------------------------------------------------------ */
    245     public Principal popRole(Principal user)
    246     {
    247         WrappedUser wu = (WrappedUser)user;
    248         return wu.getUserPrincipal();
    249     }
    250
    251     /* ------------------------------------------------------------ */
    252     /** Put user into realm.
    253      * @param name User name
    254      * @param credentials String password, Password or UserPrinciple
    255      *                    instance.
    256      * @return Old UserPrinciple value or null
    257      */
    258     public synchronized Object put(Object name, Object credentials)
    259     {
    260         if (credentials instanceof Principal)
    261             return _users.put(name.toString(),credentials);
    262
    263         if (credentials instanceof Password)
    264             return _users.put(name,new KnownUser(name.toString(),(Password)credentials));
    265         if (credentials != null)
    266             return _users.put(name,new KnownUser(name.toString(),Credential.getCredential(credentials.toString())));
    267         return null;
    268     }
    269
    270     /* ------------------------------------------------------------ */
    271     /** Add a user to a role.
    272      * @param userName
    273      * @param roleName
    274      */
    275     public synchronized void addUserToRole(String userName, String roleName)
    276     {
    277         HashSet userSet = (HashSet)_roles.get(roleName);
    278         if (userSet==null)
    279         {
    280             userSet=new HashSet(11);
    281             _roles.put(roleName,userSet);
    282         }
    283         userSet.add(userName);
    284     }
    285
    286     /* -------------------------------------------------------- */
    287     public boolean reauthenticate(Principal user)
    288     {
    289         return ((User)user).isAuthenticated();
    290     }
    291
    292     /* ------------------------------------------------------------ */
    293     /** Check if a user is in a role.
    294      * @param user The user, which must be from this realm
    295      * @param roleName
    296      * @return True if the user can act in the role.
    297      */
    298     public synchronized boolean isUserInRole(Principal user, String roleName)
    299     {
    300         if (user instanceof WrappedUser)
    301             return ((WrappedUser)user).isUserInRole(roleName);
    302
    303         if (user==null || !(user instanceof User) || ((User)user).getUserRealm()!=this)
    304             return false;
    305
    306         HashSet userSet = (HashSet)_roles.get(roleName);
    307         return userSet!=null && userSet.contains(user.getName());
    308     }
    309
    310     /* ------------------------------------------------------------ */
    311     public void logout(Principal user)
    312     {}
    313
    314     /* ------------------------------------------------------------ */
    315     public String toString()
    316     {
    317         return "Realm["+_realmName+"]=="+_users.keySet();
    318     }
    319
    320     /* ------------------------------------------------------------ */
    321     public void dump(PrintStream out)
    322     {
    323         out.println(this+":");
    324         out.println(super.toString());
    325         out.println(_roles);
    326     }
    327
    328     /* ------------------------------------------------------------ */
    329     /**
    330      * @return The SSORealm to delegate single sign on requests to.
    331      */
    332     public SSORealm getSSORealm()
    333     {
    334         return _ssoRealm;
    335     }
    336
    337     /* ------------------------------------------------------------ */
    338     /** Set the SSORealm.
    339      * A SSORealm implementation may be set to enable support for SSO.
    340      * @param ssoRealm The SSORealm to delegate single sign on requests to.
    341      */
    342     public void setSSORealm(SSORealm ssoRealm)
    343     {
    344         _ssoRealm = ssoRealm;
    345     }
    346
    347     /* ------------------------------------------------------------ */
    348     public Credential getSingleSignOn(Request request,Response response)
    349     {
    350         if (_ssoRealm!=null)
    351             return _ssoRealm.getSingleSignOn(request,response);
    352         return null;
    353     }
    354
    355     /* ------------------------------------------------------------ */
    356     public void setSingleSignOn(Request request,Response response,Principal principal,Credential credential)
    357     {
    358         if (_ssoRealm!=null)
    359             _ssoRealm.setSingleSignOn(request,response,principal,credential);
    360     }
    361
    362     /* ------------------------------------------------------------ */
    363     public void clearSingleSignOn(String username)
    364     {
    365         if (_ssoRealm!=null)
    366             _ssoRealm.clearSingleSignOn(username);
    367     }
    368
    369
    370
    371
    372
    373     /**
    374      * @see org.mortbay.component.AbstractLifeCycle#doStart()
    375      */
    376     protected void doStart() throws Exception
    377     {
    378         super.doStart();
    379         if (_scanner!=null)
    380             _scanner.stop();
    381
    382         if (getRefreshInterval() > 0)
    383         {
    384             _scanner = new Scanner();
    385             _scanner.setScanInterval(getRefreshInterval());
    386             List dirList = new ArrayList(1);
    387             dirList.add(_configResource.getFile());
    388             _scanner.setScanDirs(dirList);
    389             _scanner.setFilenameFilter(new FilenameFilter ()
    390             {
    391                 public boolean accept(File dir, String name)
    392                 {
    393                     File f = new File(dir,name);
    394                     try
    395                     {
    396                         if (f.compareTo(_configResource.getFile())==0)
    397                             return true;
    398                     }
    399                     catch (IOException e)
    400                     {
    401                         return false;
    402                     }
    403
    404                     return false;
    405                 }
    406
    407             });
    408             _scanner.addListener(new BulkListener()
    409             {
    410                 public void filesChanged(List filenames) throws Exception
    411                 {
    412                     if (filenames==null)
    413                         return;
    414                     if (filenames.isEmpty())
    415                         return;
    416                     if (filenames.size()==1 && filenames.get(0).equals(_config))
    417                         loadConfig();
    418                 }
    419                 public String toString()
    420                 {
    421                     return "HashUserRealm$Scanner";
    422                 }
    423
    424             });
    425             _scanner.setReportExistingFilesOnStartup(false);
    426             _scanner.setRecursive(false);
    427             _scanner.start();
    428         }
    429     }
    430
    431     /**
    432      * @see org.mortbay.component.AbstractLifeCycle#doStop()
    433      */
    434     protected void doStop() throws Exception
    435     {
    436         super.doStop();
    437         if (_scanner!=null)
    438             _scanner.stop();
    439         _scanner=null;
    440     }
    441
    442
    443
    444     /* ------------------------------------------------------------ */
    445     /* ------------------------------------------------------------ */
    446     /* ------------------------------------------------------------ */
    447     private class User implements Principal
    448     {
    449         List roles=null;
    450
    451         /* ------------------------------------------------------------ */
    452         private UserRealm getUserRealm()
    453         {
    454             return HashUserRealm.this;
    455         }
    456
    457         public String getName()
    458         {
    459             return "Anonymous";
    460         }
    461
    462         public boolean isAuthenticated()
    463         {
    464             return false;
    465         }
    466
    467         public String toString()
    468         {
    469             return getName();
    470         }
    471     }
    472
    473     /* ------------------------------------------------------------ */
    474     /* ------------------------------------------------------------ */
    475     /* ------------------------------------------------------------ */
    476     private class KnownUser extends User
    477     {
    478         private String _userName;
    479         private Credential _cred;
    480
    481         /* -------------------------------------------------------- */
    482         KnownUser(String name,Credential credential)
    483         {
    484             _userName=name;
    485             _cred=credential;
    486         }
    487
    488         /* -------------------------------------------------------- */
    489         boolean authenticate(Object credentials)
    490         {
    491             return _cred!=null && _cred.check(credentials);
    492         }
    493
    494         /* ------------------------------------------------------------ */
    495         public String getName()
    496         {
    497             return _userName;
    498         }
    499
    500         /* -------------------------------------------------------- */
    501         public boolean isAuthenticated()
    502         {
    503             return true;
    504         }
    505     }
    506
    507     /* ------------------------------------------------------------ */
    508     /* ------------------------------------------------------------ */
    509     /* ------------------------------------------------------------ */
    510     private class WrappedUser extends User
    511     {
    512         private Principal user;
    513         private String role;
    514
    515         WrappedUser(Principal user, String role)
    516         {
    517             this.user=user;
    518             this.role=role;
    519         }
    520
    521         Principal getUserPrincipal()
    522         {
    523             return user;
    524         }
    525
    526         public String getName()
    527         {
    528             return "role:"+role;
    529         }
    530
    531         public boolean isAuthenticated()
    532         {
    533             return true;
    534         }
    535
    536         public boolean isUserInRole(String role)
    537         {
    538             return this.role.equals(role);
    539         }
    540     }
    541 }