Include Page | ||||
---|---|---|---|---|
|
On July 24, 2013, it was reported that Grouper WS getGrouperPrivilegesLite() was returning more data than the user should see. Here is what the problem is:
- Any valid WS user can call getGrouperPrivilegesLite WS operation specifying a subject and a group or stem, and if that subject is an admin, then the admin's privileges will be returned (e.g. if the admin can READ the group, ADMIN the group, etc)
- Any valid WS user can call getGrouperPrivilegesLite WS operation with a subject and no group or stem, and the privileges will be returned on objects for which the subject is an admin
- Any valid WS user can call getGrouperPrivilegesLite WS operation with a stem and all privileges will be returned
Note that if you have the grouper-ws.properties config option: ws.client.user.group.name, then the exposure is reduced since only valid WS users are connecting to the WS. All version of Grouper WS are affected by this problem: v1_4, v1_5, v1_6, v2_0, v2_1. This is fixed in all of the respective branches. If your release is older than July 24, 2013, then your version is affected.
Reproduce the problem
You need a group with an admin, and a web service user who is not an admin on the group (and not a wheel group member or sys admin). You might already have an example of this in Grouper, though if you dont you can customize this GSH script (change the strings at the top, with valid users and group) and run it. Note, on older versions, this script might not work, you can change the script or do this in the UI.
Code Block |
---|
groupName = "test:testPrivilegeIssue"; subjectIdWithAdminPriv = "test.subject.0"; subjectIdWithUpdatePriv = "test.subject.1"; subjectIdNoPrivs = "test.subject.2"; subjectIdMember = "test.subject.3"; grouperSession = GrouperSession.startRootSession(); group = new GroupSave(grouperSession).assignName(groupName).assignCreateParentStemsIfNotExist(true).save(); subjectWithAdminPriv = SubjectFinder.findByIdOrIdentifier(subjectIdWithAdminPriv, true); subjectWithUpdatePriv = SubjectFinder.findByIdOrIdentifier(subjectIdWithUpdatePriv, true); subjectNoPrivs = SubjectFinder.findByIdOrIdentifier(subjectIdNoPrivs, true); subjectMember = SubjectFinder.findByIdOrIdentifier(subjectIdMember, true); group.grantPriv(subjectWithAdminPriv, AccessPrivilege.ADMIN); group.grantPriv(subjectWithUpdatePriv, AccessPrivilege.UPDATE); group.addMember(subjectMember); |
This shows the problem, fast/medley.isc-seo.upenn.edu has no admin privileges on the group, mchyzer does, but the non-privileged user can see privileges on the group
Code Block |
---|
[mchyzer@flash pennGroupsClient-test-2.0.0]$ grep webService.kerberosPrin grouper.client.properties grouperClient.webService.kerberosPrincipal = fast/medley.isc-seo.upenn.edu [mchyzer@flash pennGroupsClient-test-2.0.0]$ java -jar grouperClient.jar --operation=getGrouperPrivilegesLiteWs --groupName=test:testGroup --subjectIdentifier=mchyzer Index 0: success: T: code: SUCCESS: group: test:testGroup: subject: 10021368: access: admin Index 1: success: T: code: SUCCESS: group: test:testGroup: subject: 10021368: access: read Index 2: success: T: code: SUCCESS: group: test:testGroup: subject: 10021368: access: update Index 3: success: T: code: SUCCESS: group: test:testGroup: subject: 10021368: access: view [mchyzer@flash pennGroupsClient-test-2.0.0]$ java -jar grouperClient.jar --operation=getGrouperPrivilegesLiteWs --groupName=test:testGroup --subjectId=fast/medley.isc-seo.upenn.edu Index 0: success: T: code: SUCCESS: group: test:testGroup: subject: fast/medley.isc-seo.upenn.edu: access: read Index 1: success: T: code: SUCCESS: group: test:testGroup: subject: fast/medley.isc-seo.upenn.edu: access: view [mchyzer@flash pennGroupsClient-test-2.0.0]$ |
Patch the server
You need to edit GrouperServiceLogic.java. Note, these instructions are tomcat-specific. Note, anytime you see /PATH_TO_GROUPER_WS_TOMCAT/ substitute that for the path to that tomcat, and anytime you see grouperWsAppName, substitute that for the app name you use for grouper WS, which is generally grouper-ws or grouperWs.
Unzip the grouper-ws.jar file in a location where you can compile a classfile. These instructions are for linux, though could be used in windows if you change the path separators (colon to semicolon)
Code Block |
---|
[appadmin@theprince lib]$ mkdir /tmp/grouperWsPatch [appadmin@theprince lib]$ cd /tmp/grouperWsPatch [appadmin@theprince grouperWsPatch]$ mkdir temp [appadmin@theprince grouperWsPatch]$ cd temp [appadmin@theprince temp]$ locate grouper-ws.jar /PATH_TO_GROUPER_WS_TOMCAT/webapps/grouperWsAppName/WEB-INF/lib/grouper-ws.jar [appadmin@theprince temp]$ unzip /PATH_TO_GROUPER_WS_TOMCAT/webapps/grouperWsAppName/WEB-INF/lib/grouper-ws.jar [appadmin@theprince temp]$ cd .. [appadmin@theprince grouperWsPatch]$ mkdir -p edu/internet2/middleware/grouper/ws [appadmin@theprince grouperWsPatch]$ cp temp/edu/internet2/middleware/grouper/ws/GrouperServiceLogic.java edu/internet2/middleware/grouper/ws [appadmin@theprince grouperWsPatch]$ rm -rf temp/ [appadmin@theprince grouperWsPatch]$ emacs edu/internet2/middleware/grouper/ws/GrouperServiceLogic.java |
Add this near the top of the file:
Code Block |
---|
import java.util.HashMap; import edu.internet2.middleware.grouper.GroupFinder; import edu.internet2.middleware.grouper.exception.GrouperSessionException; import edu.internet2.middleware.grouper.misc.GrouperSessionHandler; |
Add one line to the method: getGrouperPrivilegesLite
FROM:
Code Block |
---|
//see if we need to remove, if specifying privs, and this doesnt match Iterator<? extends GrouperPrivilege> iterator = privileges.iterator(); while (iterator.hasNext()) { GrouperPrivilege current = iterator.next(); if (privilegeName != null && !StringUtils.equals(privilegeName.getName(), current.getName())){ iterator.remove(); } } WsGrouperPrivilegeResult[] privilegeResults = new WsGrouperPrivilegeResult[privileges.size()]; |
TO:
Code Block |
---|
//see if we need to remove, if specifying privs, and this doesnt match Iterator<? extends GrouperPrivilege> iterator = privileges.iterator(); while (iterator.hasNext()) { GrouperPrivilege current = iterator.next(); if (privilegeName != null && !StringUtils.equals(privilegeName.getName(), current.getName())){ iterator.remove(); } } //ADD THIS LINE: GRP-923 WS getGrouperPrivilegesLite can return more data should removePrivsNotAllowedToSee(privileges); WsGrouperPrivilegeResult[] privilegeResults = new WsGrouperPrivilegeResult[privileges.size()]; |
Add this method below that method:
Code Block |
---|
/** * remove privileges not allowed to see * @param privileges */ public static void removePrivsNotAllowedToSee(TreeSet<GrouperPrivilege> privileges) { int originalNumberOfPrivileges = GrouperUtil.length(privileges); if (privileges != null) { //subject who is making the query final Subject grouperSessionSubject = GrouperSession.staticGrouperSession().getSubject(); //if this change breaks an app, and you need a quick fix, you can whitelist users final String groupNameOfUsersWhoCanCheckAllPrivileges = GrouperWsConfig.getPropertyString("ws.groupNameOfUsersWhoCanCheckAllPrivileges"); //if there is a whitelist to preserve old broken behavior if (!StringUtils.isBlank(groupNameOfUsersWhoCanCheckAllPrivileges)) { //do this as root since the user who is allowed might not be able to read the whitelist group... boolean done = (Boolean)GrouperSession.callbackGrouperSession(GrouperSession.staticGrouperSession().internal_getRootSession(), new GrouperSessionHandler() { public Object callback(GrouperSession grouperSession1) throws GrouperSessionException { Group groupOfUsersWhoCanCheckAllPrivileges = GroupFinder.findByName(grouperSession1, groupNameOfUsersWhoCanCheckAllPrivileges, false); if (groupOfUsersWhoCanCheckAllPrivileges != null) { //if the subject in the grouper session is in the whitelist group, then allow the query without filtering privileges if (groupOfUsersWhoCanCheckAllPrivileges.hasMember(grouperSessionSubject)) { return true; } } else { //it is misconfigured, just keep going, but filter privileges based on calling user LOG.error("Why is ws.groupNameOfUsersWhoCanCheckAllPrivileges: " + groupNameOfUsersWhoCanCheckAllPrivileges + ", not found????"); } return false; } }); //this means the calling user is in the whitelist for the old bad logic... if (done) { return; } } //map of group name to if the user is allowed to see privileges Map<String, Boolean> groupPrivilegeCache = new HashMap<String, Boolean>(); //map of stem name to if the user is allowed to see privileges Map<String, Boolean> stemPrivilegeCache = new HashMap<String, Boolean>(); Iterator<GrouperPrivilege> iterator = privileges.iterator(); while (iterator.hasNext()) { GrouperPrivilege grouperPrivilege = iterator.next(); GrouperAPI grouperApi = grouperPrivilege.getGrouperApi(); if (grouperApi instanceof Group) { Group group = (Group)grouperApi; String groupName = group.getName(); //check the cache Boolean allowed = groupPrivilegeCache.get(groupName); if (allowed == null) { //not in cache //see if allowed allowed = group.hasAdmin(grouperSessionSubject); //add back to cache groupPrivilegeCache.put(group.getName(), allowed); } if (!allowed) { iterator.remove(); } } else if (grouperApi instanceof Stem) { Stem stem = (Stem)grouperApi; String stemName = stem.getName(); //check the cache Boolean allowed = stemPrivilegeCache.get(stemName); if (allowed == null) { //not in cache //see if allowed allowed = stem.hasStem(grouperSessionSubject); //add back to cache stemPrivilegeCache.put(stem.getName(), allowed); } if (!allowed) { iterator.remove(); } } else { //this should never happen throw new RuntimeException("Not expecting GrouperAPI of type: " + grouperApi.getClass() + ", " + grouperApi); } } } if (LOG.isDebugEnabled()) { LOG.debug("removePrivsNotAllowedToSee() from " + originalNumberOfPrivileges + " to " + GrouperUtil.length(privileges) + " privileges"); } } |
Compile the patched file, note, if you are using Java previous to 1.6, then you need to list out the jar files, instead of the asterisk. Install, and bounce tomcat
Code Block |
---|
[appadmin@theprince grouperWsPatch]$ javac -classpath "/PATH_TO_GROUPER_WS_TOMAT/webapps/grouperWsAppName/WEB-INF/lib/*" -sourcepath . edu/internet2/middleware/grouper/ws/GrouperServiceLogic.java [appadmin@theprince grouperWsPatch]$ find . ./edu ./edu/internet2 ./edu/internet2/middleware ./edu/internet2/middleware/grouper ./edu/internet2/middleware/grouper/ws ./edu/internet2/middleware/grouper/ws/GrouperServiceLogic$4$1.class ./edu/internet2/middleware/grouper/ws/GrouperServiceLogic$10.class ./edu/internet2/middleware/grouper/ws/GrouperServiceLogic$5.class ./edu/internet2/middleware/grouper/ws/GrouperServiceLogic$14.class ./edu/internet2/middleware/grouper/ws/GrouperServiceLogic$9.class ./edu/internet2/middleware/grouper/ws/GrouperServiceLogic$7.class ./edu/internet2/middleware/grouper/ws/GrouperServiceLogic$1$1.class ./edu/internet2/middleware/grouper/ws/GrouperServiceLogic$13.class ./edu/internet2/middleware/grouper/ws/GrouperServiceLogic$10$1.class ./edu/internet2/middleware/grouper/ws/GrouperServiceLogic.class ./edu/internet2/middleware/grouper/ws/GrouperServiceLogic$8.class ./edu/internet2/middleware/grouper/ws/GrouperServiceLogic$3.class ./edu/internet2/middleware/grouper/ws/GrouperServiceLogic$12$1.class ./edu/internet2/middleware/grouper/ws/GrouperServiceLogic$10$2.class ./edu/internet2/middleware/grouper/ws/GrouperServiceLogic$1.class ./edu/internet2/middleware/grouper/ws/GrouperServiceLogic$6.class ./edu/internet2/middleware/grouper/ws/GrouperServiceLogic$4.class ./edu/internet2/middleware/grouper/ws/GrouperServiceLogic$12.class ./edu/internet2/middleware/grouper/ws/GrouperServiceLogic$11.class ./edu/internet2/middleware/grouper/ws/GrouperServiceLogic$2.class ./edu/internet2/middleware/grouper/ws/GrouperServiceLogic.java [appadmin@theprince grouperWsPatch]$ cp -R edu /PATH_TO_GROUPER_WS_TOMAT/webapps/grouperWsAppName/WEB-INF/classes/ [appadmin@theprince grouperWsPatch]$ find /PATH_TO_GROUPER_WS_TOMAT/webapps/grouperWsAppName/WEB-INF/classes/edu /PATH_TO_GROUPER_WS_TOMCAT/webapps/grouperWsAppName/WEB-INF/classes/edu /PATH_TO_GROUPER_WS_TOMCAT/webapps/grouperWsAppName/WEB-INF/classes/edu/internet2 /PATH_TO_GROUPER_WS_TOMCAT/webapps/grouperWsAppName/WEB-INF/classes/edu/internet2/middleware /PATH_TO_GROUPER_WS_TOMCAT/webapps/grouperWsAppName/WEB-INF/classes/edu/internet2/middleware/grouper /PATH_TO_GROUPER_WS_TOMCAT/webapps/grouperWsAppName/WEB-INF/classes/edu/internet2/middleware/grouper/ws /PATH_TO_GROUPER_WS_TOMCAT/webapps/grouperWsAppName/WEB-INF/classes/edu/internet2/middleware/grouper/ws/GrouperServiceLogic$4$1.class /PATH_TO_GROUPER_WS_TOMCAT/webapps/grouperWsAppName/WEB-INF/classes/edu/internet2/middleware/grouper/ws/GrouperServiceLogic$10.class /PATH_TO_GROUPER_WS_TOMCAT/webapps/grouperWsAppName/WEB-INF/classes/edu/internet2/middleware/grouper/ws/GrouperServiceLogic$5.class /PATH_TO_GROUPER_WS_TOMCAT/webapps/grouperWsAppName/WEB-INF/classes/edu/internet2/middleware/grouper/ws/GrouperServiceLogic$14.class /PATH_TO_GROUPER_WS_TOMCAT/webapps/grouperWsAppName/WEB-INF/classes/edu/internet2/middleware/grouper/ws/GrouperServiceLogic$9.class /PATH_TO_GROUPER_WS_TOMCAT/webapps/grouperWsAppName/WEB-INF/classes/edu/internet2/middleware/grouper/ws/GrouperServiceLogic$7.class /PATH_TO_GROUPER_WS_TOMCAT/webapps/grouperWsAppName/WEB-INF/classes/edu/internet2/middleware/grouper/ws/GrouperServiceLogic$1$1.class /PATH_TO_GROUPER_WS_TOMCAT/webapps/grouperWsAppName/WEB-INF/classes/edu/internet2/middleware/grouper/ws/GrouperServiceLogic$13.class /PATH_TO_GROUPER_WS_TOMCAT/webapps/grouperWsAppName/WEB-INF/classes/edu/internet2/middleware/grouper/ws/GrouperServiceLogic$10$1.class /PATH_TO_GROUPER_WS_TOMCAT/webapps/grouperWsAppName/WEB-INF/classes/edu/internet2/middleware/grouper/ws/GrouperServiceLogic.class /PATH_TO_GROUPER_WS_TOMCAT/webapps/grouperWsAppName/WEB-INF/classes/edu/internet2/middleware/grouper/ws/GrouperServiceLogic$8.class /PATH_TO_GROUPER_WS_TOMCAT/webapps/grouperWsAppName/WEB-INF/classes/edu/internet2/middleware/grouper/ws/GrouperServiceLogic$3.class /PATH_TO_GROUPER_WS_TOMCAT/webapps/grouperWsAppName/WEB-INF/classes/edu/internet2/middleware/grouper/ws/GrouperServiceLogic$12$1.class /PATH_TO_GROUPER_WS_TOMCAT/webapps/grouperWsAppName/WEB-INF/classes/edu/internet2/middleware/grouper/ws/GrouperServiceLogic$10$2.class /PATH_TO_GROUPER_WS_TOMCAT/webapps/grouperWsAppName/WEB-INF/classes/edu/internet2/middleware/grouper/ws/GrouperServiceLogic$1.class /PATH_TO_GROUPER_WS_TOMCAT/webapps/grouperWsAppName/WEB-INF/classes/edu/internet2/middleware/grouper/ws/GrouperServiceLogic$6.class /PATH_TO_GROUPER_WS_TOMCAT/webapps/grouperWsAppName/WEB-INF/classes/edu/internet2/middleware/grouper/ws/GrouperServiceLogic$4.class /PATH_TO_GROUPER_WS_TOMCAT/webapps/grouperWsAppName/WEB-INF/classes/edu/internet2/middleware/grouper/ws/GrouperServiceLogic$12.class /PATH_TO_GROUPER_WS_TOMCAT/webapps/grouperWsAppName/WEB-INF/classes/edu/internet2/middleware/grouper/ws/GrouperServiceLogic$11.class /PATH_TO_GROUPER_WS_TOMCAT/webapps/grouperWsAppName/WEB-INF/classes/edu/internet2/middleware/grouper/ws/GrouperServiceLogic$2.class /PATH_TO_GROUPER_WS_TOMCAT/webapps/grouperWsAppName/WEB-INF/classes/edu/internet2/middleware/grouper/ws/GrouperServiceLogic.java [appadmin@theprince grouperWsPatch]$ |
Note, if you run on a cluster, you should zip up the files in the patch, and copy them to all servers that need them. Make sure the path is grouperWsAppName/WEB-INF/classes/edu/insternet2... Also, you should probably copy the source file and the clsasfiles in case there are further tweaks.
Bounce tomcat.
Test that it works by seeing the privs not delivered
Code Block |
---|
[mchyzer@flash pennGroupsClient-test-2.0.0]$ java -jar grouperClient.jar --operation=getGrouperPrivilegesLiteWs --groupName=test:testGroup --subjectIdentifier=mchyzer [mchyzer@flash pennGroupsClient-test-2.0.0]$ |
If you change the grouper.client.properties to a user who has admin, you should see stuff
Code Block |
---|
[mchyzer@flash pennGroupsClient-test-2.0.0]$ java -jar grouperClient.jar --operation=getGrouperPrivilegesLiteWs --groupName=test:testGroup --subjectId=fast/medley.isc-seo.upenn.edu Index 0: success: T: code: SUCCESS: group: test:testGroup: subject: fast/medley.isc-seo.upenn.edu: access: read Index 1: success: T: code: SUCCESS: group: test:testGroup: subject: fast/medley.isc-seo.upenn.edu: access: view |
If this new fixed behavior breaks existing clients who depend on the broken behavior
Ideally you would grant whatever privileges these clients need so that they can make legitimate WS calls.
If it is not feasible to do that, a workaround is to grant those client access to the old behavior (hopefully temporarily until they can fix their code). You can do this by configuring a group in the grouper-ws.properties:
Code Block |
---|
ws.groupNameOfUsersWhoCanCheckAllPrivileges = some:group:name |
Then add the authenticated WS user to that group, and they will get the old behavior.