Revision 802
Added by bojilova over 23 years ago
src/edu/ucsb/nceas/metacat/AuthLdap.java | ||
---|---|---|
36 | 36 |
import javax.naming.NamingEnumeration; |
37 | 37 |
import javax.naming.NamingException; |
38 | 38 |
import javax.naming.InitialContext; |
39 |
import javax.naming.directory.InvalidSearchFilterException; |
|
39 | 40 |
import javax.naming.directory.Attribute; |
40 | 41 |
import javax.naming.directory.Attributes; |
41 | 42 |
import javax.naming.directory.BasicAttribute; |
... | ... | |
93 | 94 |
String ldapsUrl = this.ldapsUrl; |
94 | 95 |
String ldapBase = this.ldapBase; |
95 | 96 |
boolean authenticated = false; |
97 |
String identifier = user; |
|
96 | 98 |
|
97 | 99 |
// Identify service provider to use |
98 | 100 |
Hashtable env = new Hashtable(11); |
... | ... | |
100 | 102 |
"com.sun.jndi.ldap.LdapCtxFactory"); |
101 | 103 |
|
102 | 104 |
try { |
105 |
|
|
106 |
try { |
|
107 |
this.ldapBase = identifier.substring(identifier.indexOf(",")+1); |
|
108 |
identifier = identifier.substring(0,identifier.indexOf(",")); |
|
109 |
} catch (StringIndexOutOfBoundsException e) {} |
|
103 | 110 |
|
104 | 111 |
/* |
105 | 112 |
* get all subtrees first in the current dir context |
... | ... | |
111 | 118 |
// while ( enum.hasMoreElements() ) { |
112 | 119 |
// ldapBase = (String)enum.nextElement(); |
113 | 120 |
// ldapUrl = (String)subtrees.get(ldapBase); |
114 |
String identifier = getIdentifyingName(user,ldapUrl,ldapBase); |
|
121 |
identifier = getIdentifyingName(identifier,ldapUrl,ldapBase); |
|
122 |
System.out.println(ldapsUrl + identifier + "," + ldapBase); |
|
115 | 123 |
|
116 | 124 |
if (identifier != null && !password.equals("")) { |
117 | 125 |
// Now that we have the dn, we can authenticate, so |
118 | 126 |
// authenticate this time when opening the DirContext |
119 |
System.out.println(ldapsUrl + identifier + "," + ldapBase); |
|
120 | 127 |
env.put(Context.PROVIDER_URL, ldapsUrl + ldapBase); |
121 | 128 |
if ( !ldapsUrl.equals(ldapUrl) ) { |
122 | 129 |
// ldap is set on default port 389 |
... | ... | |
216 | 223 |
// If we find a record, determine the dn for the record |
217 | 224 |
util.debugMessage("\nStarting search phase...\n"); |
218 | 225 |
|
219 |
String filter = "(uid=" + user + ")"; |
|
220 |
NamingEnumeration answer = ctx.search("", filter, ctls); |
|
226 |
String filter = "(" + user + ")"; |
|
227 |
NamingEnumeration answer; |
|
228 |
try { |
|
229 |
answer = ctx.search("", filter, ctls); |
|
230 |
if (answer.hasMore()) { |
|
231 |
SearchResult sr = (SearchResult)answer.next(); |
|
232 |
identifier = sr.getName(); |
|
233 |
if ( !sr.isRelative() ) { |
|
234 |
this.ldapUrl = identifier.substring(0,identifier.lastIndexOf("/")+1); |
|
235 |
this.ldapBase = identifier.substring(identifier.indexOf(",")+1); |
|
236 |
identifier = identifier.substring(identifier.lastIndexOf("/")+1, |
|
237 |
identifier.indexOf(",")); |
|
238 |
} |
|
239 |
util.debugMessage("Found: " + identifier); |
|
240 |
return identifier; |
|
241 |
} |
|
242 |
} catch (InvalidSearchFilterException e) {} |
|
243 |
|
|
244 |
filter = "(uid=" + user + ")"; |
|
245 |
answer = ctx.search("", filter, ctls); |
|
221 | 246 |
if (answer.hasMore()) { |
222 | 247 |
SearchResult sr = (SearchResult)answer.next(); |
223 | 248 |
identifier = sr.getName(); |
... | ... | |
683 | 708 |
throws ConnectException |
684 | 709 |
{ |
685 | 710 |
HashMap attributes = new HashMap(); |
711 |
String ldapUrl = this.ldapUrl; |
|
712 |
String ldapBase = this.ldapBase; |
|
713 |
String userident = foruser; |
|
714 |
try { |
|
715 |
this.ldapBase = userident.substring(userident.indexOf(",")+1); |
|
716 |
userident = userident.substring(0,userident.indexOf(",")); |
|
717 |
} catch (StringIndexOutOfBoundsException e) {} |
|
686 | 718 |
|
687 | 719 |
// Identify service provider to use |
688 | 720 |
Hashtable env = new Hashtable(11); |
... | ... | |
690 | 722 |
"com.sun.jndi.ldap.LdapCtxFactory"); |
691 | 723 |
env.put(Context.PROVIDER_URL, ldapUrl + ldapBase); |
692 | 724 |
|
693 |
// Authentication information |
|
694 |
|
|
695 | 725 |
try { |
696 | 726 |
|
697 |
// NO NEED FOR AUTHENTICATION; ALL ATTRIBUTES READABLE EXCEPT userPassword |
|
698 |
//if ((user != null) && (password != null)) { |
|
699 |
// String identifier = getIdentifyingName(user,this.ldapUrl,this.ldapBase); |
|
700 |
// env.put(Context.SECURITY_AUTHENTICATION, "simple"); |
|
701 |
// env.put(Context.SECURITY_PRINCIPAL, identifier + "," + ldapBase); |
|
702 |
// env.put(Context.SECURITY_CREDENTIALS, password); |
|
703 |
//} |
|
704 |
|
|
705 | 727 |
// Create the initial directory context |
706 | 728 |
DirContext ctx = new InitialDirContext(env); |
707 | 729 |
|
708 | 730 |
// Find out the identifying attribute for the user |
709 |
String userident = getIdentifyingName(foruser,this.ldapUrl,this.ldapBase);
|
|
731 |
userident = getIdentifyingName(userident,ldapUrl,ldapBase);
|
|
710 | 732 |
|
711 | 733 |
// Ask for all attributes of the user |
712 | 734 |
Attributes attrs = ctx.getAttributes(userident); |
... | ... | |
831 | 853 |
StringBuffer out = new StringBuffer(); |
832 | 854 |
Vector usersIn = new Vector(); |
833 | 855 |
|
834 |
//String ldapUrl = "ldap://dev.nceas.ucsb.edu/"; |
|
835 |
//String ldapBase = "dc=ecoinformatics,dc=org"; |
|
836 |
//String ldapUrl = "ldap://ldap.nceas.ucsb.edu/"; |
|
837 |
//String ldapBase = "o=NCEAS,c=US"; |
|
838 |
|
|
839 | 856 |
out.append("<?xml version=\"1.0\"?>\n"); |
840 | 857 |
out.append("<principals>\n"); |
841 | 858 |
|
... | ... | |
860 | 877 |
if ( groups.length > 0 ) { |
861 | 878 |
for (int i=0; i < groups.length; i++ ) { |
862 | 879 |
out.append(" <group>\n"); |
863 |
out.append(" <groupname>" + groups[i] + "<groupname>\n"); |
|
880 |
out.append(" <groupname>" + groups[i] + "</groupname>\n");
|
|
864 | 881 |
String[] usersForGroup = getUsers(user,password,groups[i]); |
865 | 882 |
for (int j=0; j < usersForGroup.length; j++ ) { |
866 | 883 |
usersIn.addElement(usersForGroup[j]); |
867 | 884 |
out.append(" <user>\n"); |
868 |
out.append(" <username>" + usersForGroup[j] + "<username>\n"); |
|
885 |
out.append(" <username>" + usersForGroup[j] + "</username>\n");
|
|
869 | 886 |
out.append(" </user>\n"); |
870 | 887 |
} |
871 | 888 |
out.append(" </group>\n"); |
... | ... | |
876 | 893 |
for (int j=0; j < users.length; j++ ) { |
877 | 894 |
if ( !usersIn.contains(users[j]) ) { |
878 | 895 |
out.append(" <user>\n"); |
879 |
out.append(" <username>" + users[j] + "<username>\n"); |
|
896 |
out.append(" <username>" + users[j] + "</username>\n");
|
|
880 | 897 |
out.append(" </user>\n"); |
881 | 898 |
} |
882 | 899 |
} |
... | ... | |
931 | 948 |
|
932 | 949 |
} |
933 | 950 |
|
934 |
/* |
|
935 |
// get the whole list of users |
|
936 |
if (isValid) { |
|
937 |
String[] users = authservice.getUsers(user, password); |
|
938 |
for (int i=0; i < users.length; i++) { |
|
939 |
System.out.println(users[i]); |
|
940 |
} |
|
941 |
System.out.println("Total " + users.length + " users."); |
|
942 |
} |
|
943 |
*/ |
|
944 |
/* |
|
945 |
// get the whole list of users for a group |
|
946 |
if (isValid) { |
|
947 |
String group = args[2]; |
|
948 |
String[] users = authservice.getUsers(user, password, group); |
|
949 |
for (int i=0; i < users.length; i++) { |
|
950 |
System.out.println(users[i]); |
|
951 |
} |
|
952 |
} |
|
953 |
*/ |
|
954 | 951 |
// get the whole list groups and users in XML format |
955 |
/* |
|
956 | 952 |
if (isValid) { |
957 | 953 |
authservice = new AuthLdap(); |
958 | 954 |
String out = authservice.getPrincipals(user, password); |
959 |
java.io.File f = new java.io.File("principals.txt");
|
|
955 |
java.io.File f = new java.io.File("principals.xml");
|
|
960 | 956 |
java.io.FileWriter fw = new java.io.FileWriter(f); |
961 | 957 |
java.io.BufferedWriter buff = new java.io.BufferedWriter(fw); |
962 | 958 |
buff.write(out); |
... | ... | |
964 | 960 |
buff.close(); |
965 | 961 |
fw.close(); |
966 | 962 |
} |
967 |
*/ |
|
963 |
|
|
968 | 964 |
} catch (ConnectException ce) { |
969 | 965 |
System.err.println(ce.getMessage()); |
970 | 966 |
} catch (java.io.IOException ioe) { |
src/edu/ucsb/nceas/metacat/AuthSession.java | ||
---|---|---|
112 | 112 |
session.setAttribute("username", username); |
113 | 113 |
session.setAttribute("password", password); |
114 | 114 |
if ( groups.length > 0 ) { |
115 |
session.setAttribute("groupname", groups[0]);
|
|
115 |
session.setAttribute("groupnames", groups);
|
|
116 | 116 |
} |
117 | 117 |
|
118 | 118 |
return session; |
src/edu/ucsb/nceas/metacat/DBQuery.java | ||
---|---|---|
183 | 183 |
* @param user the username of the user |
184 | 184 |
* @param group the group of the user |
185 | 185 |
*/ |
186 |
public Hashtable findDocuments(Reader xmlquery, String user, String group)
|
|
186 |
public Hashtable findDocuments(Reader xmlquery, String user, String[] groups)
|
|
187 | 187 |
{ |
188 |
return findDocuments(xmlquery, user, group, true); |
|
188 |
return findDocuments(xmlquery, user, groups, true);
|
|
189 | 189 |
} |
190 | 190 |
|
191 | 191 |
/** |
... | ... | |
196 | 196 |
* @param group the group of the user |
197 | 197 |
* @param useXMLIndex flag whether to search using the path index |
198 | 198 |
*/ |
199 |
public Hashtable findDocuments(Reader xmlquery, String user, String group,
|
|
199 |
public Hashtable findDocuments(Reader xmlquery, String user, String[] groups,
|
|
200 | 200 |
boolean useXMLIndex) |
201 | 201 |
{ |
202 | 202 |
Hashtable docListResult = new Hashtable(); |
... | ... | |
237 | 237 |
while (tableHasRows) |
238 | 238 |
{ |
239 | 239 |
docid = rs.getString(1).trim(); |
240 |
if ( !hasPermission(dbconn, user, group, docid) ) { |
|
240 |
if ( !hasPermission(dbconn, user, groups, docid) ) {
|
|
241 | 241 |
// Advance to the next record in the cursor |
242 | 242 |
tableHasRows = rs.next(); |
243 | 243 |
continue; |
... | ... | |
391 | 391 |
while(tableHasRows) |
392 | 392 |
{ |
393 | 393 |
docid = rs.getString(1).trim(); |
394 |
if ( !hasPermission(dbconn, user, group, docid) ) { |
|
394 |
if ( !hasPermission(dbconn, user, groups, docid) ) {
|
|
395 | 395 |
// Advance to the next record in the cursor |
396 | 396 |
tableHasRows = rs.next(); |
397 | 397 |
continue; |
... | ... | |
785 | 785 |
* from DB connection |
786 | 786 |
*/ |
787 | 787 |
private boolean hasPermission ( Connection conn, String user, |
788 |
String group, String docid )
|
|
788 |
String[] groups, String docid )
|
|
789 | 789 |
throws SQLException |
790 | 790 |
{ |
791 |
// b' of the command line invocation |
|
792 |
if ( (user == null) && (group == null) ) { |
|
793 |
return true; |
|
794 |
} |
|
795 |
|
|
796 |
// Check for READ permission on @docid for @user and/or @group |
|
791 |
// Check for READ permission on @docid for @user and/or @groups |
|
797 | 792 |
AccessControlList aclobj = new AccessControlList(conn); |
798 |
boolean hasPermission = aclobj.hasPermission("READ",user,docid); |
|
799 |
if ( !hasPermission && group != null ) { |
|
800 |
hasPermission = aclobj.hasPermission("READ",group,docid); |
|
801 |
} |
|
802 |
|
|
803 |
return hasPermission; |
|
793 |
return aclobj.hasPermission("READ", user, groups, docid); |
|
804 | 794 |
} |
805 | 795 |
|
806 | 796 |
} |
src/edu/ucsb/nceas/metacat/MetaCatServlet.java | ||
---|---|---|
252 | 252 |
// other than "login" and "logout" |
253 | 253 |
String username = null; |
254 | 254 |
String password = null; |
255 |
String groupname = null;
|
|
255 |
String[] groupnames = null;
|
|
256 | 256 |
String sess_id = null; |
257 | 257 |
|
258 | 258 |
// handle login action |
... | ... | |
276 | 276 |
} else { |
277 | 277 |
username = (String)sess.getAttribute("username"); |
278 | 278 |
password = (String)sess.getAttribute("password"); |
279 |
groupname = (String)sess.getAttribute("groupname");
|
|
279 |
groupnames = (String[])sess.getAttribute("groupnames");
|
|
280 | 280 |
try { |
281 | 281 |
sess_id = (String)sess.getId(); |
282 | 282 |
} catch(IllegalStateException ise) { |
... | ... | |
290 | 290 |
// Now that we know the session is valid, we can delegate the request |
291 | 291 |
// to a particular action handler |
292 | 292 |
if(action.equals("query")) { |
293 |
handleQuery(response.getWriter(), params, response, username, groupname);
|
|
293 |
handleQuery(response.getWriter(),params,response,username,groupnames);
|
|
294 | 294 |
} else if(action.equals("squery")) { |
295 | 295 |
if(params.containsKey("query")) { |
296 |
handleSQuery(response.getWriter(), params, response, username, groupname);
|
|
296 |
handleSQuery(response.getWriter(),params,response,username,groupnames);
|
|
297 | 297 |
} else { |
298 | 298 |
PrintWriter out = response.getWriter(); |
299 | 299 |
out.println("Illegal action squery without \"query\" parameter"); |
300 | 300 |
} |
301 | 301 |
} else if (action.equals("read")) { |
302 |
handleReadAction(params, response, username, groupname); |
|
302 |
handleReadAction(params, response, username, groupnames);
|
|
303 | 303 |
} else if (action.equals("insert") || action.equals("update")) { |
304 | 304 |
PrintWriter out = response.getWriter(); |
305 | 305 |
if ( (username != null) && !username.equals("public") ) { |
306 |
handleInsertOrUpdateAction(out, params, response, username, groupname);
|
|
306 |
handleInsertOrUpdateAction(out,params,response,username,groupnames);
|
|
307 | 307 |
} else { |
308 | 308 |
out.println("Permission denied for " + action); |
309 | 309 |
} |
310 | 310 |
} else if (action.equals("delete")) { |
311 | 311 |
PrintWriter out = response.getWriter(); |
312 | 312 |
if ( (username != null) && !username.equals("public") ) { |
313 |
handleDeleteAction(out, params, response, username, groupname); |
|
313 |
handleDeleteAction(out, params, response, username, groupnames);
|
|
314 | 314 |
} else { |
315 | 315 |
out.println("Permission denied for " + action); |
316 | 316 |
} |
... | ... | |
319 | 319 |
handleValidateAction(out, params, response); |
320 | 320 |
} else if (action.equals("getaccesscontrol")) { |
321 | 321 |
PrintWriter out = response.getWriter(); |
322 |
handleGetAccessControlAction(out, params, response, username, groupname);
|
|
322 |
handleGetAccessControlAction(out,params,response,username,groupnames);
|
|
323 | 323 |
} else if (action.equals("getprincipals")) { |
324 | 324 |
PrintWriter out = response.getWriter(); |
325 | 325 |
handleGetPrincipalsAction(out, username, password); |
... | ... | |
488 | 488 |
* @param conn the database connection |
489 | 489 |
*/ |
490 | 490 |
protected void handleSQuery(PrintWriter out, Hashtable params, |
491 |
HttpServletResponse response, String user, String group)
|
|
491 |
HttpServletResponse response, String user, String[] groups)
|
|
492 | 492 |
{ |
493 | 493 |
String xmlquery = ((String[])params.get("query"))[0]; |
494 | 494 |
String qformat = ((String[])params.get("qformat"))[0]; |
495 | 495 |
String resultdoc = null; |
496 | 496 |
|
497 |
Hashtable doclist = runQuery(xmlquery, user, group); |
|
497 |
Hashtable doclist = runQuery(xmlquery, user, groups);
|
|
498 | 498 |
|
499 | 499 |
resultdoc = createResultDocument(doclist, transformQuery(xmlquery)); |
500 | 500 |
|
... | ... | |
518 | 518 |
* @param response the response object linked to the client |
519 | 519 |
*/ |
520 | 520 |
protected void handleQuery(PrintWriter out, Hashtable params, |
521 |
HttpServletResponse response, String user, String group)
|
|
521 |
HttpServletResponse response, String user, String[] groups)
|
|
522 | 522 |
{ |
523 | 523 |
//create the query and run it |
524 | 524 |
String xmlquery = DBQuery.createSQuery(params); |
525 |
Hashtable doclist = runQuery(xmlquery, user, group); |
|
525 |
Hashtable doclist = runQuery(xmlquery, user, groups);
|
|
526 | 526 |
String qformat = ((String[])params.get("qformat"))[0]; |
527 | 527 |
String resultdoc = null; |
528 | 528 |
|
... | ... | |
577 | 577 |
* |
578 | 578 |
* @param xmlquery the query to run |
579 | 579 |
*/ |
580 |
private Hashtable runQuery(String xmlquery, String user, String group)
|
|
580 |
private Hashtable runQuery(String xmlquery, String user, String[] groups)
|
|
581 | 581 |
{ |
582 | 582 |
Hashtable doclist=null; |
583 | 583 |
Connection conn = null; |
... | ... | |
585 | 585 |
{ |
586 | 586 |
conn = util.getConnection(); |
587 | 587 |
DBQuery queryobj = new DBQuery(conn, saxparser); |
588 |
doclist = queryobj.findDocuments(new StringReader(xmlquery),user,group); |
|
588 |
doclist = queryobj.findDocuments(new StringReader(xmlquery),user,groups);
|
|
589 | 589 |
util.returnConnection(conn); |
590 | 590 |
return doclist; |
591 | 591 |
} |
... | ... | |
670 | 670 |
* @param params the Hashtable of HTTP request parameters |
671 | 671 |
* @param response the HTTP response object linked to the client |
672 | 672 |
* @param user the username sent the request |
673 |
* @param group the user's groupname
|
|
673 |
* @param groups the user's groupnames
|
|
674 | 674 |
*/ |
675 | 675 |
private void handleReadAction(Hashtable params, HttpServletResponse response, |
676 |
String user, String group)
|
|
676 |
String user, String[] groups)
|
|
677 | 677 |
{ |
678 | 678 |
ServletOutputStream out = null; |
679 | 679 |
ZipOutputStream zout = null; |
... | ... | |
719 | 719 |
addDocToZip(docid, zout); |
720 | 720 |
} else { |
721 | 721 |
readFromMetacat(response, docid, qformat, abstrpath, |
722 |
user, group, zip, zout); |
|
722 |
user, groups, zip, zout);
|
|
723 | 723 |
} |
724 | 724 |
|
725 | 725 |
// case docid="http://.../filename" |
... | ... | |
739 | 739 |
addDocToZip(docid, zout); |
740 | 740 |
} else { |
741 | 741 |
readFromMetacat(response, docid, qformat, abstrpath, |
742 |
user, group, zip, zout); |
|
742 |
user, groups, zip, zout);
|
|
743 | 743 |
} |
744 | 744 |
} |
745 | 745 |
|
... | ... | |
775 | 775 |
// read metadata or data from Metacat |
776 | 776 |
private void readFromMetacat(HttpServletResponse response, String docid, |
777 | 777 |
String qformat, String abstrpath, String user, |
778 |
String group, boolean zip, ZipOutputStream zout)
|
|
778 |
String[] groups, boolean zip, ZipOutputStream zout)
|
|
779 | 779 |
throws ClassNotFoundException, IOException, SQLException, |
780 | 780 |
McdbException, Exception |
781 | 781 |
{ |
... | ... | |
1011 | 1011 |
* to the database connection |
1012 | 1012 |
*/ |
1013 | 1013 |
private void handleInsertOrUpdateAction(PrintWriter out, Hashtable params, |
1014 |
HttpServletResponse response, String user, String group) {
|
|
1014 |
HttpServletResponse response, String user, String[] groups) {
|
|
1015 | 1015 |
|
1016 | 1016 |
Connection conn = null; |
1017 | 1017 |
|
... | ... | |
1064 | 1064 |
accNumber = null; |
1065 | 1065 |
} |
1066 | 1066 |
newdocid = DocumentImpl.write(conn, xml, pub, dtd, doAction, |
1067 |
accNumber, user, group, validate); |
|
1067 |
accNumber, user, groups, validate);
|
|
1068 | 1068 |
} catch (NullPointerException npe) { |
1069 | 1069 |
newdocid = DocumentImpl.write(conn, xml, pub, dtd, doAction, |
1070 |
null, user, group, validate); |
|
1070 |
null, user, groups, validate);
|
|
1071 | 1071 |
} |
1072 | 1072 |
} finally { |
1073 | 1073 |
util.returnConnection(conn); |
... | ... | |
1176 | 1176 |
* from the database connection |
1177 | 1177 |
*/ |
1178 | 1178 |
private void handleDeleteAction(PrintWriter out, Hashtable params, |
1179 |
HttpServletResponse response, String user, String group) {
|
|
1179 |
HttpServletResponse response, String user, String[] groups) {
|
|
1180 | 1180 |
|
1181 | 1181 |
String[] docid = (String[])params.get("docid"); |
1182 | 1182 |
Connection conn = null; |
... | ... | |
1189 | 1189 |
// FOR EXISTENCE OF DOCID PARAM |
1190 | 1190 |
// BEFORE ACCESSING ARRAY |
1191 | 1191 |
try { |
1192 |
DocumentImpl.delete(conn, docid[0], user, group); |
|
1192 |
DocumentImpl.delete(conn, docid[0], user, groups);
|
|
1193 | 1193 |
response.setContentType("text/xml"); |
1194 | 1194 |
out.println("<?xml version=\"1.0\"?>"); |
1195 | 1195 |
out.println("<success>"); |
... | ... | |
1287 | 1287 |
*/ |
1288 | 1288 |
private void handleGetAccessControlAction(PrintWriter out, Hashtable params, |
1289 | 1289 |
HttpServletResponse response, |
1290 |
String username, String groupname) {
|
|
1290 |
String username, String[] groupnames) {
|
|
1291 | 1291 |
|
1292 | 1292 |
Connection conn = null; |
1293 | 1293 |
String docid = ((String[])params.get("docid"))[0]; |
... | ... | |
1297 | 1297 |
// get connection from the pool |
1298 | 1298 |
conn = util.getConnection(); |
1299 | 1299 |
AccessControlList aclobj = new AccessControlList(conn); |
1300 |
String acltext = aclobj.getACL(docid, username, groupname); |
|
1300 |
String acltext = aclobj.getACL(docid, username, groupnames);
|
|
1301 | 1301 |
out.println(acltext); |
1302 | 1302 |
|
1303 | 1303 |
} catch (Exception e) { |
... | ... | |
1529 | 1529 |
// Get the session information |
1530 | 1530 |
String username = null; |
1531 | 1531 |
String password = null; |
1532 |
String groupname = null;
|
|
1532 |
String[] groupnames = null;
|
|
1533 | 1533 |
String sess_id = null; |
1534 | 1534 |
|
1535 | 1535 |
// be aware of session expiration on every request |
... | ... | |
1541 | 1541 |
} else { |
1542 | 1542 |
username = (String)sess.getAttribute("username"); |
1543 | 1543 |
password = (String)sess.getAttribute("password"); |
1544 |
groupname = (String)sess.getAttribute("groupname");
|
|
1544 |
groupnames = (String[])sess.getAttribute("groupnames");
|
|
1545 | 1545 |
try { |
1546 | 1546 |
sess_id = (String)sess.getId(); |
1547 | 1547 |
} catch(IllegalStateException ise) { |
... | ... | |
1554 | 1554 |
if ( action.equals("upload")) { |
1555 | 1555 |
if (username != null && !username.equals("public")) { |
1556 | 1556 |
handleUploadAction(request, response, params, fileList, |
1557 |
username, groupname); |
|
1557 |
username, groupnames);
|
|
1558 | 1558 |
} else { |
1559 | 1559 |
try { |
1560 | 1560 |
out = response.getWriter(); |
... | ... | |
1586 | 1586 |
private void handleUploadAction(HttpServletRequest request, |
1587 | 1587 |
HttpServletResponse response, |
1588 | 1588 |
Hashtable params, Hashtable fileList, |
1589 |
String username, String groupname)
|
|
1589 |
String username, String[] groupnames)
|
|
1590 | 1590 |
{ |
1591 | 1591 |
PrintWriter out = null; |
1592 | 1592 |
Connection conn = null; |
src/edu/ucsb/nceas/metacat/DBSAXHandler.java | ||
---|---|---|
63 | 63 |
private String action = null; |
64 | 64 |
private String docid = null; |
65 | 65 |
private String user = null; |
66 |
private String group = null;
|
|
66 |
private String[] groups = null;
|
|
67 | 67 |
private String pub = null; |
68 | 68 |
private Thread xmlIndex; |
69 | 69 |
private boolean endDocument = false; |
... | ... | |
97 | 97 |
* @param action - "INSERT" or "UPDATE" |
98 | 98 |
* @param docid to be inserted or updated into JDBC connection |
99 | 99 |
* @param user the user connected to MetaCat servlet and owns the document |
100 |
* @param group the group to which user belongs
|
|
100 |
* @param groups the groups to which user belongs
|
|
101 | 101 |
* @param pub flag for public "read" access on document |
102 | 102 |
* @param serverCode the serverid from xml_replication on which this document |
103 | 103 |
* resides. |
104 | 104 |
* |
105 | 105 |
*/ |
106 | 106 |
public DBSAXHandler(Connection conn, String action, String docid, |
107 |
String user, String group, String pub, int serverCode)
|
|
107 |
String user, String[] groups, String pub, int serverCode)
|
|
108 | 108 |
{ |
109 | 109 |
this(conn); |
110 | 110 |
this.action = action; |
111 | 111 |
this.docid = docid; |
112 | 112 |
this.user = user; |
113 |
this.group = group;
|
|
113 |
this.groups = groups;
|
|
114 | 114 |
this.pub = pub; |
115 | 115 |
this.serverCode = serverCode; |
116 | 116 |
this.xmlIndex = new Thread(this); |
117 | 117 |
} |
118 | 118 |
|
119 |
// NOT USED ANY MORE |
|
120 |
// public DBSAXHandler(Connection conn,String action,String docid, |
|
121 |
// String user, String group) |
|
122 |
// { |
|
123 |
// this(conn); |
|
124 |
// this.action = action; |
|
125 |
// this.docid = docid; |
|
126 |
// this.user = user; |
|
127 |
// this.group = group; |
|
128 |
// this.xmlIndex = new Thread(this); |
|
129 |
// //this.xmlIndex.setPriority(Thread.MIN_PRIORITY); |
|
130 |
// } |
|
131 |
|
|
132 | 119 |
/** SAX Handler that receives notification of beginning of the document */ |
133 | 120 |
public void startDocument() throws SAXException { |
134 | 121 |
MetaCatUtil.debugMessage("start Document"); |
... | ... | |
266 | 253 |
try { |
267 | 254 |
AccessControlList aclobj = |
268 | 255 |
new AccessControlList(dbconn, docid, new StringReader(xml), |
269 |
user, group, serverCode); |
|
256 |
user, groups, serverCode);
|
|
270 | 257 |
dbconn.commit(); |
271 | 258 |
} catch (SAXException e) { |
272 | 259 |
try { |
src/edu/ucsb/nceas/metacat/DocumentImpl.java | ||
---|---|---|
100 | 100 |
* @param docid the identifier of the document to be created |
101 | 101 |
* @param readNodes flag indicating whether the xmlnodes should be read |
102 | 102 |
*/ |
103 |
public DocumentImpl(Connection conn, String docid, boolean readNodes) throws McdbException |
|
103 |
public DocumentImpl(Connection conn, String docid, boolean readNodes) |
|
104 |
throws McdbException |
|
104 | 105 |
{ |
105 | 106 |
try { |
106 | 107 |
this.conn = conn; |
... | ... | |
825 | 826 |
* @param action the action to be performed (INSERT OR UPDATE) |
826 | 827 |
* @param docid the docid to use for the INSERT OR UPDATE |
827 | 828 |
* @param user the user that owns the document |
828 |
* @param group the group to which user belongs
|
|
829 |
* @param groups the groups to which user belongs
|
|
829 | 830 |
*/ |
830 | 831 |
public static String write(Connection conn,String filename, |
831 | 832 |
String pub, String dtdfilename, |
832 | 833 |
String action, String docid, String user, |
833 |
String group )
|
|
834 |
String[] groups )
|
|
834 | 835 |
throws Exception { |
835 | 836 |
|
836 | 837 |
Reader dtd = null; |
... | ... | |
838 | 839 |
dtd = new FileReader(new File(dtdfilename).toString()); |
839 | 840 |
} |
840 | 841 |
return write ( conn, new FileReader(new File(filename).toString()), |
841 |
pub, dtd, action, docid, user, group, false); |
|
842 |
pub, dtd, action, docid, user, groups, false);
|
|
842 | 843 |
} |
843 | 844 |
|
844 | 845 |
public static String write(Connection conn,Reader xml,String pub,Reader dtd, |
845 | 846 |
String action, String docid, String user, |
846 |
String group, boolean validate)
|
|
847 |
String[] groups, boolean validate)
|
|
847 | 848 |
throws Exception { |
848 |
return write(conn,xml,pub,dtd,action,docid,user,group,1,false,validate); |
|
849 |
return write(conn,xml,pub,dtd,action,docid,user,groups,1,false,validate);
|
|
849 | 850 |
} |
850 | 851 |
|
851 | 852 |
public static String write(Connection conn, Reader xml, String pub, |
852 | 853 |
String action, String docid, String user, |
853 |
String group )
|
|
854 |
String[] groups )
|
|
854 | 855 |
throws Exception { |
855 | 856 |
if(action.equals("UPDATE")) |
856 | 857 |
{//if the document is being updated then use the servercode from the |
857 | 858 |
//originally inserted document. |
858 | 859 |
DocumentImpl doc = new DocumentImpl(conn, docid); |
859 | 860 |
int servercode = doc.getServerlocation(); |
860 |
return write(conn, xml, pub, action, docid, user, group, servercode); |
|
861 |
return write(conn, xml, pub, action, docid, user, groups, servercode);
|
|
861 | 862 |
} |
862 | 863 |
else |
863 | 864 |
{//if the file is being inserted then the servercode is always 1 |
864 |
return write(conn, xml, pub, action, docid, user, group, 1); |
|
865 |
return write(conn, xml, pub, action, docid, user, groups, 1);
|
|
865 | 866 |
} |
866 | 867 |
} |
867 | 868 |
|
868 | 869 |
public static String write( Connection conn, Reader xml, |
869 | 870 |
String action, String docid, String user, |
870 |
String group, int serverCode )
|
|
871 |
String[] groups, int serverCode )
|
|
871 | 872 |
throws Exception |
872 | 873 |
{ |
873 |
return write(conn,xml,null,action,docid,user,group,serverCode); |
|
874 |
return write(conn,xml,null,action,docid,user,groups,serverCode);
|
|
874 | 875 |
} |
875 | 876 |
|
876 | 877 |
public static String write( Connection conn, Reader xml, String pub, |
877 | 878 |
String action, String docid, String user, |
878 |
String group, int serverCode)
|
|
879 |
String[] groups, int serverCode)
|
|
879 | 880 |
throws Exception |
880 | 881 |
{ |
881 |
return write(conn,xml,pub,null,action,docid,user,group, |
|
882 |
return write(conn,xml,pub,null,action,docid,user,groups,
|
|
882 | 883 |
serverCode,false,false); |
883 | 884 |
} |
884 | 885 |
|
885 | 886 |
public static String write( Connection conn, Reader xml, String pub, |
886 | 887 |
String action, String docid, String user, |
887 |
String group, int serverCode, boolean override)
|
|
888 |
String[] groups, int serverCode, boolean override)
|
|
888 | 889 |
throws Exception |
889 | 890 |
{ |
890 |
return write(conn,xml,pub,null,action,docid,user,group, |
|
891 |
return write(conn,xml,pub,null,action,docid,user,groups,
|
|
891 | 892 |
serverCode,override,false); |
892 | 893 |
} |
893 | 894 |
|
... | ... | |
901 | 902 |
* @param action the action to be performed (INSERT or UPDATE) |
902 | 903 |
* @param accnum the docid + rev# to use on INSERT or UPDATE |
903 | 904 |
* @param user the user that owns the document |
904 |
* @param group the group to which user belongs
|
|
905 |
* @param groups the groups to which user belongs
|
|
905 | 906 |
* @param serverCode the serverid from xml_replication on which this document |
906 | 907 |
* resides. |
907 | 908 |
* @param override flag to stop insert replication checking. |
... | ... | |
913 | 914 |
|
914 | 915 |
public static String write( Connection conn,Reader xml,String pub,Reader dtd, |
915 | 916 |
String action, String accnum, String user, |
916 |
String group, int serverCode, boolean override,
|
|
917 |
String[] groups, int serverCode, boolean override,
|
|
917 | 918 |
boolean validate) |
918 | 919 |
throws Exception |
919 | 920 |
{ |
... | ... | |
972 | 973 |
MetacatReplication.replLog("lock granted for " + accnum + " from " + |
973 | 974 |
server); |
974 | 975 |
XMLReader parser = initializeParser(conn, action, docid, validate, |
975 |
user, group, pub, serverCode, dtd); |
|
976 |
user, groups, pub, serverCode, dtd);
|
|
976 | 977 |
conn.setAutoCommit(false); |
977 | 978 |
parser.parse(new InputSource(xml)); |
978 | 979 |
conn.commit(); |
... | ... | |
1022 | 1023 |
if ( action.equals("UPDATE") ) { |
1023 | 1024 |
// check for 'write' permission for 'user' to update this document |
1024 | 1025 |
|
1025 |
if ( !hasPermission(conn, user, group, docid) ) { |
|
1026 |
if ( !hasPermission(conn, user, groups, docid) ) {
|
|
1026 | 1027 |
throw new Exception("User " + user + |
1027 | 1028 |
" does not have permission to update XML Document #" + accnum); |
1028 | 1029 |
} |
... | ... | |
1032 | 1033 |
try |
1033 | 1034 |
{ |
1034 | 1035 |
XMLReader parser = initializeParser(conn, action, docid, validate, |
1035 |
user, group, pub, serverCode, dtd); |
|
1036 |
user, groups, pub, serverCode, dtd);
|
|
1036 | 1037 |
conn.setAutoCommit(false); |
1037 | 1038 |
parser.parse(new InputSource(xml)); |
1038 | 1039 |
conn.commit(); |
... | ... | |
1062 | 1063 |
* @param docid the ID of the document to be deleted from the database |
1063 | 1064 |
*/ |
1064 | 1065 |
public static void delete( Connection conn, String accnum, |
1065 |
String user, String group )
|
|
1066 |
String user, String[] groups )
|
|
1066 | 1067 |
throws Exception |
1067 | 1068 |
{ |
1068 | 1069 |
// OLD |
... | ... | |
1082 | 1083 |
|
1083 | 1084 |
|
1084 | 1085 |
// check for 'write' permission for 'user' to delete this document |
1085 |
if ( !hasPermission(conn, user, group, docid) ) { |
|
1086 |
if ( !hasPermission(conn, user, groups, docid) ) {
|
|
1086 | 1087 |
throw new Exception("User " + user + |
1087 | 1088 |
" does not have permission to delete XML Document #" + accnum); |
1088 | 1089 |
} |
... | ... | |
1110 | 1111 |
} |
1111 | 1112 |
|
1112 | 1113 |
/** |
1113 |
* Check for "WRITE" permission on @docid for @user and/or @group |
|
1114 |
* Check for "WRITE" permission on @docid for @user and/or @groups
|
|
1114 | 1115 |
* from DB connection |
1115 | 1116 |
*/ |
1116 |
private static boolean hasPermission( Connection conn, String user, |
|
1117 |
String group, String docid)
|
|
1118 |
throws SQLException
|
|
1117 |
private static boolean hasPermission ( Connection conn, String user,
|
|
1118 |
String[] groups, String docid )
|
|
1119 |
throws SQLException
|
|
1119 | 1120 |
{ |
1120 |
// b' of the command line invocation |
|
1121 |
if ( (user == null) && (group == null) ) { |
|
1122 |
return true; |
|
1123 |
} |
|
1124 |
|
|
1125 |
// Check for WRITE permission on @docid for @user and/or @group |
|
1121 |
// Check for WRITE permission on @docid for @user and/or @groups |
|
1126 | 1122 |
AccessControlList aclobj = new AccessControlList(conn); |
1127 |
boolean hasPermission = aclobj.hasPermission("WRITE",user,docid); |
|
1128 |
if ( !hasPermission && group != null ) { |
|
1129 |
hasPermission = aclobj.hasPermission("WRITE",group,docid); |
|
1130 |
} |
|
1131 |
|
|
1132 |
return hasPermission; |
|
1123 |
return aclobj.hasPermission("WRITE", user, groups, docid); |
|
1133 | 1124 |
} |
1134 | 1125 |
|
1135 | 1126 |
/** |
... | ... | |
1137 | 1128 |
*/ |
1138 | 1129 |
private static XMLReader initializeParser(Connection conn, String action, |
1139 | 1130 |
String docid, boolean validate, |
1140 |
String user, String group, String pub,
|
|
1131 |
String user, String[] groups, String pub,
|
|
1141 | 1132 |
int serverCode, Reader dtd) |
1142 | 1133 |
throws Exception |
1143 | 1134 |
{ |
... | ... | |
1147 | 1138 |
// |
1148 | 1139 |
try { |
1149 | 1140 |
ContentHandler chandler = new DBSAXHandler(conn, action, docid, |
1150 |
user, group, pub, serverCode); |
|
1141 |
user, groups, pub, serverCode);
|
|
1151 | 1142 |
EntityResolver eresolver= new DBEntityResolver(conn, |
1152 | 1143 |
(DBSAXHandler)chandler, dtd); |
1153 | 1144 |
DTDHandler dtdhandler = new DBDTDHandler(conn); |
1154 | 1145 |
|
1155 | 1146 |
// Get an instance of the parser |
1156 |
MetaCatUtil util = new MetaCatUtil(); |
|
1157 |
String parserName = util.getOption("saxparser"); |
|
1147 |
String parserName = MetaCatUtil.getOption("saxparser"); |
|
1158 | 1148 |
parser = XMLReaderFactory.createXMLReader(parserName); |
1159 | 1149 |
|
1160 | 1150 |
// Turn on validation |
src/edu/ucsb/nceas/metacat/AccessControlList.java | ||
---|---|---|
66 | 66 |
private String parserName; |
67 | 67 |
private Stack elementStack; |
68 | 68 |
private String server; |
69 |
private String sep; |
|
69 | 70 |
|
70 | 71 |
private boolean processingDTD; |
71 | 72 |
private String user; |
72 |
private String group;
|
|
73 |
private String[] groups;
|
|
73 | 74 |
private String aclid; |
75 |
private int rev; |
|
74 | 76 |
private String docname; |
75 | 77 |
private String doctype; |
76 | 78 |
private String systemid; |
... | ... | |
82 | 84 |
private int permission; |
83 | 85 |
private String permType; |
84 | 86 |
private String permOrder; |
85 |
private String publicAcc; |
|
87 |
// private String publicAcc;
|
|
86 | 88 |
private String beginTime; |
87 | 89 |
private String endTime; |
88 | 90 |
private int ticketCount; |
89 | 91 |
private int serverCode = 1; |
92 |
|
|
93 |
private Vector aclObjects = new Vector(); |
|
90 | 94 |
|
91 | 95 |
/** |
92 | 96 |
* Construct an instance of the AccessControlList class. |
... | ... | |
108 | 112 |
* @param aclid the Accession# of the document with the acl data |
109 | 113 |
* @param acl the acl file containing acl data |
110 | 114 |
* @param user the user connected to MetaCat servlet and owns the document |
111 |
* @param group the group to which user belongs
|
|
115 |
* @param groups the groups to which user belongs
|
|
112 | 116 |
* @param serverCode the serverid from xml_replication on which this document |
113 | 117 |
* resides. |
114 | 118 |
*/ |
115 | 119 |
public AccessControlList(Connection conn, String aclid, Reader acl, |
116 |
String user, String group, int serverCode) |
|
117 |
throws SAXException, IOException, ClassNotFoundException |
|
120 |
String user, String[] groups, int serverCode) |
|
121 |
throws SAXException, IOException, ClassNotFoundException, |
|
122 |
Exception |
|
118 | 123 |
{ |
119 |
// Get an instance of the parser |
|
120 |
MetaCatUtil util = new MetaCatUtil(); |
|
121 |
String parserName = util.getOption("saxparser"); |
|
122 |
this.server = util.getOption("server"); |
|
124 |
String parserName = MetaCatUtil.getOption("saxparser"); |
|
125 |
this.server = MetaCatUtil.getOption("server"); |
|
126 |
this.sep = MetaCatUtil.getOption("accNumSeparator"); |
|
123 | 127 |
|
124 | 128 |
this.conn = conn; |
125 | 129 |
this.parserName = parserName; |
... | ... | |
127 | 131 |
this.elementStack = new Stack(); |
128 | 132 |
|
129 | 133 |
this.user = user; |
130 |
this.group = group;
|
|
134 |
this.groups = groups;
|
|
131 | 135 |
this.aclid = aclid; |
136 |
DocumentImpl aclinfo = new DocumentImpl(conn,aclid,false); |
|
137 |
this.rev = aclinfo.getRev(); |
|
132 | 138 |
this.resourceURL = new Vector(); |
133 | 139 |
this.resourceID = new Vector(); |
134 | 140 |
this.principal = new Vector(); |
135 | 141 |
this.permission = 0; |
136 | 142 |
this.ticketCount = 0; |
137 |
this.publicAcc = null; |
|
143 |
// this.publicAcc = null;
|
|
138 | 144 |
this.serverCode = serverCode; |
139 | 145 |
|
140 | 146 |
// Initialize the parser and read the queryspec |
... | ... | |
152 | 158 |
* @param docid the Accession# of the document with the acl data |
153 | 159 |
* @param aclfilename the name of acl file containing acl data |
154 | 160 |
* @param user the user connected to MetaCat servlet and owns the document |
155 |
* @param group the group to which user belongs
|
|
161 |
* @param groups the groups to which user belongs
|
|
156 | 162 |
*/ |
157 | 163 |
public AccessControlList( Connection conn, String aclid, String aclfilename, |
158 |
String user, String group ) |
|
159 |
throws SAXException, IOException, ClassNotFoundException |
|
164 |
String user, String[] groups ) |
|
165 |
throws SAXException, IOException, ClassNotFoundException, |
|
166 |
Exception |
|
160 | 167 |
{ |
161 | 168 |
this(conn, aclid, new FileReader(new File(aclfilename).toString()), |
162 |
user, group, 1); |
|
169 |
user, groups, 1);
|
|
163 | 170 |
} |
164 | 171 |
|
165 | 172 |
/* Set up the SAX parser for reading the XML serialized ACL */ |
... | ... | |
196 | 203 |
//delete all previously submitted permissions @ relations |
197 | 204 |
//this happens only on UPDATE of the access file |
198 | 205 |
try { |
206 |
this.aclObjects = getACLObjects(aclid + sep + rev); |
|
207 |
|
|
199 | 208 |
if ( aclid != null ) { |
200 |
//first delete all permissions for resources related to @aclid if any
|
|
209 |
//delete all permissions for resources related to @aclid if any |
|
201 | 210 |
deletePermissionsForRelatedResources(aclid); |
202 |
//then delete all relations with docid of @aclid if any |
|
203 |
deleteRelations(aclid); |
|
211 |
// //then delete all relations with docid of @aclid if any
|
|
212 |
// deleteRelations(aclid);
|
|
204 | 213 |
} |
205 | 214 |
} catch (SQLException sqle) { |
206 | 215 |
throw new SAXException(sqle); |
... | ... | |
223 | 232 |
currentNode.setAttribute(atts.getLocalName(i), atts.getValue(i)); |
224 | 233 |
} |
225 | 234 |
} |
226 |
if ( currentNode.getTagName().equals("resource") ) {
|
|
235 |
if ( currentNode.getTagName().equals("acl") ) {
|
|
227 | 236 |
permOrder = currentNode.getAttribute("order"); |
228 |
publicAcc = currentNode.getAttribute("public"); |
|
237 |
// publicAcc = currentNode.getAttribute("public");
|
|
229 | 238 |
} |
230 | 239 |
elementStack.push(currentNode); |
231 | 240 |
} |
... | ... | |
242 | 251 |
BasicNode currentNode = (BasicNode)elementStack.peek(); |
243 | 252 |
String currentTag = currentNode.getTagName(); |
244 | 253 |
|
245 |
if (currentTag.equals("resourceIdentifier")) {
|
|
254 |
if (currentTag.equals("principal")) {
|
|
246 | 255 |
|
247 |
// docid of the current resource |
|
248 |
String docid = getDocid(inputString); |
|
249 |
// URL string of the current resource |
|
250 |
// docurl is declared in the class |
|
251 |
try { |
|
252 |
docurl = (new URL(inputString)).toString(); |
|
253 |
} catch (MalformedURLException murle) { |
|
254 |
throw new SAXException(murle.getMessage()); |
|
255 |
} |
|
256 |
// collect them in Vector variables |
|
257 |
resourceID.addElement(docid); |
|
258 |
resourceURL.addElement(docurl); |
|
259 |
|
|
260 |
// if it is the local server (originator of the document), |
|
261 |
// check for permission for @user on resource is needed |
|
262 |
// @user must have permission "all" on it(docid) |
|
263 |
if ( serverCode == 1 ) { |
|
264 |
boolean hasPermission = false; |
|
265 |
try { |
|
266 |
hasPermission = hasPermission("ALL",user,docid); |
|
267 |
if ( !hasPermission && group != null ) { |
|
268 |
hasPermission = hasPermission("ALL",group,docid); |
|
269 |
} |
|
270 |
} catch (SQLException e) { |
|
271 |
throw new SAXException(e.getMessage()); |
|
272 |
} |
|
273 |
if ( !hasPermission ) { |
|
274 |
throw new SAXException( |
|
275 |
"Permission denied for setting access control on " + docid); |
|
276 |
} |
|
277 |
} |
|
278 |
// end of check for "all" perm on docid |
|
256 |
principal.addElement(inputString); |
|
279 | 257 |
|
280 |
} else if (currentTag.equals("principal")) {
|
|
258 |
} else if (currentTag.equals("permission")) {
|
|
281 | 259 |
|
282 |
principal.addElement(inputString); |
|
260 |
if ( inputString.trim().toUpperCase().equals("READ") ) { |
|
261 |
permission = permission | READ; |
|
262 |
} else if ( inputString.trim().toUpperCase().equals("WRITE") ) { |
|
263 |
permission = permission | WRITE; |
|
264 |
} else if ( inputString.trim().toUpperCase().equals("ALL") ) { |
|
265 |
permission = permission | ALL; |
|
266 |
} else { |
|
267 |
throw new SAXException("Unknown permission type: " + inputString); |
|
268 |
} |
|
283 | 269 |
|
284 |
} else if (currentTag.equals("permission")) { |
|
270 |
} else if ( currentTag.equals("startDate") && beginTime == null ) { |
|
271 |
beginTime = inputString.trim(); |
|
285 | 272 |
|
286 |
if ( inputString.trim().toUpperCase().equals("READ") ) { |
|
287 |
permission = permission | READ; |
|
288 |
} else if ( inputString.trim().toUpperCase().equals("WRITE") ) { |
|
289 |
permission = permission | WRITE; |
|
290 |
} else if ( inputString.trim().toUpperCase().equals("ALL") ) { |
|
291 |
permission = permission | ALL; |
|
292 |
} else { |
|
293 |
throw new SAXException("Unknown permission type: " + inputString); |
|
294 |
} |
|
273 |
} else if ( currentTag.equals("stopDate") && endTime == null) { |
|
274 |
endTime = inputString.trim(); |
|
295 | 275 |
|
296 |
} else if (currentTag.equals("duration") && |
|
297 |
beginTime == null && endTime == null ) { |
|
298 |
try { |
|
299 |
beginTime = inputString.substring(0, inputString.indexOf(" ")); |
|
300 |
endTime = inputString.substring(inputString.indexOf(" ")+1); |
|
301 |
} catch (StringIndexOutOfBoundsException se) { |
|
302 |
beginTime = inputString; |
|
276 |
} else if (currentTag.equals("ticketCount") && ticketCount == 0 ) { |
|
277 |
try { |
|
278 |
ticketCount = (new Integer(inputString.trim())).intValue(); |
|
279 |
} catch (NumberFormatException nfe) { |
|
280 |
throw new SAXException("Wrong integer format for:" + inputString); |
|
281 |
} |
|
303 | 282 |
} |
304 |
|
|
305 |
} else if (currentTag.equals("ticketCount") && ticketCount == 0 ) { |
|
306 |
try { |
|
307 |
ticketCount = (new Integer(inputString.trim())).intValue(); |
|
308 |
} catch (NumberFormatException nfe) { |
|
309 |
throw new SAXException("Wrong integer format for:" + inputString); |
|
310 |
} |
|
311 |
} |
|
312 | 283 |
} |
313 | 284 |
|
314 | 285 |
/** |
... | ... | |
322 | 293 |
BasicNode leaving = (BasicNode)elementStack.pop(); |
323 | 294 |
String leavingTagName = leaving.getTagName(); |
324 | 295 |
|
325 |
if ( leavingTagName.equals("resourceIdentifier") ) { |
|
296 |
if ( leavingTagName.equals("allow") || |
|
297 |
leavingTagName.equals("deny") ) { |
|
326 | 298 |
|
327 |
try { |
|
328 |
// make a relationship for @aclid on the current resource(docurl) |
|
329 |
if ( aclid != null ) { |
|
330 |
insertRelation(aclid, docurl); |
|
331 |
} |
|
332 |
} catch (SQLException sqle) { |
|
333 |
throw new SAXException(sqle); |
|
334 |
} |
|
335 |
|
|
336 |
} else if ( leavingTagName.equals("allow") || |
|
337 |
leavingTagName.equals("deny") ) { |
|
338 |
|
|
339 | 299 |
if ( permission > 0 ) { |
340 | 300 |
|
341 | 301 |
// insert into db calculated permission for the list of principals |
342 | 302 |
try { |
343 |
insertPermissions(leavingTagName); |
|
303 |
// System.out.println("before insertPermission " +leavingTagName); |
|
304 |
// go through the objects in xml_relation about this acl doc |
|
305 |
for (int i=0; i < aclObjects.size(); i++) { |
|
306 |
// docid of the current object |
|
307 |
String docid = (String)aclObjects.elementAt(i); |
|
308 |
DocumentIdentifier docID = new DocumentIdentifier(docid); |
|
309 |
docid = docID.getIdentifier(); |
|
310 |
// System.out.println(docid); |
|
311 |
insertPermissions(docid,leavingTagName); |
|
312 |
} |
|
313 |
|
|
344 | 314 |
} catch (SQLException sqle) { |
345 | 315 |
throw new SAXException(sqle); |
316 |
} catch (Exception e) { |
|
317 |
throw new SAXException(e); |
|
346 | 318 |
} |
347 | 319 |
} |
348 | 320 |
|
... | ... | |
353 | 325 |
endTime = null; |
354 | 326 |
ticketCount = 0; |
355 | 327 |
|
356 |
} else if ( leavingTagName.equals("resource") ) { |
|
357 |
|
|
358 |
// update public access for the list of resources |
|
359 |
if ( publicAcc != null ) { |
|
360 |
try { |
|
361 |
updatePublicAccess(publicAcc); |
|
362 |
} catch (SQLException sqle) { |
|
363 |
throw new SAXException(sqle); |
|
364 |
} |
|
365 |
} |
|
366 |
|
|
367 |
// reset the resource |
|
368 |
resourceID = new Vector(); |
|
369 |
resourceURL = new Vector(); |
|
370 |
permOrder = null; |
|
371 |
publicAcc = null; |
|
372 | 328 |
} |
373 | 329 |
|
374 | 330 |
} |
... | ... | |
384 | 340 |
docname = name; |
385 | 341 |
doctype = publicId; |
386 | 342 |
systemid = systemId; |
387 |
// processingDTD = true; |
|
388 | 343 |
} |
389 | 344 |
|
390 | 345 |
/** |
... | ... | |
421 | 376 |
return processingDTD; |
422 | 377 |
} |
423 | 378 |
|
424 |
/* Delete from db all permission for resources related to @aclid if any.*/
|
|
425 |
private void deletePermissionsForRelatedResources(String aclid)
|
|
379 |
/* Get all objects associated with @aclid from db.*/
|
|
380 |
private Vector getACLObjects(String aclid)
|
|
426 | 381 |
throws SQLException |
427 | 382 |
{ |
383 |
Vector aclObjects = new Vector(); |
|
428 | 384 |
// delete all acl records for resources related to @aclid if any |
429 |
Statement stmt = conn.createStatement(); |
|
430 |
stmt.execute("DELETE FROM xml_access WHERE accessfileid = '" + aclid + "'"); |
|
431 |
stmt.close(); |
|
385 |
PreparedStatement pstmt = conn.prepareStatement( |
|
386 |
"SELECT object FROM xml_relation " + |
|
387 |
"WHERE subject = ? " + |
|
388 |
"AND relationship = 'isAccessFileFor'"); |
|
389 |
pstmt.setString(1,aclid); |
|
390 |
pstmt.execute(); |
|
391 |
ResultSet rs = pstmt.getResultSet(); |
|
392 |
boolean hasRows = rs.next(); |
|
393 |
while (hasRows) { |
|
394 |
aclObjects.addElement(rs.getString(1)); |
|
395 |
hasRows = rs.next(); |
|
396 |
} |
|
397 |
|
|
398 |
pstmt.close(); |
|
399 |
|
|
400 |
return aclObjects; |
|
432 | 401 |
} |
433 | 402 |
|
434 |
/* Delete all of the relations with a docid of @docid from db connection.*/
|
|
435 |
private void deleteRelations(String docid)
|
|
403 |
/* Delete from db all permission for resources related to @aclid if any.*/
|
|
404 |
private void deletePermissionsForRelatedResources(String aclid)
|
|
436 | 405 |
throws SQLException |
437 | 406 |
{ |
407 |
// delete all acl records for resources related to @aclid if any |
|
438 | 408 |
Statement stmt = conn.createStatement(); |
439 |
stmt.execute("DELETE FROM xml_relation WHERE docid='" + docid + "'");
|
|
409 |
stmt.execute("DELETE FROM xml_access WHERE accessfileid = '" + aclid + "'");
|
|
440 | 410 |
stmt.close(); |
441 | 411 |
} |
442 | 412 |
|
443 |
/* Insert relationship for @aclid on @resourceURL into db connection.*/ |
|
444 |
private void insertRelation(String aclid, String resourceURL) |
|
445 |
throws SQLException |
|
446 |
{ |
|
447 |
String aclURL = "metacat://" + server + "/?docid=" + aclid; |
|
448 |
|
|
449 |
// insert relationship |
|
450 |
PreparedStatement pstmt; |
|
451 |
pstmt = conn.prepareStatement( |
|
452 |
"INSERT INTO xml_relation (docid,subject,relationship,object) " + |
|
453 |
"VALUES (?, ?, ?, ?)"); |
|
454 |
pstmt.setString(1, aclid); |
|
455 |
pstmt.setString(2, aclURL); |
|
456 |
pstmt.setString(3, "isaclfor"); |
|
457 |
pstmt.setString(4, resourceURL); |
|
458 |
|
|
459 |
pstmt.execute(); |
|
460 |
pstmt.close(); |
|
461 |
} |
|
462 |
|
|
463 | 413 |
/* Insert into db calculated permission for the list of principals */ |
464 |
private void insertPermissions( String permType ) |
|
414 |
private void insertPermissions(String docid, String permType )
|
|
465 | 415 |
throws SQLException |
466 | 416 |
{ |
467 | 417 |
PreparedStatement pstmt; |
... | ... | |
473 | 423 |
"begin_time,end_time,ticket_count, accessfileid) VALUES " + |
474 | 424 |
"(?,?,?,?,?,to_date(?,'mm/dd/yy'),to_date(?,'mm/dd/yy'),?,?)"); |
475 | 425 |
// Bind the values to the query |
426 |
pstmt.setString(1, docid); |
|
476 | 427 |
pstmt.setInt(3, permission); |
477 | 428 |
pstmt.setString(4, permType); |
478 | 429 |
pstmt.setString(5, permOrder); |
... | ... | |
484 | 435 |
} else { |
485 | 436 |
pstmt.setString(8, ""); |
486 | 437 |
} |
487 |
String docid; |
|
438 |
|
|
488 | 439 |
String prName; |
489 |
for ( int i = 0; i < resourceID.size(); i++ ) { |
|
490 |
docid = (String)resourceID.elementAt(i); |
|
491 |
pstmt.setString(1, docid); |
|
492 |
for ( int j = 0; j < principal.size(); j++ ) { |
|
493 |
prName = (String)principal.elementAt(j); |
|
494 |
pstmt.setString(2, prName); |
|
495 |
pstmt.execute(); |
|
496 |
|
|
497 |
// check if there are conflict with permission's order |
|
498 |
String permOrderOpos = permOrder; |
|
499 |
int perm = getPermissions(permission, prName, docid, permOrder); |
|
500 |
if ( perm != 0 ) { |
|
501 |
if ( permOrder.equals("allowFirst") ) { |
|
502 |
permOrderOpos = "denyFirst"; |
|
503 |
} else if ( permOrder.equals("denyFirst") ) { |
|
504 |
permOrderOpos = "allowFirst"; |
|
505 |
} |
|
506 |
throw new SQLException("Permission(s) " + txtValue(perm) + |
|
507 |
" for \"" + prName + "\" on document #" + docid + |
|
508 |
" has/have been used with \"" + permOrderOpos + "\""); |
|
440 |
for ( int j = 0; j < principal.size(); j++ ) { |
|
441 |
prName = (String)principal.elementAt(j); |
|
442 |
pstmt.setString(2, prName); |
|
443 |
pstmt.execute(); |
|
444 |
/* |
|
445 |
// check if there are conflict with permission's order |
|
446 |
String permOrderOpos = permOrder; |
|
447 |
int perm = getPermissions(permission, prName, docid, permOrder); |
|
448 |
if ( perm != 0 ) { |
|
449 |
if ( permOrder.equals("allowFirst") ) { |
|
450 |
permOrderOpos = "denyFirst"; |
|
451 |
} else if ( permOrder.equals("denyFirst") ) { |
|
452 |
permOrderOpos = "allowFirst"; |
|
509 | 453 |
} |
454 |
throw new SQLException("Permission(s) " + txtValue(perm) + |
|
455 |
" for \"" + prName + "\" on document #" + docid + |
|
456 |
" has/have been used with \"" + permOrderOpos + "\""); |
|
510 | 457 |
} |
458 |
*/ |
|
511 | 459 |
} |
512 | 460 |
pstmt.close(); |
513 | 461 |
|
... | ... | |
517 | 465 |
} |
518 | 466 |
} |
519 | 467 |
|
520 |
/* Update into db public "read" access for the list of resources. */ |
|
521 |
private void updatePublicAccess(String publicAcc) |
|
522 |
throws SQLException |
|
523 |
{ |
|
524 |
try { |
|
525 |
PreparedStatement pstmt; |
|
526 |
pstmt = conn.prepareStatement( |
|
527 |
"UPDATE xml_documents SET public_access = ?" + |
|
528 |
" WHERE docid = ?"); |
|
529 |
// Bind the values to the query |
|
530 |
if ( publicAcc == null ) { |
|
531 |
pstmt.setString(1, null); |
|
532 |
} else if ( publicAcc.toUpperCase().equals("YES") ) { |
|
533 |
pstmt.setInt(1, 1); |
|
534 |
} else { |
|
535 |
pstmt.setInt(1, 0); |
|
536 |
} |
|
537 |
for ( int i = 0; i < resourceID.size(); i++ ) { |
|
538 |
pstmt.setString(2, (String)resourceID.elementAt(i)); |
|
539 |
pstmt.execute(); |
|
540 |
} |
|
541 |
pstmt.close(); |
|
542 |
|
|
543 |
} catch (SQLException e) { |
|
544 |
throw new |
|
545 |
SQLException("AccessControlList.updatePublicAccess(): " + e.getMessage()); |
|
546 |
} |
|
547 |
} |
|
548 |
|
|
549 | 468 |
/* Get permissions with permission order different than @permOrder. */ |
550 | 469 |
private int getPermissions(int permission, String principal, |
551 | 470 |
String docid, String permOrder) |
... | ... | |
610 | 529 |
return txtPerm.append("\"").toString(); |
611 | 530 |
} |
612 | 531 |
|
613 |
/* Read docid from @url. */ |
|
614 |
private String getDocid ( String url ) throws SAXException |
|
532 |
/** |
|
533 |
* Check from db connection if at least one of the list of @principals |
|
534 |
* has @permission on @docid. |
|
535 |
* @param permission permission type to check for |
|
536 |
* @param principals list of names of principals to check for @permission |
|
537 |
* @param docid document identifier to check on |
|
538 |
*/ |
|
539 |
public boolean hasPermission(String permission, String user, |
|
540 |
String[] groups, String docid ) |
|
541 |
throws SQLException |
|
615 | 542 |
{ |
616 |
MetaCatUtil util = new MetaCatUtil(); |
|
617 |
try { |
|
618 |
URL urlobj = new URL(url); |
|
619 |
Hashtable urlParams = util.parseQuery(urlobj.getQuery()); |
|
620 |
if ( urlParams.containsKey("docid") ) { |
|
621 |
return (String)urlParams.get("docid"); // return the docid value |
|
622 |
} else { |
|
623 |
throw new |
|
624 |
SAXException("\"docid\" not specified within " + url); |
|
543 |
// b' of the command line invocation |
|
544 |
if ( (user == null) && (groups == null || groups.length == 0) ) { |
|
545 |
return true; |
|
546 |
} |
|
547 |
|
|
548 |
// Check for @permission on @docid for @user and/or @groups |
|
549 |
boolean hasPermission = hasPermission(permission,user,docid); |
|
550 |
int i = 0; |
|
551 |
if ( groups != null ) { |
|
552 |
while ( !hasPermission && i<groups.length ) { |
|
553 |
hasPermission = hasPermission(permission,groups[i++],docid); |
|
625 | 554 |
} |
626 |
} catch (MalformedURLException e) { |
|
627 |
throw new SAXException(e.getMessage()); |
|
628 | 555 |
} |
629 |
} |
|
556 |
// Check for @permission for "public" user |
|
557 |
if ( !hasPermission ) { |
|
558 |
hasPermission = hasPermission(permission,"public",docid); |
|
559 |
} |
|
560 |
|
|
561 |
return hasPermission; |
|
562 |
} |
|
630 | 563 |
|
631 |
|
|
632 | 564 |
/** |
633 |
* Check from db connection if @principal has @permission on @resourceID.
|
|
565 |
* Check from db connection if @principal has @permission on @docid.
|
|
634 | 566 |
* @param permission permission type to check for |
635 | 567 |
* @param principal name of principal to check for @permission |
636 |
* @param resourceID resource identifier to check on
|
|
568 |
* @param docid document identifier to check on
|
|
637 | 569 |
*/ |
638 |
public boolean hasPermission ( String permission,
|
|
639 |
String principal, String resourceID )
|
|
570 |
private boolean hasPermission(String permission,
|
|
571 |
String principal, String docid)
|
|
640 | 572 |
throws SQLException |
641 | 573 |
{ |
642 | 574 |
PreparedStatement pstmt; |
643 |
// check public access to @resourceID from xml_documents table
|
|
575 |
// check public access to @docid from xml_documents table
|
|
644 | 576 |
if ( permission.equals("READ") ) { |
645 | 577 |
try { |
646 | 578 |
pstmt = conn.prepareStatement( |
647 | 579 |
"SELECT 'x' FROM xml_documents " + |
648 | 580 |
"WHERE docid = ? AND public_access = 1"); |
649 | 581 |
// Bind the values to the query |
650 |
pstmt.setString(1, resourceID);
|
|
582 |
pstmt.setString(1, docid);
|
|
651 | 583 |
|
652 | 584 |
pstmt.execute(); |
653 | 585 |
ResultSet rs = pstmt.getResultSet(); |
... | ... | |
661 | 593 |
} catch (SQLException e) { |
662 | 594 |
throw new |
663 | 595 |
SQLException("AccessControlList.hasPermission(). " + |
664 |
"Error checking public access for document #"+resourceID+
|
|
596 |
"Error checking public access for document #"+docid+
|
|
665 | 597 |
". " + e.getMessage()); |
666 | 598 |
} |
667 | 599 |
} |
668 | 600 |
|
669 | 601 |
// since owner of resource has all permission on it, |
670 |
// check if @principal is owner of @resourceID in xml_documents table
|
|
602 |
// check if @principal is owner of @docid in xml_documents table
|
|
671 | 603 |
if ( principal != null ) { |
672 | 604 |
try { |
673 | 605 |
pstmt = conn.prepareStatement( |
674 | 606 |
"SELECT 'x' FROM xml_documents " + |
675 | 607 |
"WHERE docid = ? AND user_owner = ?"); |
676 | 608 |
// Bind the values to the query |
677 |
pstmt.setString(1, resourceID);
|
|
609 |
pstmt.setString(1, docid);
|
|
678 | 610 |
pstmt.setString(2, principal); |
679 | 611 |
|
680 | 612 |
pstmt.execute(); |
... | ... | |
690 | 622 |
throw new |
691 | 623 |
SQLException("AccessControlList.hasPermission(). " + |
692 | 624 |
"Error checking ownership for " + principal + |
693 |
" on document #" + resourceID + ". " + e.getMessage());
|
|
625 |
" on document #" + docid + ". " + e.getMessage());
|
|
694 | 626 |
} |
695 | 627 |
|
696 |
// check @principal's @permission on @resourceID from xml_access table
|
|
628 |
// check @principal's @permission on @docid from xml_access table
|
|
697 | 629 |
int accessValue = 0; |
698 | 630 |
int ticketCount = 0; |
699 | 631 |
String permOrder = ""; |
... | ... | |
709 | 641 |
"AND " + isnull + "(end_time," + sysdate + ")"); |
710 | 642 |
// check if it is "deny" with "allowFirst" first |
711 | 643 |
// Bind the values to the query |
712 |
pstmt.setString(1, resourceID);
|
|
644 |
pstmt.setString(1, docid);
|
|
713 | 645 |
pstmt.setString(2, principal); |
714 | 646 |
pstmt.setString(3, "deny"); |
715 | 647 |
|
... | ... | |
724 | 656 |
( permOrder.equals("allowFirst") ) && |
725 | 657 |
( rs.wasNull() || ticketCount > 0 ) ) { |
726 | 658 |
if ( !rs.wasNull() && ticketCount > 0 ) { |
727 |
decreaseNumberOfAccess(accessValue,principal,resourceID,"deny","allowFirst");
|
|
659 |
decreaseNumberOfAccess(accessValue,principal,docid,"deny","allowFirst");
|
|
728 | 660 |
} |
729 | 661 |
pstmt.close(); |
730 | 662 |
return false; |
... | ... | |
735 | 667 |
|
736 | 668 |
// it is not denied then check if it is "allow" |
737 | 669 |
// Bind the values to the query |
738 |
pstmt.setString(1, resourceID);
|
|
670 |
pstmt.setString(1, docid);
|
|
739 | 671 |
pstmt.setString(2, principal); |
740 | 672 |
pstmt.setString(3, "allow"); |
741 | 673 |
|
... | ... | |
749 | 681 |
if ( ( accessValue & intValue(permission) )==intValue(permission) && |
750 | 682 |
( rs.wasNull() || ticketCount > 0 ) ) { |
751 | 683 |
if ( !rs.wasNull() && ticketCount > 0 ) { |
752 |
decreaseNumberOfAccess(accessValue,principal,resourceID,"allow",permOrder);
|
|
684 |
decreaseNumberOfAccess(accessValue,principal,docid,"allow",permOrder);
|
|
753 | 685 |
} |
754 | 686 |
pstmt.close(); |
755 | 687 |
return true; |
... | ... | |
760 | 692 |
|
761 | 693 |
// it is not allowed then check if it is "deny" with "denyFirst" |
762 | 694 |
// Bind the values to the query |
763 |
pstmt.setString(1, resourceID);
|
|
695 |
pstmt.setString(1, docid);
|
|
764 | 696 |
pstmt.setString(2, principal); |
765 | 697 |
pstmt.setString(3, "deny"); |
766 | 698 |
|
... | ... | |
775 | 707 |
( permOrder.equals("denyFirst") ) && |
776 | 708 |
( rs.wasNull() || ticketCount > 0 ) ) { |
777 | 709 |
if ( !rs.wasNull() && ticketCount > 0 ) { |
778 |
decreaseNumberOfAccess(accessValue,principal,resourceID,"deny","denyFirst");
|
|
710 |
decreaseNumberOfAccess(accessValue,principal,docid,"deny","denyFirst");
|
|
779 | 711 |
} |
780 | 712 |
pstmt.close(); |
781 | 713 |
return false; |
... | ... | |
791 | 723 |
throw new |
792 | 724 |
SQLException("AccessControlList.hasPermission(). " + |
793 | 725 |
"Error checking " + permission + " permission for " + |
794 |
principal + " on document #" + resourceID + ". " +
|
|
726 |
principal + " on document #" + docid + ". " +
|
|
795 | 727 |
e.getMessage()); |
796 | 728 |
} |
797 | 729 |
} |
... | ... | |
799 | 731 |
return false; |
800 | 732 |
} |
801 | 733 |
|
802 |
/* Decrease the number of access to @resourceID for @principal in db. */
|
|
734 |
/* Decrease the number of access to @docid for @principal in db. */
|
|
803 | 735 |
private void decreaseNumberOfAccess(int permission, String principal, |
804 |
String resourceID, String permType,
|
|
736 |
String docid, String permType,
|
|
805 | 737 |
String permOrder) |
806 | 738 |
throws SQLException |
807 | 739 |
{ |
... | ... | |
817 | 749 |
" BETWEEN " + isnull + "(begin_time," + sysdate + ") " + |
818 | 750 |
"AND " + isnull + "(end_time," + sysdate + ")"); |
819 | 751 |
// Bind the values to the query |
820 |
pstmt.setString(1, resourceID);
|
|
752 |
pstmt.setString(1, docid);
|
|
821 | 753 |
pstmt.setString(2, principal); |
822 | 754 |
pstmt.setInt(3, permission); |
823 | 755 |
pstmt.setString(4, permType); |
... | ... | |
834 | 766 |
* access control information for a document specified by @docid. |
835 | 767 |
* @param docid document identifier which acl info to get |
836 | 768 |
* @param user name of user connected to Metacat system |
837 |
* @param group name of user's group to which user belongs
|
|
769 |
* @param groups names of user's groups to which user belongs
|
|
838 | 770 |
*/ |
839 |
public String getACL(String docid, String user, String group)
|
|
771 |
public String getACL(String docid, String user, String[] groups)
|
|
840 | 772 |
throws SQLException |
841 | 773 |
{ |
842 | 774 |
StringBuffer output = new StringBuffer(); |
... | ... | |
901 | 833 |
ticketCount = rs.getInt(8); |
902 | 834 |
|
903 | 835 |
// if @docid is not owned by @user, only ACL info from that |
904 |
// access files to which @user/@group has "read" permission |
|
836 |
// access files to which @user/@groups has "read" permission
|
|
905 | 837 |
// is extracted |
906 | 838 |
if ( !isOwned ) { |
907 | 839 |
if ( !acfid.equals(acfid_prev) ) { |
908 | 840 |
acfid_prev = acfid; |
909 |
hasPermission = hasPermission("READ",user,acfid); |
|
910 |
if ( !hasPermission && group != null ) { |
|
911 |
hasPermission = hasPermission("READ",group,acfid); |
|
912 |
} |
|
841 |
hasPermission = this.hasPermission("READ",user,groups,acfid); |
Also available in: Unified diff
added support for multiple group membership