1   // ========================================================================
2   // $Id: DataSourceUserRealm.java 3720 2008-10-01 05:08:31Z janb $
3   // Copyright 2008 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  
17  package org.mortbay.jetty.plus.security;
18  
19  import java.io.IOException;
20  import java.security.Principal;
21  import java.sql.Connection;
22  import java.sql.PreparedStatement;
23  import java.sql.ResultSet;
24  import java.sql.SQLException;
25  import java.util.Properties;
26  
27  import javax.naming.InitialContext;
28  import javax.naming.NameNotFoundException;
29  import javax.naming.NamingException;
30  import javax.sql.DataSource;
31  
32  import org.mortbay.jetty.Request;
33  import org.mortbay.jetty.Server;
34  import org.mortbay.jetty.plus.naming.NamingEntryUtil;
35  import org.mortbay.jetty.security.HashUserRealm;
36  import org.mortbay.log.Log;
37  
38  
39  
40  /**
41   * DataSourceUserRealm
42   *
43   * Obtain user/password/role information from a database
44   * via jndi DataSource.
45   */
46  public class DataSourceUserRealm extends HashUserRealm
47  {
48      private String _jndiName = "javax.sql.DataSource/default";
49      private DataSource _datasource;
50      private Server _server;
51      private String _userTableName = "users";
52      private String _userTableKey = "id";
53      private String _userTableUserField = "username";
54      private String _userTablePasswordField = "pwd";
55      private String _roleTableName = "roles";
56      private String _roleTableKey = "id";
57      private String _roleTableRoleField = "role";
58      private String _userRoleTableName = "user_roles";
59      private String _userRoleTableUserKey = "user_id";
60      private String _userRoleTableRoleKey = "role_id";
61      private int _cacheMs = 30000;
62      private long _lastHashPurge = 0;
63      private String _userSql;
64      private String _roleSql;
65      
66  
67  
68      public DataSourceUserRealm (String jndiName)
69      {
70          _jndiName=jndiName;
71      }
72      
73      
74      public DataSourceUserRealm()
75      {
76      }
77      
78      
79      public void setServer (Server server)
80      {
81          _server=server;
82      }
83      
84      public Server getServer()
85      {
86          return _server;
87      }
88      
89      public void setUserTableName (String name)
90      {
91          _userTableName=name;
92      }
93      
94      public String getUserTableName()
95      {
96          return _userTableName;
97      }
98      
99      public String getUserTableKey()
100     {
101         return _userTableKey;
102     }
103 
104 
105     public void setUserTableKey(String tableKey)
106     {
107         _userTableKey = tableKey;
108     }
109 
110 
111     public String getUserTableUserField()
112     {
113         return _userTableUserField;
114     }
115 
116 
117     public void setUserTableUserField(String tableUserField)
118     {
119         _userTableUserField = tableUserField;
120     }
121 
122 
123     public String getUserTablePasswordField()
124     {
125         return _userTablePasswordField;
126     }
127 
128 
129     public void setUserTablePasswordField(String tablePasswordField)
130     {
131         _userTablePasswordField = tablePasswordField;
132     }
133 
134 
135     public String getRoleTableName()
136     {
137         return _roleTableName;
138     }
139 
140 
141     public void setRoleTableName(String tableName)
142     {
143         _roleTableName = tableName;
144     }
145 
146 
147     public String getRoleTableKey()
148     {
149         return _roleTableKey;
150     }
151 
152 
153     public void setRoleTableKey(String tableKey)
154     {
155         _roleTableKey = tableKey;
156     }
157 
158 
159     public String getRoleTableRoleField()
160     {
161         return _roleTableRoleField;
162     }
163 
164 
165     public void setRoleTableRoleField(String tableRoleField)
166     {
167         _roleTableRoleField = tableRoleField;
168     }
169 
170 
171     public String getUserRoleTableName()
172     {
173         return _userRoleTableName;
174     }
175 
176 
177     public void setUserRoleTableName(String roleTableName)
178     {
179         _userRoleTableName = roleTableName;
180     }
181 
182 
183     public String getUserRoleTableUserKey()
184     {
185         return _userRoleTableUserKey;
186     }
187 
188 
189     public void setUserRoleTableUserKey(String roleTableUserKey)
190     {
191         _userRoleTableUserKey = roleTableUserKey;
192     }
193 
194 
195     public String getUserRoleTableRoleKey()
196     {
197         return _userRoleTableRoleKey;
198     }
199 
200 
201     public void setUserRoleTableRoleKey(String roleTableRoleKey)
202     {
203         _userRoleTableRoleKey = roleTableRoleKey;
204     }
205 
206     public void setCacheMs (int ms)
207     {
208         _cacheMs=ms;
209     }
210     
211     public int getCacheMs ()
212     {
213         return _cacheMs;
214     }
215     
216     /* ------------------------------------------------------------ */
217     /** 
218      * Check if user is authentic
219      * 
220      * @see org.mortbay.jetty.security.HashUserRealm#authenticate(java.lang.String, java.lang.Object, org.mortbay.jetty.Request)
221      */
222     public Principal authenticate(String username,
223             Object credentials,
224             Request request)
225     {
226         synchronized (this)
227         {
228             long now = System.currentTimeMillis();
229             if (now - _lastHashPurge > _cacheMs || _cacheMs == 0)
230             {
231                 _users.clear();
232                 _roles.clear();
233                 _lastHashPurge = now;
234             }
235             Principal user = super.getPrincipal(username);
236             if (user == null)
237             {
238                 loadUser(username);
239                 user = super.getPrincipal(username);
240             }
241         }
242         return super.authenticate(username, credentials, request);
243     }
244 
245     /* ------------------------------------------------------------ */
246     /** Check if a user is in a role.
247      * @param user The user, which must be from this realm 
248      * @param roleName 
249      * @return True if the user can act in the role.
250      */
251     public synchronized boolean isUserInRole(Principal user, String roleName)
252     {
253         if(super.getPrincipal(user.getName())==null)
254             loadUser(user.getName());
255         return super.isUserInRole(user, roleName);
256     }
257 
258     /* ------------------------------------------------------------ */
259     /** Load database configuration from properties file.
260      * This is really here to satisfy the HashUserRealm interface.
261      * Setters should be used instead.
262      *     
263      * @exception IOException 
264      */
265     protected void loadConfig()
266     throws IOException
267     {        
268         Properties properties = new Properties();
269 
270         properties.load(getConfigResource().getInputStream());
271 
272         _jndiName = properties.getProperty("jndiname");
273         setUserTableName(properties.getProperty("usertable"));
274         setUserTableKey(properties.getProperty("usertablekey"));
275         setUserTableUserField(properties.getProperty("usertableuserfield"));
276         setUserTablePasswordField(properties.getProperty("usertablepasswordfield"));
277         setRoleTableName(properties.getProperty("roletable"));
278         setRoleTableKey(properties.getProperty("roletablekey"));
279         setRoleTableRoleField(properties.getProperty("roletablerolefield"));
280         setUserRoleTableName(properties.getProperty("userroletable"));
281         setUserRoleTableUserKey(properties.getProperty("userroletableuserkey"));
282         setUserRoleTableRoleKey(properties.getProperty("userroletablerolekey"));
283         // default cachetime = 30s
284         String cacheSec = properties.getProperty("cachetime");
285         if (cacheSec != null)
286             setCacheMs(new Integer(cacheSec).intValue() * 1000);
287 
288         if (_jndiName == null || _cacheMs < 0)
289         {
290             if(Log.isDebugEnabled())Log.debug("UserRealm " + getName()
291                     + " has not been properly configured");
292         }
293     }
294 
295     /* ------------------------------------------------------------ */
296     /** Load user's info from database.
297      * 
298      * @param user
299      */
300     private void loadUser (String user)
301     {
302         Connection connection = null;
303         try
304         {        
305             initDb();
306             connection = getConnection();
307             
308             PreparedStatement statement = connection.prepareStatement(_userSql);
309             statement.setObject(1, user);
310             ResultSet rs = statement.executeQuery();
311     
312             if (rs.next())
313             {
314                 int key = rs.getInt(_userTableKey);
315                 put(user, rs.getString(_userTablePasswordField));
316                 statement.close();
317                 
318                 statement = connection.prepareStatement(_roleSql);
319                 statement.setInt(1, key);
320                 rs = statement.executeQuery();
321 
322                 while (rs.next())
323                     addUserToRole(user, rs.getString(_roleTableRoleField));
324                 
325                 statement.close();
326             }
327         }
328         catch (NamingException e)
329         {
330             Log.warn("No datasource for "+_jndiName, e);
331         }
332         catch (SQLException e)
333         {
334             Log.warn("Problem loading user info for "+user, e);
335         }
336         finally
337         {
338             if (connection != null)
339             {
340                 try
341                 {
342                     connection.close();
343                 }
344                 catch (SQLException x)
345                 {
346                     Log.warn("Problem closing connection", x);
347                 }
348                 finally
349                 {
350                     connection = null;
351                 }
352             }
353         }
354     }
355    
356     /* ------------------------------------------------------------ */
357     /**
358      * Lookup the datasource for the jndiName and formulate the
359      * necessary sql query strings based on the configured table
360      * and column names.
361      * 
362      * @throws NamingException
363      */
364     private void initDb() throws NamingException
365     {
366         if (_datasource != null)
367             return;
368         
369         InitialContext ic = new InitialContext();
370         //try finding the datasource in the Server scope
371         try
372         {
373            _datasource = (DataSource)NamingEntryUtil.lookup(_server, _jndiName);
374         }
375         catch (NameNotFoundException e)
376         {
377             //next try the jvm scope
378         }
379 
380         //try finding the datasource in the jvm scope
381         if (_datasource==null)
382         {
383             _datasource = (DataSource)NamingEntryUtil.lookup(null, _jndiName);
384         }
385 
386         _userSql = "select " + _userTableKey + "," + _userTablePasswordField 
387                   + " from " + _userTableName 
388                   + " where "+ _userTableUserField + " = ?";
389         
390         _roleSql = "select r." + _roleTableRoleField
391                   + " from " + _roleTableName + " r, " + _userRoleTableName 
392                   + " u where u."+ _userRoleTableUserKey + " = ?"
393                   + " and r." + _roleTableKey + " = u." + _userRoleTableRoleKey;
394     }
395     
396     private Connection getConnection () 
397     throws NamingException, SQLException
398     {
399         initDb();
400         return _datasource.getConnection();
401     }
402 
403 }