1   // ========================================================================
2   // $Id: JDBCUserRealm.java 3736 2008-10-04 22:19:26Z gregw $
3   // Copyright 2003-2004 Mort Bay Consulting Pty. Ltd.
4   // ------------------------------------------------------------------------
5   // Licensed under the Apache License, Version 2.0 (the "License");
6   // you may not use this file except in compliance with the License.
7   // You may obtain a copy of the License at 
8   // http://www.apache.org/licenses/LICENSE-2.0
9   // Unless required by applicable law or agreed to in writing, software
10  // distributed under the License is distributed on an "AS IS" BASIS,
11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  // See the License for the specific language governing permissions and
13  // limitations under the License.
14  // ========================================================================
15  
16  package org.mortbay.jetty.security;
17  
18  import java.io.IOException;
19  import java.security.Principal;
20  import java.sql.Connection;
21  import java.sql.DriverManager;
22  import java.sql.PreparedStatement;
23  import java.sql.ResultSet;
24  import java.sql.SQLException;
25  import java.util.Properties;
26  
27  import org.mortbay.jetty.Request;
28  import org.mortbay.jetty.UserRealm;
29  import org.mortbay.log.Log;
30  import org.mortbay.resource.Resource;
31  import org.mortbay.util.Loader;
32  
33  /* ------------------------------------------------------------ */
34  /** HashMapped User Realm with JDBC as data source.
35   * JDBCUserRealm extends HashUserRealm and adds a method to fetch user
36   * information from database.
37   * The authenticate() method checks the inherited HashMap for the user.
38   * If the user is not found, it will fetch details from the database
39   * and populate the inherited HashMap. It then calls the HashUserRealm
40   * authenticate() method to perform the actual authentication.
41   * Periodically (controlled by configuration parameter), internal
42   * hashes are cleared. Caching can be disabled by setting cache
43   * refresh interval to zero.
44   * Uses one database connection that is initialized at startup. Reconnect
45   * on failures. authenticate() is 'synchronized'.
46   *
47   * An example properties file for configuration is in
48   * $JETTY_HOME/etc/jdbcRealm.properties
49   *
50   * @version $Id: JDBCUserRealm.java 3736 2008-10-04 22:19:26Z gregw $
51   * @author Arkadi Shishlov (arkadi)
52   * @author Fredrik Borgh
53   * @author Greg Wilkins (gregw)
54   * @author Ben Alex
55   */
56  
57  public class JDBCUserRealm extends HashUserRealm implements UserRealm
58  {
59  
60      private String _jdbcDriver;
61      private String _url;
62      private String _userName;
63      private String _password;
64      private String _userTable;
65      private String _userTableKey;
66      private String _userTableUserField;
67      private String _userTablePasswordField;
68      private String _roleTable;
69      private String _roleTableKey;
70      private String _roleTableRoleField;
71      private String _userRoleTable;
72      private String _userRoleTableUserKey;
73      private String _userRoleTableRoleKey;
74      private int _cacheTime;
75      
76      private long _lastHashPurge;
77      private Connection _con;
78      private String _userSql;
79      private String _roleSql;
80      
81      /* ------------------------------------------------------------ */
82      /** Constructor. 
83       */
84      public JDBCUserRealm()
85      {
86          super();
87      }
88      
89      /* ------------------------------------------------------------ */
90      /** Constructor. 
91       * @param name 
92       */
93      public JDBCUserRealm(String name)
94      {
95          super(name);
96      }
97      
98      /* ------------------------------------------------------------ */
99      /** Constructor. 
100      * @param name Realm name
101      * @param config Filename or url of JDBC connection properties file.
102      * @exception IOException 
103      * @exception ClassNotFoundException 
104      */
105     public JDBCUserRealm(String name, String config)
106         throws IOException,
107                ClassNotFoundException,
108                InstantiationException,
109                IllegalAccessException
110     {
111         super(name);
112         setConfig(config);
113         Loader.loadClass(this.getClass(),_jdbcDriver).newInstance();
114         connectDatabase();
115     }    
116 
117     /* ------------------------------------------------------------ */
118     /** Load JDBC connection configuration from properties file.
119      *     
120      * @exception IOException 
121      */
122     protected void loadConfig()
123         throws IOException
124     {        
125         Properties properties = new Properties();
126         
127         properties.load(getConfigResource().getInputStream());
128         
129         _jdbcDriver = properties.getProperty("jdbcdriver");
130         _url = properties.getProperty("url");
131         _userName = properties.getProperty("username");
132         _password = properties.getProperty("password");
133         _userTable = properties.getProperty("usertable");
134         _userTableKey = properties.getProperty("usertablekey");
135         _userTableUserField = properties.getProperty("usertableuserfield");
136         _userTablePasswordField = properties.getProperty("usertablepasswordfield");
137         _roleTable = properties.getProperty("roletable");
138         _roleTableKey = properties.getProperty("roletablekey");
139         _roleTableRoleField = properties.getProperty("roletablerolefield");
140         _userRoleTable = properties.getProperty("userroletable");
141         _userRoleTableUserKey = properties.getProperty("userroletableuserkey");
142         _userRoleTableRoleKey = properties.getProperty("userroletablerolekey");
143         // default cachetime = 30s
144         String cachetime = properties.getProperty("cachetime");
145         _cacheTime = cachetime!=null ? new Integer(cachetime).intValue() : 30;
146         
147         if (_jdbcDriver == null || _jdbcDriver.equals("")
148             || _url == null || _url.equals("")
149             || _userName == null || _userName.equals("")
150             || _password == null
151             || _cacheTime < 0)
152         {
153             if(Log.isDebugEnabled())Log.debug("UserRealm " + getName()
154                         + " has not been properly configured");
155         }
156         _cacheTime *= 1000;
157         _lastHashPurge = 0;
158         _userSql = "select " + _userTableKey + ","
159             + _userTablePasswordField + " from "
160             + _userTable + " where "
161             + _userTableUserField + " = ?";
162         _roleSql = "select r." + _roleTableRoleField
163             + " from " + _roleTable + " r, "
164             + _userRoleTable + " u where u."
165             + _userRoleTableUserKey + " = ?"
166             + " and r." + _roleTableKey + " = u."
167             + _userRoleTableRoleKey;
168     }
169 
170     /* ------------------------------------------------------------ */
171     public void logout(Principal user)
172     {}
173     
174     /* ------------------------------------------------------------ */
175     /** (re)Connect to database with parameters setup by loadConfig()
176      */
177     public void connectDatabase()
178     {
179         try 
180         {
181              Class.forName(_jdbcDriver);
182             _con = DriverManager.getConnection(_url, _userName, _password);
183         }
184         catch(SQLException e)
185         {
186             Log.warn("UserRealm " + getName()
187                       + " could not connect to database; will try later", e);
188         }
189         catch(ClassNotFoundException e)
190         {
191             Log.warn("UserRealm " + getName()
192                       + " could not connect to database; will try later", e);
193         }
194     }
195     
196     /* ------------------------------------------------------------ */
197     public Principal authenticate(String username,
198                                   Object credentials,
199                                   Request request)
200     {
201         synchronized (this)
202         {
203             long now = System.currentTimeMillis();
204             if (now - _lastHashPurge > _cacheTime || _cacheTime == 0)
205             {
206                 _users.clear();
207                 _roles.clear();
208                 _lastHashPurge = now;
209             }
210             Principal user = super.getPrincipal(username);
211             if (user == null)
212             {
213                 loadUser(username);
214                 user = super.getPrincipal(username);
215             }
216         }
217         return super.authenticate(username, credentials, request);
218     }
219     
220     /* ------------------------------------------------------------ */
221     /** Check if a user is in a role.
222      * @param user The user, which must be from this realm 
223      * @param roleName 
224      * @return True if the user can act in the role.
225      */
226     public synchronized boolean isUserInRole(Principal user, String roleName)
227     {
228         if(super.getPrincipal(user.getName())==null)
229             loadUser(user.getName());
230         return super.isUserInRole(user, roleName);
231     }
232     
233 
234 
235     
236     /* ------------------------------------------------------------ */
237     private void loadUser(String username)
238     {
239         try
240         {
241             if (null==_con)
242                 connectDatabase();
243             
244             if (null==_con)
245                 throw new SQLException("Can't connect to database");
246             
247             PreparedStatement stat = _con.prepareStatement(_userSql);
248             stat.setObject(1, username);
249             ResultSet rs = stat.executeQuery();
250     
251             if (rs.next())
252             {
253                 int key = rs.getInt(_userTableKey);
254                 put(username, rs.getString(_userTablePasswordField));
255                 stat.close();
256                 
257                 stat = _con.prepareStatement(_roleSql);
258                 stat.setInt(1, key);
259                 rs = stat.executeQuery();
260 
261                 while (rs.next())
262                     addUserToRole(username, rs.getString(_roleTableRoleField));
263                 
264                 stat.close();
265             }
266         }
267         catch (SQLException e)
268         {
269             Log.warn("UserRealm " + getName()
270                       + " could not load user information from database", e);
271             connectDatabase();
272         }
273     }
274 }