1   /*
2    *  AccessControllerImpl.java
3    *
4    *  Copyright (c) 1998-2005, The University of Sheffield.
5    *
6    *  This file is part of GATE (see http://gate.ac.uk/), and is free
7    *  software, licenced under the GNU Library General Public License,
8    *  Version 2, June 1991 (in the distribution as file licence.html,
9    *  and also available at http://gate.ac.uk/gate/licence.html).
10   *
11   *  Marin Dimitrov, 19/Sep/2001
12   *
13   *  $Id: AccessControllerImpl.java,v 1.53 2005/01/11 13:51:36 ian Exp $
14   */
15  
16  package gate.security;
17  
18  import java.sql.*;
19  import java.util.*;
20  
21  import junit.framework.Assert;
22  
23  import gate.Gate;
24  import gate.event.*;
25  import gate.persist.DBHelper;
26  import gate.persist.PersistenceException;
27  import gate.util.MethodNotImplementedException;
28  
29  
30  public class AccessControllerImpl
31    implements AccessController, ObjectModificationListener {
32  
33    public static final int DEFAULT_SESSION_TIMEOUT_MIN = 4*60;
34  
35    public static final int LOGIN_OK = 1;
36    public static final int LOGIN_FAILED = 2;
37  
38    private static long MY_VERY_SECRET_CONSTANT;
39    private static final int RANDOM_MAX = 1024;
40  
41    private HashMap     sessions;
42    private HashMap     sessionLastUsed;
43    private HashMap     sessionTimeouts;
44  
45    private Connection  jdbcConn;
46    private String      jdbcURL;
47    private String      jdbcSchema;
48    protected int       dbType;
49  
50    private HashMap     usersByID;
51    private HashMap     usersByName;
52  
53    private HashMap     groupsByID;
54    private HashMap     groupsByName;
55  
56    private static Random r;
57    private boolean isPooled;
58  
59    private int refCnt;
60  
61    /** --- */
62    private Vector omModificationListeners;
63    /** --- */
64    private Vector omCreationListeners;
65    /** --- */
66    private Vector omDeletionListeners;
67  
68  
69    static {
70      r = new Random();
71      MY_VERY_SECRET_CONSTANT = r.nextInt(RANDOM_MAX) * r.nextInt(RANDOM_MAX)
72                                    + Math.round(Math.PI * Math.E);
73    }
74  
75    /** --- */
76    public AccessControllerImpl(String jdbcURL) {
77  
78      Assert.assertNotNull(jdbcURL);
79  
80      this.refCnt = 0;
81      this.jdbcURL = jdbcURL;
82      this.jdbcSchema = DBHelper.getSchemaPrefix(this.jdbcURL);
83      this.dbType = DBHelper.getDatabaseType(this.jdbcURL);
84  
85      Assert.assertNotNull(this.jdbcSchema);
86      Assert.assertTrue(this.dbType == DBHelper.ORACLE_DB ||
87                        this.dbType == DBHelper.POSTGRES_DB);
88  
89      sessions = new HashMap();
90      sessionLastUsed = new HashMap();
91      sessionTimeouts = new HashMap();
92  
93      usersByID =  new HashMap();
94      usersByName=  new HashMap();
95  
96      groupsByID = new HashMap();
97      groupsByName = new HashMap();
98  
99      this.omModificationListeners = new Vector();
100     this.omCreationListeners = new Vector();
101     this.omDeletionListeners = new Vector();
102   }
103 
104   /** --- */
105   public void open()
106     throws PersistenceException{
107 
108     synchronized(this) {
109       if (refCnt++ == 0) {
110         //open connection
111         try {
112           //1. get connection to the database
113           jdbcConn = DBHelper.connect(this.jdbcURL);
114 
115           Assert.assertNotNull(jdbcConn);
116 
117           //2. initialize group/user collections
118           //init, i.e. read users and groups from DB
119           init();
120         }
121         catch(SQLException sqle) {
122           throw new PersistenceException("could not get DB connection ["+ sqle.getMessage() +"]");
123         }
124         catch(ClassNotFoundException clse) {
125           throw new PersistenceException("cannot locate JDBC driver ["+ clse.getMessage() +"]");
126         }
127       }
128     }
129 
130 
131   }
132 
133   /** --- */
134   public void close()
135     throws PersistenceException{
136 
137     if (--this.refCnt == 0) {
138 
139       //0. Invalidate all sessions
140       this.sessions.clear();
141       this.sessionLastUsed.clear();
142       this.sessionTimeouts.clear();
143 
144       //1. deregister self as listener for groups
145       Set groupMappings = this.groupsByName.entrySet();
146       Iterator itGroups = groupMappings.iterator();
147 
148       while (itGroups.hasNext()) {
149         Map.Entry mapEntry = (Map.Entry)itGroups.next();
150         GroupImpl  grp = (GroupImpl)mapEntry.getValue();
151         grp.unregisterObjectModificationListener(this,
152                                                ObjectModificationEvent.OBJECT_MODIFIED);
153       }
154 
155       //1.1. deregister self as listener for users
156       Set userMappings = this.usersByName.entrySet();
157       Iterator itUsers = userMappings.iterator();
158 
159       while (itUsers.hasNext()) {
160         Map.Entry mapEntry = (Map.Entry)itUsers.next();
161         UserImpl  usr = (UserImpl)mapEntry.getValue();
162         usr.unregisterObjectModificationListener(this,
163                                              ObjectModificationEvent.OBJECT_MODIFIED);
164       }
165 
166       //1.2 release all listeners registered for this object
167       this.omCreationListeners.removeAllElements();
168       this.omDeletionListeners.removeAllElements();
169       this.omModificationListeners.removeAllElements();
170 
171       //2. delete all groups/users collections
172       this.groupsByID.clear();
173       this.groupsByName.clear();
174       this.usersByID.clear();
175       this.groupsByName.clear();
176 
177       //3.close connection (if not pooled)
178       try {
179         if (false == this.isPooled) {
180           this.jdbcConn.close();
181         }
182       }
183       catch(SQLException sqle) {
184         throw new PersistenceException("can't close connection to DB:["+
185                                         sqle.getMessage()+"]");
186       }
187     }
188   }
189 
190   /** --- */
191   public Group findGroup(String name)
192     throws PersistenceException,SecurityException{
193 
194     Group grp = (Group)this.groupsByName.get(name);
195 
196     if (null == grp) {
197       throw new SecurityException("No such group");
198     }
199 
200     return grp;
201   }
202 
203   /** --- */
204   public Group findGroup(Long id)
205     throws PersistenceException,SecurityException {
206 
207     Group grp = (Group)this.groupsByID.get(id);
208 
209     if (null == grp) {
210       throw new SecurityException("No such group");
211     }
212 
213     return grp;
214   }
215 
216   /** --- */
217   public User findUser(String name)
218     throws PersistenceException,SecurityException {
219 
220     User usr = (User)this.usersByName.get(name);
221 
222     if (null == usr) {
223       throw new SecurityException("No such user (" + name + ")");
224     }
225 
226     return usr;
227   }
228 
229   /** --- */
230   public User findUser(Long id)
231     throws PersistenceException,SecurityException {
232 
233     User usr = (User)this.usersByID.get(id);
234 
235     if (null == usr) {
236       throw new SecurityException("No such user");
237     }
238 
239     return usr;
240   }
241 
242   /** --- */
243   public Session findSession(Long id)
244     throws SecurityException {
245 
246     Session s = (Session)this.sessions.get(id);
247 
248     if (null==s) {
249       throw new SecurityException("No such session ID!");
250     }
251 
252     return s;
253   }
254 
255   /** --- */
256   public Group createGroup(String name,Session s)
257     throws PersistenceException, SecurityException {
258 
259     Assert.assertNotNull(name);
260 
261     //-1. check session
262     if (false == isValidSession(s)) {
263       throw new SecurityException("invalid session supplied");
264     }
265 
266     //0. check privileges
267     if (false == s.isPrivilegedSession()) {
268       throw new SecurityException("insufficient privileges");
269     }
270 
271 
272     //1. create group in DB
273     CallableStatement stmt = null;
274     PreparedStatement pstmt = null;
275     ResultSet rs = null;
276     Long new_id;
277 
278     //Oracle / Postgres ?
279 
280     if (this.dbType == DBHelper.ORACLE_DB) {
281 
282       try {
283         stmt = this.jdbcConn.prepareCall(
284                 "{ call "+Gate.DB_OWNER+".security.create_group(?,?)} ");
285         stmt.setString(1,name);
286         //numbers generated from Oracle sequences are BIGINT
287         stmt.registerOutParameter(2,java.sql.Types.BIGINT);
288         stmt.execute();
289         new_id = new Long(stmt.getLong(2));
290       }
291       catch(SQLException sqle) {
292 
293         //check for more specifi exceptions
294         switch(sqle.getErrorCode()) {
295 
296           case DBHelper.X_ORACLE_DUPLICATE_GROUP_NAME:
297             throw new PersistenceException(
298                   "can't create a group in DB, name is not unique: ["
299                   + sqle.getMessage()+"]");
300 
301           default:
302             throw new PersistenceException(
303                 "can't create a group in DB: ["+ sqle.getMessage()+"]");
304         }
305       }
306       finally {
307         DBHelper.cleanup(stmt);
308       }
309     }
310     else if (this.dbType == DBHelper.POSTGRES_DB) {
311       try {
312         String sql = "select security_create_group(?) ";
313         pstmt = this.jdbcConn.prepareStatement(sql);
314         pstmt.setString(1,name);
315         pstmt.execute();
316         rs = pstmt.getResultSet();
317 
318         if (false == rs.next()) {
319           throw new PersistenceException("empty resultset");
320         }
321 
322         new_id = new Long(rs.getLong(1));
323       }
324       catch(SQLException sqle) {
325 
326         //check for more specifi exceptions
327         switch(sqle.getErrorCode()) {
328 
329           case DBHelper.X_ORACLE_DUPLICATE_GROUP_NAME:
330             throw new PersistenceException(
331                   "can't create a group in DB, name is not unique: ["
332                   + sqle.getMessage()+"]");
333 
334           default:
335             throw new PersistenceException(
336                 "can't create a group in DB: ["+ sqle.getMessage()+"]");
337         }
338       }
339       finally {
340         DBHelper.cleanup(rs);
341         DBHelper.cleanup(pstmt);
342       }
343 
344     }
345     else {
346       throw new IllegalArgumentException();
347     }
348 
349     //2. create GroupImpl for the new group and
350     // users list is empty
351     GroupImpl grp = new GroupImpl(new_id,name,new Vector(),this,this.jdbcConn);
352 
353     //3. register as objectModification listener for this group
354     //we care only about name changes
355     grp.registerObjectModificationListener(this,ObjectModificationEvent.OBJECT_MODIFIED);
356 
357     //4.put in collections
358     this.groupsByID.put(new_id,grp);
359     this.groupsByName.put(name,grp);
360 
361     return grp;
362   }
363 
364   /** --- */
365   public void deleteGroup(Long id, Session s)
366     throws PersistenceException,SecurityException {
367 
368     Group grp = (Group)this.groupsByID.get(id);
369     if (null == grp) {
370       throw new SecurityException("incorrect group id supplied ( id = ["+id+"])");
371     }
372 
373     //delegate
374     deleteGroup(grp,s);
375   }
376 
377   /** --- */
378   public void deleteGroup(Group grp, Session s)
379     throws PersistenceException,SecurityException {
380 
381     //1. check session
382     if (false == isValidSession(s)) {
383       throw new SecurityException("invalid session supplied");
384     }
385 
386     //2. check privileges
387     if (false == s.isPrivilegedSession()) {
388       throw new SecurityException("insufficient privileges");
389     }
390 
391     //3. delete in DB
392     CallableStatement stmt = null;
393     PreparedStatement pstmt = null;
394 
395     //Oracle /Postgres ?
396     if (this.dbType == DBHelper.ORACLE_DB) {
397 
398       try {
399         stmt = this.jdbcConn.prepareCall(
400                   "{ call "+Gate.DB_OWNER+".security.delete_group(?) } ");
401         stmt.setLong(1,grp.getID().longValue());
402         stmt.execute();
403       }
404       catch(SQLException sqle) {
405         //check for more specific exceptions
406         switch(sqle.getErrorCode()) {
407 
408           case DBHelper.X_ORACLE_GROUP_OWNS_RESOURCES:
409             throw new PersistenceException(
410                   "can't delete a group from DB, the group owns LR(s): ["
411                     + sqle.getMessage()+"]");
412 
413           default:
414             throw new PersistenceException(
415                   "can't delete a group from DB: ["+ sqle.getMessage()+"]");
416         }
417       }
418       finally {
419         DBHelper.cleanup(stmt);
420       }
421     }
422 
423     else if (this.dbType == DBHelper.POSTGRES_DB) {
424 
425       try {
426         String sql = "select security_delete_group(?)";
427         pstmt = this.jdbcConn.prepareStatement(sql);
428         pstmt.setLong(1,grp.getID().longValue());
429         pstmt.execute();
430       }
431       catch(SQLException sqle) {
432         //check for more specific exceptions
433         switch(sqle.getErrorCode()) {
434 
435           case DBHelper.X_ORACLE_GROUP_OWNS_RESOURCES:
436             throw new PersistenceException(
437                   "can't delete a group from DB, the group owns LR(s): ["
438                     + sqle.getMessage()+"]");
439 
440           default:
441             throw new PersistenceException(
442                   "can't delete a group from DB: ["+ sqle.getMessage()+"]");
443         }
444       }
445       finally {
446         DBHelper.cleanup(pstmt);
447       }
448 
449     }
450     else {
451       throw new IllegalArgumentException();
452     }
453 
454 
455     //4. delete from collections
456     this.groupsByID.remove(grp.getID());
457     this.groupsByName.remove(grp.getName());
458 
459     //5. notify all other listeners
460     //this one is tricky - sent OBJECT_DELETED event to all who care
461     //but note that the SOURCE is not us but the object being deleted
462     ObjectModificationEvent e = new ObjectModificationEvent(
463                       grp,
464                       ObjectModificationEvent.OBJECT_DELETED,
465                       0);
466 
467     fireObjectDeletedEvent(e);
468 
469     //6. this one is tricky: invalidate all sessions
470     //that are for user logged in as members of this group
471     Set sessionMappings = this.sessions.entrySet();
472     Iterator itSessions = sessionMappings.iterator();
473 
474     //6.1 to avoid ConcurrentModificationException store the sessions
475     //found in a temp vector
476     Vector sessionsToDelete = new Vector();
477     while (itSessions.hasNext()) {
478       Map.Entry mapEntry = (Map.Entry)itSessions.next();
479       SessionImpl  ses = (SessionImpl)mapEntry.getValue();
480       if (ses.getGroup().equals(grp)) {
481         //logout(ses); --> this will cause ConcurrentModificationException
482         sessionsToDelete.add(ses);
483       }
484     }
485     //6.2 now delete sessions
486     for (int i=0; i< sessionsToDelete.size(); i++) {
487       Session ses = (Session)sessionsToDelete.elementAt(i);
488       logout(ses);
489     }
490 
491   }
492 
493   /** --- */
494   public User createUser(String name, String passwd,Session s)
495     throws PersistenceException,SecurityException {
496 
497     Assert.assertNotNull(name);
498 
499     //-1. check session
500     if (false == isValidSession(s)) {
501       throw new SecurityException("invalid session supplied");
502     }
503 
504     //0. check privileges
505     if (false == s.isPrivilegedSession()) {
506       throw new SecurityException("insufficient privileges");
507     }
508 
509     //1. create user in DB
510     CallableStatement stmt = null;
511     PreparedStatement pstmt = null;
512     ResultSet rs = null;
513 
514     Long new_id;
515 
516     if (this.dbType == DBHelper.ORACLE_DB) {
517 
518       try {
519         stmt = this.jdbcConn.prepareCall(
520                   "{ call "+Gate.DB_OWNER+".security.create_user(?,?,?)} ");
521         stmt.setString(1,name);
522         stmt.setString(2,passwd);
523         //numbers generated from Oracle sequences are BIGINT
524         stmt.registerOutParameter(3,java.sql.Types.BIGINT);
525         stmt.execute();
526         new_id = new Long(stmt.getLong(3));
527       }
528       catch(SQLException sqle) {
529         //check for more specific exceptions
530         switch(sqle.getErrorCode()) {
531 
532           case DBHelper.X_ORACLE_DUPLICATE_USER_NAME:
533             throw new PersistenceException(
534                   "can't create a user in DB, name is not unique: ["
535                     + sqle.getMessage()+"]");
536           default:
537             throw new PersistenceException(
538                   "can't create a user in DB: ["+ sqle.getMessage()+"]");
539         }
540       }
541       finally {
542         DBHelper.cleanup(stmt);
543       }
544     }
545 
546     else if (this.dbType == DBHelper.POSTGRES_DB) {
547 
548       try {
549         String sql = "select security_create_user(?,?) ";
550         pstmt = this.jdbcConn.prepareStatement(sql);
551         pstmt.setString(1,name);
552         pstmt.setString(2,passwd);
553         pstmt.execute();
554         rs = pstmt.getResultSet();
555 
556         if (false == rs.next()) {
557           throw new PersistenceException("empty resultset");
558         }
559 
560         new_id = new Long(rs.getLong(1));
561       }
562       catch(SQLException sqle) {
563         //check for more specific exceptions
564         switch(sqle.getErrorCode()) {
565 
566           case DBHelper.X_ORACLE_DUPLICATE_USER_NAME:
567             throw new PersistenceException(
568                   "can't create a user in DB, name is not unique: ["
569                     + sqle.getMessage()+"]");
570           default:
571             throw new PersistenceException(
572                   "can't create a user in DB: ["+ sqle.getMessage()+"]");
573         }
574       }
575       finally {
576         DBHelper.cleanup(rs);
577         DBHelper.cleanup(pstmt);
578       }
579 
580     }
581 
582     else {
583       throw new IllegalArgumentException();
584     }
585 
586     //2. create UserImpl for the new user
587     // groups list is empty
588     UserImpl usr = new UserImpl(new_id,name,new Vector(),this,this.jdbcConn);
589 
590     //3. register as objectModification listener for this user
591     //we care only about user changing name
592     usr.registerObjectModificationListener(this,ObjectModificationEvent.OBJECT_MODIFIED);
593 
594     //4. put in collections
595     this.usersByID.put(new_id,usr);
596     this.usersByName.put(name,usr);
597 
598     return usr;
599   }
600 
601   /** --- */
602   public void deleteUser(User usr, Session s)
603     throws PersistenceException,SecurityException {
604 
605     //1. check session
606     if (false == isValidSession(s)) {
607       throw new SecurityException("invalid session supplied");
608     }
609 
610     //2. check privileges
611     if (false == s.isPrivilegedSession()) {
612       throw new SecurityException("insufficient privileges");
613     }
614 
615     //3. delete in DB
616     CallableStatement cstmt = null;
617     PreparedStatement pstmt = null;
618 
619     if (this.dbType == DBHelper.ORACLE_DB) {
620 
621       try {
622         cstmt = this.jdbcConn.prepareCall(
623                     "{ call "+Gate.DB_OWNER+".security.delete_user(?) } ");
624         cstmt.setLong(1,usr.getID().longValue());
625         cstmt.execute();
626       }
627       catch(SQLException sqle) {
628         switch(sqle.getErrorCode()) {
629 
630           case DBHelper.X_ORACLE_USER_OWNS_RESOURCES:
631             throw new PersistenceException(
632                   "can't delete user from DB, the user owns LR(s): ["
633                     + sqle.getMessage()+"]");
634           default:
635             throw new PersistenceException(
636                   "can't delete user from DB: ["+ sqle.getMessage()+"]");
637         }
638       }
639       finally {
640         DBHelper.cleanup(cstmt);
641       }
642     }
643 
644     else if (this.dbType == DBHelper.POSTGRES_DB) {
645 
646       try {
647         String sql = "select security_delete_user(?) ";
648         pstmt = this.jdbcConn.prepareStatement(sql);
649         pstmt.setLong(1,usr.getID().longValue());
650         pstmt.execute();
651       }
652       catch(SQLException sqle) {
653         switch(sqle.getErrorCode()) {
654 
655           case DBHelper.X_ORACLE_USER_OWNS_RESOURCES:
656             throw new PersistenceException(
657                   "can't delete user from DB, the user owns LR(s): ["
658                     + sqle.getMessage()+"]");
659           default:
660             throw new PersistenceException(
661                   "can't delete user from DB: ["+ sqle.getMessage()+"]");
662         }
663       }
664       finally {
665         DBHelper.cleanup(pstmt);
666       }
667 
668     }
669 
670     else {
671       throw new IllegalArgumentException();
672     }
673 
674 
675     //4. delete from collections
676     this.usersByID.remove(usr.getID());
677     this.usersByName.remove(usr.getName());
678 
679     //6. notify all other listeners
680     //this one is tricky - sent OBJECT_DELETED event to all who care
681     //but note that the SOURCE is not us but the object being deleted
682     ObjectModificationEvent e = new ObjectModificationEvent(
683                       usr,
684                       ObjectModificationEvent.OBJECT_DELETED,
685                       0);
686 
687     fireObjectDeletedEvent(e);
688 
689     //7. this one is tricky: invalidate all sessions for the user
690     Set sessionMappings = this.sessions.entrySet();
691     Iterator itSessions = sessionMappings.iterator();
692 
693     //7.1 to avoid ConcurrentModificationException store the sessions
694     //found in a temp vector
695     Vector sessionsToDelete = new Vector();
696     while (itSessions.hasNext()) {
697       Map.Entry mapEntry = (Map.Entry)itSessions.next();
698       SessionImpl  ses = (SessionImpl)mapEntry.getValue();
699       if (ses.getUser().equals(usr)) {
700         //logout(ses); --> this will cause ConcurrentModificationException
701         sessionsToDelete.add(ses);
702       }
703     }
704     //7.2 now delete sessions
705     for (int i=0; i< sessionsToDelete.size(); i++) {
706       Session ses = (Session)sessionsToDelete.elementAt(i);
707       logout(ses);
708     }
709 
710   }
711 
712 
713   /** --- */
714   public void deleteUser(Long id, Session s)
715     throws PersistenceException,SecurityException {
716 
717     User usr = (User)usersByID.get(id);
718     if (null == usr) {
719       throw new SecurityException("incorrect user id supplied ( id = ["+id+"])");
720     }
721 
722     //delegate
723     deleteUser(usr,s);
724   }
725 
726   /** --- */
727   public Session login(String usr_name, String passwd,Long prefGroupID)
728     throws PersistenceException,SecurityException {
729 
730     //1. check the user locally
731     User usr = (User)this.usersByName.get(usr_name);
732     if (null == usr) {
733       throw new SecurityException("no such user (username=["+usr_name+"])");
734     }
735 
736     //2. check group localy
737     Group grp = (Group)this.groupsByID.get(prefGroupID);
738     if (null == grp) {
739       throw new SecurityException("no such group (id=["+prefGroupID+"])");
740     }
741 
742     //2. check user/pass in DB
743     CallableStatement cstmt = null;
744     PreparedStatement pstmt = null;
745     ResultSet rs = null;
746 
747     boolean isPrivilegedUser = false;
748 
749     if (this.dbType == DBHelper.ORACLE_DB) {
750 
751       try {
752         cstmt = this.jdbcConn.prepareCall(
753                   "{ call "+Gate.DB_OWNER+".security.login(?,?,?,?)} ");
754         cstmt.setString(1,usr_name);
755         cstmt.setString(2,passwd);
756         cstmt.setLong(3,prefGroupID.longValue());
757         cstmt.registerOutParameter(4,java.sql.Types.NUMERIC);
758         cstmt.execute();
759         isPrivilegedUser = (cstmt.getInt(4) == DBHelper.FALSE ? false : true );
760       }
761       catch(SQLException sqle) {
762         switch(sqle.getErrorCode())
763         {
764           case DBHelper.X_ORACLE_INVALID_USER_NAME :
765             throw new SecurityException("Login failed: incorrect user");
766           case DBHelper.X_ORACLE_INVALID_USER_PASS :
767             throw new SecurityException("Login failed: incorrect password");
768           case DBHelper.X_ORACLE_INVALID_USER_GROUP :
769             throw new SecurityException("Login failed: incorrect group");
770           default:
771             throw new PersistenceException("can't login user, DB error is: ["+
772                                             sqle.getMessage()+"]");
773         }
774       }
775       finally {
776         DBHelper.cleanup(cstmt);
777       }
778     }
779 
780     else if (this.dbType == DBHelper.POSTGRES_DB) {
781 
782       try {
783         String sql = "select security_login(?,?,?) ";
784         pstmt = this.jdbcConn.prepareStatement(sql);
785         pstmt.setString(1,usr_name);
786         pstmt.setString(2,passwd);
787         pstmt.setLong(3,prefGroupID.longValue());
788         pstmt.execute();
789         rs = pstmt.getResultSet();
790 
791         if (false == rs.next()) {
792           throw new PersistenceException("empty resultset");
793         }
794 
795         isPrivilegedUser = rs.getBoolean(1);
796       }
797       catch(SQLException sqle) {
798         switch(sqle.getErrorCode())
799         {
800           case DBHelper.X_ORACLE_INVALID_USER_NAME :
801             throw new SecurityException("Login failed: incorrect user");
802           case DBHelper.X_ORACLE_INVALID_USER_PASS :
803             throw new SecurityException("Login failed: incorrect password");
804           case DBHelper.X_ORACLE_INVALID_USER_GROUP :
805             throw new SecurityException("Login failed: incorrect group");
806           default:
807             throw new PersistenceException("can't login user, DB error is: ["+
808                                             sqle.getMessage()+"]");
809         }
810       }
811       finally {
812         DBHelper.cleanup(rs);
813         DBHelper.cleanup(pstmt);
814       }
815     }
816 
817     else {
818       throw new IllegalArgumentException();
819     }
820 
821     //3. create a Session and set User/Group
822     Long sessionID = createSessionID();
823     while (this.sessions.containsKey(sessionID)) {
824       sessionID = createSessionID();
825     }
826 
827     SessionImpl s = new SessionImpl(sessionID,
828                                     usr,
829                                     grp,
830                                     DEFAULT_SESSION_TIMEOUT_MIN,
831                                     isPrivilegedUser);
832 
833     //4. add session to session collections
834     this.sessions.put(s.getID(),s);
835 
836     //5. set the session timeouts and keep alives
837     this.sessionTimeouts.put(sessionID,new Long(DEFAULT_SESSION_TIMEOUT_MIN));
838     touchSession(s); //this one changes the keepAlive time
839 
840     return s;
841   }
842 
843   /** --- */
844   public void logout(Session s)
845     throws SecurityException {
846 
847     Assert.assertNotNull(s);
848     Long SID = s.getID();
849 
850     //1.sessions
851     Session removedSession = (Session)this.sessions.remove(SID);
852     Assert.assertNotNull(removedSession);
853 
854     //2. keep alives
855     Object lastUsed = this.sessionLastUsed.remove(SID);
856     Assert.assertNotNull(lastUsed);
857 
858     //3. timeouts
859     Object timeout = this.sessionTimeouts.remove(SID);
860     Assert.assertNotNull(timeout);
861   }
862 
863   /** --- */
864   public void setSessionTimeout(Session s, int timeoutMins)
865     throws SecurityException {
866 
867     this.sessionTimeouts.put(s.getID(),new Long(timeoutMins));
868   }
869 
870   /** --- */
871   public boolean isValidSession(Session s) {
872 
873     //1. do we have such session?
874     if (false == this.sessions.containsKey(s.getID())) {
875       return false;
876     }
877 
878     //2. has it expired meanwhile?
879     Assert.assertNotNull(this.sessionLastUsed.get(s.getID()));
880 
881     long lastUsedMS = ((Long)this.sessionLastUsed.get(s.getID())).longValue();
882     long sessTimeoutMin = ((Long)this.sessionTimeouts.get(s.getID())).longValue();
883     long currTimeMS = System.currentTimeMillis();
884     //timeout is in minutes
885     long lastUsedMin = (currTimeMS-lastUsedMS)/(1000*60);
886 
887     if (lastUsedMin > sessTimeoutMin) {
888       //oops, session expired
889       //invalidate it and fail
890       try {
891         logout(s);
892       }
893       catch(SecurityException se) {
894         //well, this can happen only if logout() was called together
895         //with isValidSesion() but the possibility it too low to care
896         //and synchronize access
897         ;
898       }
899 
900       return false;
901     }
902 
903     //everything ok
904     //touch session
905     touchSession(s);
906 
907     return true;
908   }
909 
910   /** -- */
911   public List listGroups()
912     throws PersistenceException {
913 
914     //1. read all groups from DB
915     Statement stmt = null;
916     ResultSet rs = null;
917     String    sql;
918     Vector    result = new Vector();
919 
920     try {
921       stmt = this.jdbcConn.createStatement();
922 
923       //1.1 read groups
924       sql = " SELECT grp_name "+
925             " FROM   "+this.jdbcSchema+"t_group "+
926             " ORDER BY grp_name ASC";
927       rs = stmt.executeQuery(sql);
928 
929       while (rs.next()) {
930         //access by index is faster
931         //first column index is 1
932         String grp_name = rs.getString(1);
933         result.add(grp_name);
934       }
935 
936       return result;
937     }
938     catch (SQLException sqle) {
939       throw new PersistenceException("cannot read groups from DB :["+
940                                         sqle.getMessage() +"]");
941     }
942     finally {
943       DBHelper.cleanup(rs);
944       DBHelper.cleanup(stmt);
945     }
946   }
947 
948   /** -- */
949   public List listUsers()
950     throws PersistenceException {
951 
952     //1. read all users from DB
953     Statement stmt = null;
954     ResultSet rs = null;
955     String    sql;
956     Vector    result = new Vector();
957 
958     try {
959       stmt = this.jdbcConn.createStatement();
960 
961       //1.1 read groups
962       sql = " SELECT usr_login "+
963             " FROM   "+this.jdbcSchema+"t_user "+
964             " ORDER BY usr_login DESC";
965       rs = stmt.executeQuery(sql);
966 
967       while (rs.next()) {
968         //access by index is faster
969         //first column index is 1
970         String usr_name = rs.getString(1);
971         result.add(usr_name);
972       }
973 
974       return result;
975     }
976     catch (SQLException sqle) {
977       throw new PersistenceException("cannot read groups from DB :["+
978                                         sqle.getMessage() +"]");
979     }
980     finally {
981       DBHelper.cleanup(rs);
982       DBHelper.cleanup(stmt);
983     }
984   }
985 
986 
987 
988   /*  private methods */
989 
990   private void touchSession(Session s) {
991 
992     this.sessionLastUsed.put(s.getID(),  new Long(System.currentTimeMillis()));
993   }
994 
995 
996   private Long createSessionID() {
997 
998     //need a hint?
999     return new Long(((System.currentTimeMillis() << 16) >> 16)*
1000                      (r.nextInt(RANDOM_MAX))*
1001                          Runtime.getRuntime().freeMemory()*
1002                              MY_VERY_SECRET_CONSTANT);
1003  }
1004
1005
1006  private boolean canDeleteGroup(Group grp)
1007    throws PersistenceException, SecurityException{
1008
1009    //1. check group localy
1010    if (false == this.groupsByID.containsValue(grp)) {
1011      throw new SecurityException("no such group (id=["+grp.getID()+"])");
1012    }
1013
1014    //2. check DB
1015    CallableStatement stmt = null;
1016    PreparedStatement pstmt = null;
1017    ResultSet rs = null;
1018
1019    if (this.dbType == DBHelper.ORACLE_DB) {
1020
1021      try {
1022        stmt = this.jdbcConn.prepareCall(
1023                  "{ ? = call "+Gate.DB_OWNER+".security.can_delete_group(?) }");
1024        stmt.registerOutParameter(1,java.sql.Types.INTEGER);
1025        stmt.setLong(2,grp.getID().longValue());
1026        stmt.execute();
1027        boolean res = stmt.getBoolean(1);
1028
1029        return res;
1030      }
1031      catch(SQLException sqle) {
1032        throw new PersistenceException("can't perform document checks, DB error is: ["+
1033                                            sqle.getMessage()+"]");
1034      }
1035      finally {
1036        DBHelper.cleanup(stmt);
1037      }
1038    }
1039
1040    else if (this.dbType == DBHelper.POSTGRES_DB) {
1041
1042      try {
1043        String sql = "select security_can_delete_group(?)";
1044        pstmt = this.jdbcConn.prepareCall(sql);
1045        pstmt.setLong(1,grp.getID().longValue());
1046        pstmt.execute();
1047        rs = pstmt.getResultSet();
1048
1049        if (false == rs.next()) {
1050          throw new PersistenceException("empty resultset");
1051        }
1052
1053        boolean res = rs.getBoolean(1);
1054
1055        return res;
1056      }
1057      catch(SQLException sqle) {
1058        throw new PersistenceException("can't perform document checks, DB error is: ["+
1059                                            sqle.getMessage()+"]");
1060      }
1061      finally {
1062        DBHelper.cleanup(rs);
1063        DBHelper.cleanup(pstmt);
1064      }
1065
1066    }
1067
1068    else {
1069      throw new IllegalArgumentException();
1070    }
1071  }
1072
1073
1074  private boolean canDeleteUser(User usr)
1075    throws PersistenceException, SecurityException{
1076
1077    //1. check group localy
1078    if (false == this.usersByID.containsValue(usr)) {
1079      throw new SecurityException("no such user (id=["+usr.getID()+"])");
1080    }
1081
1082    //2. check DB
1083    CallableStatement stmt = null;
1084    PreparedStatement pstmt = null;
1085    ResultSet rs = null;
1086
1087    if (this.dbType == DBHelper.ORACLE_DB) {
1088
1089      try {
1090        stmt = this.jdbcConn.prepareCall(
1091                  "{ ? = call "+Gate.DB_OWNER+".security.can_delete_user(?) }");
1092
1093        stmt.registerOutParameter(1,java.sql.Types.INTEGER);
1094        stmt.setLong(2,usr.getID().longValue());
1095        stmt.execute();
1096        boolean res = stmt.getBoolean(1);
1097
1098        return res;
1099      }
1100      catch(SQLException sqle) {
1101        throw new PersistenceException("can't perform document checks, DB error is: ["+
1102                                            sqle.getMessage()+"]");
1103      }
1104      finally {
1105        DBHelper.cleanup(stmt);
1106      }
1107    }
1108
1109    else if (this.dbType == DBHelper.POSTGRES_DB) {
1110
1111      try {
1112        String sql = "select security_can_delete_user(?) ";
1113        pstmt = this.jdbcConn.prepareCall(sql);
1114        pstmt.setLong(1,usr.getID().longValue());
1115        pstmt.execute();
1116        boolean res = rs.getBoolean(1);
1117
1118        return res;
1119      }
1120      catch(SQLException sqle) {
1121        throw new PersistenceException("can't perform document checks, DB error is: ["+
1122                                            sqle.getMessage()+"]");
1123      }
1124      finally {
1125        DBHelper.cleanup(rs);
1126        DBHelper.cleanup(pstmt);
1127      }
1128
1129    }
1130
1131    else {
1132      throw new IllegalArgumentException();
1133    }
1134
1135  }
1136
1137  private void init()
1138    throws PersistenceException {
1139
1140    //1. read all groups and users from DB
1141    Statement stmt = null;
1142    ResultSet rs = null;
1143    String    sql;
1144    Hashtable   groupNames = new Hashtable();
1145    Hashtable   groupMembers= new Hashtable();
1146    Hashtable   userNames= new Hashtable();
1147    Hashtable   userGroups= new Hashtable();
1148
1149    try {
1150      stmt = this.jdbcConn.createStatement();
1151
1152      //1.1 read groups
1153      sql = " SELECT grp_id, " +
1154            "        grp_name "+
1155            " FROM   "+this.jdbcSchema+"t_group";
1156      rs = stmt.executeQuery(sql);
1157
1158
1159
1160      while (rs.next()) {
1161        //access by index is faster
1162        //first column index is 1
1163        long grp_id = rs.getLong(1);
1164        String grp_name = rs.getString(2);
1165        groupNames.put(new Long(grp_id),grp_name);
1166        groupMembers.put(new Long(grp_id),new Vector());
1167      }
1168      DBHelper.cleanup(rs);
1169
1170
1171      //1.2 read users
1172      sql = " SELECT usr_id, " +
1173            "        usr_login "+
1174            " FROM   "+this.jdbcSchema+"t_user";
1175      rs = stmt.executeQuery(sql);
1176
1177      while (rs.next()) {
1178        //access by index is faster
1179        //first column index is 1
1180        long usr_id = rs.getLong(1);
1181        String usr_name = rs.getString(2);
1182        userNames.put(new Long(usr_id),usr_name);
1183        userGroups.put(new Long(usr_id),new Vector());
1184      }
1185      DBHelper.cleanup(rs);
1186
1187
1188      //1.3 read user/group relations
1189      sql = " SELECT    UGRP_GROUP_ID, " +
1190            "           UGRP_USER_ID "+
1191            " FROM      "+this.jdbcSchema+"t_user_group " +
1192            " ORDER BY  UGRP_GROUP_ID asc";
1193      rs = stmt.executeQuery(sql);
1194
1195      while (rs.next()) {
1196        //access by index is faster
1197        //first column index is 1
1198        Long grp_id = new Long(rs.getLong(1));
1199        Long usr_id = new Long(rs.getLong(2));
1200
1201        //append user to group members list
1202        Vector currMembers = (Vector)groupMembers.get(grp_id);
1203        currMembers.add(usr_id);
1204
1205        Vector currGroups = (Vector)userGroups.get(usr_id);
1206        currGroups.add(grp_id);
1207      }
1208      DBHelper.cleanup(rs);
1209    }
1210    catch(SQLException sqle) {
1211      throw new PersistenceException("DB error is: ["+
1212                                          sqle.getMessage()+"]");
1213    }
1214    finally {
1215      DBHelper.cleanup(rs);
1216      DBHelper.cleanup(stmt);
1217    }
1218
1219    //2. create USerImpl's and GroupImpl's and put them in collections
1220
1221    //2.1 create Groups
1222    Vector toBeInitializedGroups = new Vector();
1223
1224    Enumeration enGroups = groupNames.keys();
1225    while (enGroups.hasMoreElements()) {
1226      Long grpId = (Long)enGroups.nextElement();
1227//      Vector grpMembers = (Vector)groupMembers.get(grpId);
1228      String grpName = (String)groupNames.get(grpId);
1229
1230      //note that the Vector with group members is empty
1231      //will beinitalized later (ugly hack for bad desgin)
1232      GroupImpl grp = new GroupImpl(grpId,grpName,new Vector(),this,this.jdbcConn);
1233      //register as listener for thsi group
1234      //we care only about name changes
1235      grp.registerObjectModificationListener(this,ObjectModificationEvent.OBJECT_MODIFIED);
1236
1237      //add to collection
1238      this.groupsByID.put(grp.getID(),grp);
1239      this.groupsByName.put(grp.getName(),grp);
1240
1241      //add to vector of the objects to be initialized
1242      toBeInitializedGroups.add(grp);
1243    }
1244
1245    //2.2 create Users
1246    Vector toBeInitializedUsers = new Vector();
1247
1248    Enumeration enUsers = userNames.keys();
1249    while (enUsers.hasMoreElements()) {
1250      Long usrId = (Long)enUsers.nextElement();
1251//      Vector usrGroups = (Vector)userGroups.get(usrId);
1252      String usrName = (String)userNames.get(usrId);
1253
1254      //note that the Vector with user's group is empty
1255      //will be initalized later (ugly hack for bad desgin)
1256      UserImpl usr = new UserImpl(usrId,usrName,new Vector(),this,this.jdbcConn);
1257      //register as listener for thsi user
1258      //we care only about user changing name
1259      usr.registerObjectModificationListener(this,ObjectModificationEvent.OBJECT_MODIFIED);
1260
1261      //add to collection
1262      this.usersByID.put(usr.getID(),usr);
1263      this.usersByName.put(usr.getName(),usr);
1264
1265      //add to vector of the objects to be initialized
1266      toBeInitializedUsers.add(usr);
1267    }
1268
1269    //3. the hack itself:
1270    //all the groups and users are not fully initialized yet
1271    //(the groups/users Vectors are empty)
1272    //initialize them now
1273
1274    //3.1 initialize groups
1275    for (int i=0; i< toBeInitializedGroups.size(); i++) {
1276      GroupImpl grp = (GroupImpl)toBeInitializedGroups.elementAt(i);
1277      grp.setUsers((Vector)groupMembers.get(grp.getID()));
1278    }
1279
1280    //3.2 initialize users
1281    for (int i=0; i< toBeInitializedUsers.size(); i++) {
1282      UserImpl usr = (UserImpl)toBeInitializedUsers.elementAt(i);
1283      usr.setGroups((Vector)userGroups.get(usr.getID()));
1284    }
1285
1286  }
1287
1288
1289  private void fireObjectCreatedEvent(ObjectModificationEvent e) {
1290
1291    //sanity check
1292    if (e.getType() != ObjectModificationEvent.OBJECT_CREATED) {
1293      throw new IllegalArgumentException();
1294    }
1295
1296    for (int i=0; i< this.omCreationListeners.size(); i++) {
1297      ((ObjectModificationListener)this.omCreationListeners.elementAt(i)).objectCreated(e);
1298    }
1299  }
1300
1301
1302  private void fireObjectDeletedEvent(ObjectModificationEvent e) {
1303
1304    //sanity check
1305    if (e.getType() != ObjectModificationEvent.OBJECT_DELETED) {
1306      throw new IllegalArgumentException();
1307    }
1308
1309    for (int i=0; i< this.omDeletionListeners.size(); i++) {
1310      ((ObjectModificationListener)this.omDeletionListeners.elementAt(i)).objectDeleted(e);
1311    }
1312  }
1313
1314
1315  private void fireObjectModifiedEvent(ObjectModificationEvent e) {
1316
1317    //sanity check
1318    if (e.getType() != ObjectModificationEvent.OBJECT_MODIFIED) {
1319      throw new IllegalArgumentException();
1320    }
1321
1322    for (int i=0; i< this.omModificationListeners.size(); i++) {
1323      ((ObjectModificationListener)omModificationListeners.elementAt(i)).objectModified(e);
1324    }
1325  }
1326
1327
1328
1329
1330  public void registerObjectModificationListener(ObjectModificationListener l,
1331                                                 int eventType) {
1332
1333    if (eventType != ObjectModificationEvent.OBJECT_CREATED &&
1334        eventType != ObjectModificationEvent.OBJECT_DELETED &&
1335        eventType != ObjectModificationEvent.OBJECT_MODIFIED) {
1336
1337        throw new IllegalArgumentException();
1338    }
1339
1340    switch(eventType) {
1341      case ObjectModificationEvent.OBJECT_CREATED :
1342        this.omCreationListeners.add(l);
1343        break;
1344      case ObjectModificationEvent.OBJECT_DELETED :
1345        this.omDeletionListeners.add(l);
1346        break;
1347      case ObjectModificationEvent.OBJECT_MODIFIED :
1348        this.omModificationListeners.add(l);
1349        break;
1350      default:
1351        Assert.fail();
1352    }
1353
1354  }
1355
1356  public void unregisterObjectModificationListener(ObjectModificationListener l,
1357                                                   int eventType) {
1358
1359    if (eventType != ObjectModificationEvent.OBJECT_CREATED &&
1360        eventType != ObjectModificationEvent.OBJECT_DELETED &&
1361        eventType != ObjectModificationEvent.OBJECT_MODIFIED) {
1362
1363        throw new IllegalArgumentException();
1364    }
1365
1366    switch(eventType) {
1367      case ObjectModificationEvent.OBJECT_CREATED :
1368        this.omCreationListeners.remove(l);
1369        break;
1370      case ObjectModificationEvent.OBJECT_DELETED :
1371        this.omDeletionListeners.remove(l);
1372        break;
1373      case ObjectModificationEvent.OBJECT_MODIFIED :
1374        this.omModificationListeners.remove(l);
1375        break;
1376      default:
1377        Assert.fail();
1378    }
1379
1380  }
1381
1382
1383
1384
1385  /* ObjectModificationListener methods */
1386
1387  public void objectCreated(ObjectModificationEvent e) {
1388    //I've never registered for these events
1389    Assert.fail();
1390  }
1391
1392  public void objectModified(ObjectModificationEvent e) {
1393
1394    Object source = e.getSource();
1395    int type = e.getType();
1396    int subtype = e.getSubType();
1397
1398    //sanity checks
1399    if (type != ObjectModificationEvent.OBJECT_MODIFIED) {
1400      throw new IllegalArgumentException();
1401    }
1402
1403    //I'm interested only in Groups and Users
1404    if (false == source instanceof Group &&
1405        false == source instanceof User) {
1406
1407      throw new IllegalArgumentException();
1408    }
1409
1410
1411    if (source instanceof Group) {
1412
1413      Assert.assertTrue(subtype == Group.OBJECT_CHANGE_ADDUSER ||
1414                    subtype == Group.OBJECT_CHANGE_NAME ||
1415                    subtype == Group.OBJECT_CHANGE_REMOVEUSER);
1416
1417      //the name of the group could be different now (IDs are fixed)
1418      if (subtype == Group.OBJECT_CHANGE_NAME) {
1419        //rehash
1420        //any better idea how to do it?
1421        Set mappings = this.groupsByName.entrySet();
1422        Iterator it = mappings.iterator();
1423
1424        boolean found = false;
1425        while (it.hasNext()) {
1426          Map.Entry mapEntry = (Map.Entry)it.next();
1427          String key = (String)mapEntry.getKey();
1428          Group  grp = (Group)mapEntry.getValue();
1429
1430          if (false == key.equals(grp.getName())) {
1431            //gotcha
1432            this.groupsByName.remove(key);
1433            this.groupsByName.put(grp.getName(),grp);
1434            found = true;
1435            break;
1436          }
1437        }
1438
1439        Assert.assertTrue(found);
1440      }
1441    }
1442    else {
1443
1444      Assert.assertTrue(source instanceof User);
1445
1446      //the name of the user could be different now (IDs are fixed)
1447
1448      Assert.assertTrue(subtype == User.OBJECT_CHANGE_NAME);
1449
1450      //the name of the group could be different now (IDs are fixed)
1451      if (subtype == User.OBJECT_CHANGE_NAME) {
1452        //rehash
1453        //any better idea how to do it?
1454        Set mappings = this.usersByName.entrySet();
1455        Iterator it = mappings.iterator();
1456
1457        boolean found = false;
1458        while (it.hasNext()) {
1459          Map.Entry mapEntry = (Map.Entry)it.next();
1460          String key = (String)mapEntry.getKey();
1461          User  usr = (User)mapEntry.getValue();
1462
1463          if (false == key.equals(usr.getName())) {
1464            //gotcha
1465            this.usersByName.remove(key);
1466            this.usersByName.put(usr.getName(),usr);
1467            found = true;
1468            break;
1469          }
1470        }
1471
1472        Assert.assertTrue(found);
1473      }
1474    }
1475
1476
1477  }
1478
1479  public void objectDeleted(ObjectModificationEvent e) {
1480    //I've never registered for these events
1481    Assert.fail();
1482  }
1483
1484  public void processGateEvent(GateEvent e){
1485    throw new MethodNotImplementedException();
1486  }
1487
1488  /** -- */
1489  public boolean isValidSecurityInfo(SecurityInfo si) {
1490
1491    switch(si.getAccessMode()) {
1492
1493      case SecurityInfo.ACCESS_WR_GW:
1494      case SecurityInfo.ACCESS_GR_GW:
1495        return (null != si.getGroup());
1496
1497      case SecurityInfo.ACCESS_GR_OW:
1498        return (null != si.getGroup() &&
1499                null != si.getUser());
1500
1501      case SecurityInfo.ACCESS_OR_OW:
1502        return (null != si.getUser());
1503
1504      default:
1505        throw new IllegalArgumentException();
1506    }
1507  }
1508
1509  public void finalize() {
1510    //close connection
1511    try {
1512      this.jdbcConn.close();
1513    }
1514    catch(SQLException sqle) {}
1515
1516  }
1517
1518}
1519