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