View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  
20  package org.eclipse.jetty.plus.security;
21  
22  import java.sql.Connection;
23  import java.sql.DatabaseMetaData;
24  import java.sql.PreparedStatement;
25  import java.sql.ResultSet;
26  import java.sql.SQLException;
27  import java.sql.Statement;
28  import java.util.ArrayList;
29  import java.util.List;
30  import java.util.Locale;
31  
32  import javax.naming.InitialContext;
33  import javax.naming.NameNotFoundException;
34  import javax.naming.NamingException;
35  import javax.servlet.ServletRequest;
36  import javax.sql.DataSource;
37  
38  import org.eclipse.jetty.plus.jndi.NamingEntryUtil;
39  import org.eclipse.jetty.security.IdentityService;
40  import org.eclipse.jetty.security.MappedLoginService;
41  import org.eclipse.jetty.server.Server;
42  import org.eclipse.jetty.server.UserIdentity;
43  import org.eclipse.jetty.util.log.Log;
44  import org.eclipse.jetty.util.log.Logger;
45  import org.eclipse.jetty.util.security.Credential;
46  
47  
48  /**
49   * DataSourceUserRealm
50   * <p>
51   * Obtain user/password/role information from a database
52   * via jndi DataSource.
53   */
54  public class DataSourceLoginService extends MappedLoginService
55  {
56      private static final Logger LOG = Log.getLogger(DataSourceLoginService.class);
57  
58      private String _jndiName = "javax.sql.DataSource/default";
59      private DataSource _datasource;
60      private Server _server;
61      private String _userTableName = "users";
62      private String _userTableKey = "id";
63      private String _userTableUserField = "username";
64      private String _userTablePasswordField = "pwd";
65      private String _roleTableName = "roles";
66      private String _roleTableKey = "id";
67      private String _roleTableRoleField = "role";
68      private String _userRoleTableName = "user_roles";
69      private String _userRoleTableUserKey = "user_id";
70      private String _userRoleTableRoleKey = "role_id";
71      private int _cacheMs = 30000;
72      private long _lastPurge = 0;
73      private String _userSql;
74      private String _roleSql;
75      private boolean _createTables = false;
76      
77      
78      /**
79       * DBUser
80       */
81      public class DBUser extends KnownUser
82      {
83          private int _key;
84          
85          public DBUser(String name, Credential credential, int key)
86          {
87              super(name, credential);
88              _key = key;
89          }
90          
91          public int getKey ()
92          {
93              return _key;
94          }
95          
96      }
97  
98      /* ------------------------------------------------------------ */
99      public DataSourceLoginService()
100     {
101     }
102 
103     /* ------------------------------------------------------------ */
104     public DataSourceLoginService(String name)
105     {
106         setName(name);
107     }
108 
109     /* ------------------------------------------------------------ */
110     public DataSourceLoginService(String name, IdentityService identityService)
111     {
112         setName(name);
113         setIdentityService(identityService);
114     }
115 
116     /* ------------------------------------------------------------ */
117     public void setJndiName (String jndi)
118     {
119         _jndiName = jndi;
120     }
121 
122     /* ------------------------------------------------------------ */
123     public String getJndiName ()
124     {
125         return _jndiName;
126     }
127 
128     /* ------------------------------------------------------------ */
129     public void setServer (Server server)
130     {
131         _server=server;
132     }
133 
134     /* ------------------------------------------------------------ */
135     public Server getServer()
136     {
137         return _server;
138     }
139 
140     /* ------------------------------------------------------------ */
141     public void setCreateTables(boolean createTables)
142     {
143         _createTables = createTables;
144     }
145 
146     /* ------------------------------------------------------------ */
147     public boolean getCreateTables()
148     {
149         return _createTables;
150     }
151 
152     /* ------------------------------------------------------------ */
153     public void setUserTableName (String name)
154     {
155         _userTableName=name;
156     }
157 
158     /* ------------------------------------------------------------ */
159     public String getUserTableName()
160     {
161         return _userTableName;
162     }
163 
164     /* ------------------------------------------------------------ */
165     public String getUserTableKey()
166     {
167         return _userTableKey;
168     }
169 
170 
171     /* ------------------------------------------------------------ */
172     public void setUserTableKey(String tableKey)
173     {
174         _userTableKey = tableKey;
175     }
176 
177 
178     /* ------------------------------------------------------------ */
179     public String getUserTableUserField()
180     {
181         return _userTableUserField;
182     }
183 
184 
185     /* ------------------------------------------------------------ */
186     public void setUserTableUserField(String tableUserField)
187     {
188         _userTableUserField = tableUserField;
189     }
190 
191 
192     /* ------------------------------------------------------------ */
193     public String getUserTablePasswordField()
194     {
195         return _userTablePasswordField;
196     }
197 
198 
199     /* ------------------------------------------------------------ */
200     public void setUserTablePasswordField(String tablePasswordField)
201     {
202         _userTablePasswordField = tablePasswordField;
203     }
204 
205 
206     /* ------------------------------------------------------------ */
207     public String getRoleTableName()
208     {
209         return _roleTableName;
210     }
211 
212 
213     /* ------------------------------------------------------------ */
214     public void setRoleTableName(String tableName)
215     {
216         _roleTableName = tableName;
217     }
218 
219 
220     /* ------------------------------------------------------------ */
221     public String getRoleTableKey()
222     {
223         return _roleTableKey;
224     }
225 
226 
227     /* ------------------------------------------------------------ */
228     public void setRoleTableKey(String tableKey)
229     {
230         _roleTableKey = tableKey;
231     }
232 
233 
234     /* ------------------------------------------------------------ */
235     public String getRoleTableRoleField()
236     {
237         return _roleTableRoleField;
238     }
239 
240 
241     /* ------------------------------------------------------------ */
242     public void setRoleTableRoleField(String tableRoleField)
243     {
244         _roleTableRoleField = tableRoleField;
245     }
246 
247 
248     /* ------------------------------------------------------------ */
249     public String getUserRoleTableName()
250     {
251         return _userRoleTableName;
252     }
253 
254 
255     /* ------------------------------------------------------------ */
256     public void setUserRoleTableName(String roleTableName)
257     {
258         _userRoleTableName = roleTableName;
259     }
260 
261 
262     /* ------------------------------------------------------------ */
263     public String getUserRoleTableUserKey()
264     {
265         return _userRoleTableUserKey;
266     }
267 
268 
269     /* ------------------------------------------------------------ */
270     public void setUserRoleTableUserKey(String roleTableUserKey)
271     {
272         _userRoleTableUserKey = roleTableUserKey;
273     }
274 
275 
276     /* ------------------------------------------------------------ */
277     public String getUserRoleTableRoleKey()
278     {
279         return _userRoleTableRoleKey;
280     }
281 
282 
283     /* ------------------------------------------------------------ */
284     public void setUserRoleTableRoleKey(String roleTableRoleKey)
285     {
286         _userRoleTableRoleKey = roleTableRoleKey;
287     }
288 
289     /* ------------------------------------------------------------ */
290     public void setCacheMs (int ms)
291     {
292         _cacheMs=ms;
293     }
294 
295     /* ------------------------------------------------------------ */
296     public int getCacheMs ()
297     {
298         return _cacheMs;
299     }
300 
301     /* ------------------------------------------------------------ */
302     @Override
303     protected void loadUsers()
304     {
305     }
306     
307  
308     
309     /* ------------------------------------------------------------ */
310     /** Load user's info from database.
311      *
312      * @param userName the user name
313      */
314     @Deprecated
315     protected UserIdentity loadUser (String userName)
316     {
317         try
318         {
319             try (Connection connection = getConnection();
320                     PreparedStatement statement1 = connection.prepareStatement(_userSql))
321             {
322                 statement1.setObject(1, userName);
323                 try (ResultSet rs1 = statement1.executeQuery())
324                 {
325                     if (rs1.next())
326                     {
327                         int key = rs1.getInt(_userTableKey);
328                         String credentials = rs1.getString(_userTablePasswordField);
329                        
330                             List<String> roles = new ArrayList<String>();
331                             try (PreparedStatement statement2 = connection.prepareStatement(_roleSql))
332                             {
333                                 statement2.setInt(1, key);
334                                 try (ResultSet rs2 = statement2.executeQuery())
335                                 {
336                                     while (rs2.next())
337                                     {
338                                         roles.add(rs2.getString(_roleTableRoleField));
339                                     }
340                                 }
341                             }
342                             return putUser(userName,  Credential.getCredential(credentials), roles.toArray(new String[roles.size()]));
343                     }
344                 }
345             }
346         }
347         catch (NamingException e)
348         {
349             LOG.warn("No datasource for "+_jndiName, e);
350         }
351         catch (SQLException e)
352         {
353             LOG.warn("Problem loading user info for "+userName, e);
354         }
355         return null;
356     }
357     
358     
359     /** 
360      * @see org.eclipse.jetty.security.MappedLoginService#loadUserInfo(java.lang.String)
361      */
362     public KnownUser loadUserInfo (String username)
363     {
364         try
365         {
366             try (Connection connection = getConnection();
367                     PreparedStatement statement1 = connection.prepareStatement(_userSql))
368             {
369                 statement1.setObject(1, username);
370                 try (ResultSet rs1 = statement1.executeQuery())
371                 {
372                     if (rs1.next())
373                     {
374                         int key = rs1.getInt(_userTableKey);
375                         String credentials = rs1.getString(_userTablePasswordField);
376                         
377                         return new DBUser(username, Credential.getCredential(credentials), key);
378                     }
379                 }
380             }
381         }
382         catch (NamingException e)
383         {
384             LOG.warn("No datasource for "+_jndiName, e);
385         }
386         catch (SQLException e)
387         {
388             LOG.warn("Problem loading user info for "+username, e);
389         }
390         return null;
391     }
392     
393     /** 
394      * @see org.eclipse.jetty.security.MappedLoginService#loadRoleInfo(org.eclipse.jetty.security.MappedLoginService.KnownUser)
395      */
396     public String[] loadRoleInfo (KnownUser user)
397     {
398         DBUser dbuser = (DBUser)user;
399 
400         try
401         {
402             try (Connection connection = getConnection();
403                     PreparedStatement statement2 = connection.prepareStatement(_roleSql))
404             {
405 
406                 List<String> roles = new ArrayList<String>();
407 
408                 statement2.setInt(1, dbuser.getKey());
409                 try (ResultSet rs2 = statement2.executeQuery())
410                 {
411                     while (rs2.next())
412                     {
413                         roles.add(rs2.getString(_roleTableRoleField));
414                     }
415                     
416                     return roles.toArray(new String[roles.size()]);
417                 }
418             }
419         }
420         catch (NamingException e)
421         {
422             LOG.warn("No datasource for "+_jndiName, e);
423         }
424         catch (SQLException e)
425         {
426             LOG.warn("Problem loading user info for "+user.getName(), e);
427         }
428         return null;
429     }
430     
431     /* ------------------------------------------------------------ */
432     @Override
433     public UserIdentity login(String username, Object credentials, ServletRequest request)
434     {
435         long now = System.currentTimeMillis();
436         if (now - _lastPurge > _cacheMs || _cacheMs == 0)
437         {
438             _users.clear();
439             _lastPurge = now;
440         }
441  
442         return super.login(username,credentials, request);
443     }
444 
445     /* ------------------------------------------------------------ */
446     /**
447      * Lookup the datasource for the jndiName and formulate the
448      * necessary sql query strings based on the configured table
449      * and column names.
450      *
451      * @throws NamingException if unable to init jndi
452      * @throws SQLException if unable to init database
453      */
454     public void initDb() throws NamingException, SQLException
455     {
456         if (_datasource != null)
457             return;
458 
459         @SuppressWarnings("unused")
460         InitialContext ic = new InitialContext();
461         assert ic!=null;
462 
463         // TODO Should we try webapp scope too?
464 
465         // try finding the datasource in the Server scope
466         if (_server != null)
467         {
468             try
469             {
470                 _datasource = (DataSource)NamingEntryUtil.lookup(_server, _jndiName);
471             }
472             catch (NameNotFoundException e)
473             {
474                 //next try the jvm scope
475             }
476         }
477 
478 
479         //try finding the datasource in the jvm scope
480         if (_datasource==null)
481         {
482             _datasource = (DataSource)NamingEntryUtil.lookup(null, _jndiName);
483         }
484 
485         // set up the select statements based on the table and column names configured
486         _userSql = "select " + _userTableKey + "," + _userTablePasswordField
487                   + " from " + _userTableName
488                   + " where "+ _userTableUserField + " = ?";
489 
490         _roleSql = "select r." + _roleTableRoleField
491                   + " from " + _roleTableName + " r, " + _userRoleTableName
492                   + " u where u."+ _userRoleTableUserKey + " = ?"
493                   + " and r." + _roleTableKey + " = u." + _userRoleTableRoleKey;
494 
495         prepareTables();
496     }
497 
498     private void prepareTables()
499     throws NamingException, SQLException
500     {
501         if (_createTables)
502         {
503             boolean autocommit = true;
504             Connection connection = getConnection();
505             try (Statement stmt = connection.createStatement())
506             {
507                 autocommit = connection.getAutoCommit();
508                 connection.setAutoCommit(false);
509                 DatabaseMetaData metaData = connection.getMetaData();
510 
511                 //check if tables exist
512                 String tableName = (metaData.storesLowerCaseIdentifiers()? _userTableName.toLowerCase(Locale.ENGLISH): (metaData.storesUpperCaseIdentifiers()?_userTableName.toUpperCase(Locale.ENGLISH): _userTableName));
513                 try (ResultSet result = metaData.getTables(null, null, tableName, null))
514                 {
515                     if (!result.next())
516                     {
517                         //user table default
518                         /*
519                          * create table _userTableName (_userTableKey integer,
520                          * _userTableUserField varchar(100) not null unique,
521                          * _userTablePasswordField varchar(20) not null, primary key(_userTableKey));
522                          */
523                         stmt.executeUpdate("create table "+_userTableName+ "("+_userTableKey+" integer,"+
524                                 _userTableUserField+" varchar(100) not null unique,"+
525                                 _userTablePasswordField+" varchar(20) not null, primary key("+_userTableKey+"))");
526                         if (LOG.isDebugEnabled()) LOG.debug("Created table "+_userTableName);
527                     }
528                 }
529 
530                 tableName = (metaData.storesLowerCaseIdentifiers()? _roleTableName.toLowerCase(Locale.ENGLISH): (metaData.storesUpperCaseIdentifiers()?_roleTableName.toUpperCase(Locale.ENGLISH): _roleTableName));
531                 try (ResultSet result = metaData.getTables(null, null, tableName, null))
532                 {
533                     if (!result.next())
534                     {
535                         //role table default
536                         /*
537                          * create table _roleTableName (_roleTableKey integer,
538                          * _roleTableRoleField varchar(100) not null unique, primary key(_roleTableKey));
539                          */
540                         String str = "create table "+_roleTableName+" ("+_roleTableKey+" integer, "+
541                         _roleTableRoleField+" varchar(100) not null unique, primary key("+_roleTableKey+"))";
542                         stmt.executeUpdate(str);
543                         if (LOG.isDebugEnabled()) LOG.debug("Created table "+_roleTableName);
544                     }
545                 }
546 
547                 tableName = (metaData.storesLowerCaseIdentifiers()? _userRoleTableName.toLowerCase(Locale.ENGLISH): (metaData.storesUpperCaseIdentifiers()?_userRoleTableName.toUpperCase(Locale.ENGLISH): _userRoleTableName));
548                 try (ResultSet result = metaData.getTables(null, null, tableName, null))
549                 {
550                     if (!result.next())
551                     {
552                         //user-role table
553                         /*
554                          * create table _userRoleTableName (_userRoleTableUserKey integer,
555                          * _userRoleTableRoleKey integer,
556                          * primary key (_userRoleTableUserKey, _userRoleTableRoleKey));
557                          *
558                          * create index idx_user_role on _userRoleTableName (_userRoleTableUserKey);
559                          */
560                         stmt.executeUpdate("create table "+_userRoleTableName+" ("+_userRoleTableUserKey+" integer, "+
561                                 _userRoleTableRoleKey+" integer, "+
562                                 "primary key ("+_userRoleTableUserKey+", "+_userRoleTableRoleKey+"))");
563                         stmt.executeUpdate("create index indx_user_role on "+_userRoleTableName+"("+_userRoleTableUserKey+")");
564                         if (LOG.isDebugEnabled()) LOG.debug("Created table "+_userRoleTableName +" and index");
565                     }
566                 }
567                 connection.commit();
568             }
569             finally
570             {
571                 try
572                 {
573                     connection.setAutoCommit(autocommit);
574                 }
575                 catch (SQLException e)
576                 {
577                     if (LOG.isDebugEnabled()) LOG.debug("Prepare tables", e);
578                 }
579                 finally
580                 {
581                     try
582                     {
583                         connection.close();
584                     }
585                     catch (SQLException e)
586                     {
587                         if (LOG.isDebugEnabled()) LOG.debug("Prepare tables", e);
588                     }
589                 }
590             }
591         }
592         else if (LOG.isDebugEnabled())
593         {
594             LOG.debug("createTables false");
595         }
596     }
597 
598     private Connection getConnection ()
599     throws NamingException, SQLException
600     {
601         initDb();
602         return _datasource.getConnection();
603     }
604 }