You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 9 Next »

This Grouper enhancement facilitates secure subject attribute release.  This could be used to protect FERPA information, create private subdomains of Grouper where users from one might not be able to see subjects in another, or attributes which can only be seen by certain subjects.

In Grouper v2.0- there is no subject attribute security (unless it is baked into the custom subject source).

This is an interface point which can be implemented in Java to retrieve more attributes about a subject (i.e. attributes which may require another query, or security, or attributes not always needed when subjects are resolved).  When you implement this interface you should try to only use one (or few) queries, since this will be called a lot (for filters at least).

Implement the interface SubjectCustomizer by extending the class: edu.internet2.middleware.grouper.subj.SubjectCustomizerBase

/**
 * add the ability to decorate a list of subjects with more attributes.
 * note, while you are decorating, you can check security to see if the
 * groupersession is allowed to see those attributes
 * @author mchyzer
 *
 */
public interface SubjectCustomizer {

  /**
   * decorate subjects based on attributes requested
   * @param grouperSession
   * @param subjects
   * @param attributeNamesRequested
   * @return the subjects if same set, or make a new set
   */
  public Set<Subject> decorateSubjects(GrouperSession grouperSession, Set<Subject> subjects, Collection<String> attributeNamesRequested);
  
  /**
   * you can edit the subjects (or replace), but you shouldnt remove them
   * @param grouperSession
   * @param subjects
   * @param findSubjectsInStemName if this is a findSubjectsInStem call, this is the stem name.  This is useful
   * to filter when searching for subjects to add to a certain group
   * @return the subjects if same set, or make a new set
   */
  public Set<Subject> filterSubjects(GrouperSession grouperSession, Set<Subject> subjects, String findSubjectsInStemName);
 
  
}



Configure this in the grouper.properties:

# customize subjects by implementing this interface: edu.internet2.middleware.grouper.subj.SubjectCustomizer
# or extending this class: edu.internet2.middleware.grouper.subj.SubjectCustomizerBase (recommended)
# note the instance will be reused to make sure it is threadsafe
subjects.customizer.className = 

Note that the filter and decorator are batched for performance reasons, which is also why this is not configured in the subject source

The filterSubjects() method is called on each SubjectFinder call.  The decorateSubjects() is called when web services resolve subjects

There are two ways to protect data.  

1. You can change the data to protect it, the user will see the subject, but might only see the netId instead of the name.  Or it could just say (protected-data)

2. You could remove the subject from the results and the user will not know the subject exists

Here is an example of hiding names of students to users who arent allowed to see them:

package edu.internet2.middleware.grouper.subj;

import java.util.LinkedHashSet;
import java.util.Set;

import org.apache.commons.lang.StringUtils;

import edu.internet2.middleware.grouper.GrouperSession;
import edu.internet2.middleware.grouper.membership.GroupMembershipResult;
import edu.internet2.middleware.grouper.util.GrouperUtil;
import edu.internet2.middleware.subject.Subject;
import edu.internet2.middleware.subject.provider.SubjectImpl;

/**
 * filter students out from people who cant see them
 * @author mchyzer
 *
 */
public class SubjectCustomizerForDecoratorTesting extends SubjectCustomizerBase {

  /** student (protected data) group name */
  private static final String STUDENT_GROUP_NAME = "apps:subjectSecurity:groups:student";
  /** privileged employee group name */
  private static final String PRIVILEGED_EMPLOYEE_GROUP_NAME = "apps:subjectSecurity:groups:privilegedEmployee";

  /** source id we care about */
  private static final String SOURCE_ID = "jdbc";
  
  /**
   * @see SubjectCustomizer#filterSubjects(GrouperSession, Set)
   */
  @Override
  public Set<Subject> filterSubjects(GrouperSession grouperSession, Set<Subject> subjects) {
    
    //nothing to do if no results
    if (GrouperUtil.length(subjects) == 0) {
      return subjects;
    }
    
    //get results in one query
    GroupMembershipResult groupMembershipResult = calculateMemberships(subjects, IncludeGrouperSessionSubject.TRUE, 
        GrouperUtil.toSet(STUDENT_GROUP_NAME,PRIVILEGED_EMPLOYEE_GROUP_NAME));
    
    //see if the user is privileged
    boolean grouperSessionIsPrivileged = groupMembershipResult.hasMembership(PRIVILEGED_EMPLOYEE_GROUP_NAME, grouperSession.getSubject());
    
    //if so, we are done, they can see stuff
    if (grouperSessionIsPrivileged) {
      return subjects;
    }
    
    //loop through the subjects and see which are students, change their name and description to be their netId, with no other attributes
    Set<Subject> results = new LinkedHashSet<Subject>();
    for (Subject subject : subjects) {
      if (StringUtils.equals(SOURCE_ID, subject.getSourceId()) && groupMembershipResult.hasMembership(STUDENT_GROUP_NAME, subject)) {
        String netId = subject.getAttributeValue("netId");
        Subject replacementSubject = new SubjectImpl(subject.getId(), netId, netId, subject.getTypeName(), subject.getSourceId());
        results.add(replacementSubject);
      } else {
        results.add(subject);
      }
    }
    return results;
  }

  
  
}


Here is an example where subjects not in collab groups are filtered from users not in that group

package edu.internet2.middleware.grouper.subj;

import java.util.LinkedHashSet;
import java.util.Set;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;

import edu.internet2.middleware.grouper.GrouperSession;
import edu.internet2.middleware.grouper.membership.GroupMembershipResult;
import edu.internet2.middleware.grouper.util.GrouperUtil;
import edu.internet2.middleware.subject.Subject;

/**
 * filter out subjects not in the collaboration group
 * @author mchyzer
 *
 */
public class SubjectCustomizerForDecoratorTesting2 extends SubjectCustomizerBase {

  /** student (protected data) group name */
  private static final String COLLAB_STEM_NAME = "collaboration:collabGroups";

  /** privileged employee group name */
  private static final String PRIVILEGED_ADMIN_GROUP_NAME = "collaboration:etc:privilegedAdmin";

  /** source id we care about */
  private static final String SOURCE_ID = "jdbc";

  /**
   * @see SubjectCustomizer#filterSubjects(GrouperSession, Set)
   */
  @Override
  public Set<Subject> filterSubjects(GrouperSession grouperSession, Set<Subject> subjects) {
    
    //nothing to do if no results
    if (GrouperUtil.length(subjects) == 0) {
      return subjects;
    }
    
    //get results in one query
    GroupMembershipResult groupMembershipResult = calculateMembershipsInStems(subjects, IncludeGrouperSessionSubject.TRUE, 
        GrouperUtil.toSet(PRIVILEGED_ADMIN_GROUP_NAME), GrouperUtil.toSet(COLLAB_STEM_NAME));
    
    //see if the user is privileged
    boolean grouperSessionIsPrivileged = groupMembershipResult.hasMembership(PRIVILEGED_ADMIN_GROUP_NAME, grouperSession.getSubject());
    
    //if so, we are done, they can see stuff
    if (grouperSessionIsPrivileged) {
      return subjects;
    }
    
    //get the group names that the grouper session subject is in
    Set<String> grouperSessionGroupNames = groupMembershipResult.groupNamesInStem(grouperSession.getSubject(), COLLAB_STEM_NAME);
    
    //loop through the subjects and see which collab groups the users are in
    Set<Subject> results = new LinkedHashSet<Subject>();
    for (Subject subject : subjects) {
      //only protect one source
      if (!StringUtils.equals(SOURCE_ID, subject.getSourceId())) {
        results.add(subject);
      } else {
        Set<String> subjectGroupNames = groupMembershipResult.groupNamesInStem(subject, COLLAB_STEM_NAME);
        if (CollectionUtils.containsAny(grouperSessionGroupNames, subjectGroupNames)) {
          results.add(subject);
        }
      }
    }
    return results;
  }

  
  
}

Here is an example where extra attributes are securely retrieved

package edu.internet2.middleware.grouper.subj;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang.StringUtils;

import edu.internet2.middleware.grouper.GrouperSession;
import edu.internet2.middleware.grouper.Stem.Scope;
import edu.internet2.middleware.grouper.hibernate.HibUtils;
import edu.internet2.middleware.grouper.hibernate.HibernateSession;
import edu.internet2.middleware.grouper.membership.GroupMembershipResult;
import edu.internet2.middleware.grouper.membership.PermissionResult;
import edu.internet2.middleware.grouper.util.GrouperUtil;
import edu.internet2.middleware.subject.Subject;

/**
 * add attributes securely to the subject
 * @author mchyzer
 *
 */
public class SubjectCustomizerForDecoratorTesting3 extends SubjectCustomizerBase {

  /** stem name of the permission resources which represent columns in the attribute table */
  private static final String PERMISSIONS_STEM_NAME = "subjectAttributes:permissions:columnNames";

  /** privileged employee group name */
  private static final String PRIVILEGED_ADMIN_GROUP_NAME = "etc:privilegedAdmin";

  /** source id we care about */
  private static final String SOURCE_ID = "jdbc";

  
  /**
   * @see SubjectCustomizer#decorateSubjects(GrouperSession, Set, Collection)
   */
  @Override
  public Set<Subject> decorateSubjects(GrouperSession grouperSession,
      Set<Subject> subjects, Collection<String> attributeNamesRequested) {

    //nothing to do if no results or no attributes
    if (GrouperUtil.length(subjects) == 0 || GrouperUtil.length(attributeNamesRequested) == 0) {
      return subjects;
    }
    
    //get results in one query
    GroupMembershipResult groupMembershipResult = calculateMemberships(subjects, IncludeGrouperSessionSubject.TRUE, 
        GrouperUtil.toSet(PRIVILEGED_ADMIN_GROUP_NAME));

    
    //see if the user is privileged
    boolean grouperSessionIsPrivileged = groupMembershipResult.hasMembership(PRIVILEGED_ADMIN_GROUP_NAME, grouperSession.getSubject());
    
    //if so, we are done, they can see stuff
    if (grouperSessionIsPrivileged) {
      return subjects;
    }

    //see which attributes the user has access to based on permissions
    PermissionResult permissionResult =  calculatePermissionsInStem(null, 
        IncludeGrouperSessionSubject.TRUE, PERMISSIONS_STEM_NAME, Scope.ONE);
    
    //see which columns the user can see
    Set<String> columnsSet = permissionResult.permissionNameExtensions(PERMISSIONS_STEM_NAME, grouperSession.getSubject(), Scope.ONE);
    //intersect the columns the user can see with the ones requested
    columnsSet.retainAll(attributeNamesRequested);

    if (GrouperUtil.length(columnsSet) == 0) {
      return subjects;
    }

    List<String> columns = new ArrayList<String>(columnsSet);

    //get the list of subject ids
    Set<String> subjectIds = new LinkedHashSet<String>();
    for (Subject subject : subjects) {
      if (StringUtils.equals(SOURCE_ID, subject.getSourceId())) {
        subjectIds.add(subject.getId());
      }
    }
    
    //get the results of these columns for these subjects (by id)
    //make query
    StringBuilder sql = new StringBuilder("select id, ");
    sql.append(GrouperUtil.join(columns.iterator(), ','));
    sql.append(" from subject_attribute_table where id in( ");
    sql.append(HibUtils.convertToInClauseForSqlStatic(subjectIds));
    sql.append(")");
    
    //get the results from the DB
    List<String[]> dbResults = HibernateSession.bySqlStatic().listSelect(String[].class, sql.toString(), null);
    
    //index the results by id of row
    Map<String, String[]> dbResultLookup = new HashMap<String, String[]>();
    
    for(String[] row : dbResults) {
      dbResultLookup.put(row[0], row);
    }
    
    //loop through the subjects and match everything up
    for (Subject subject : subjects) {
      if (StringUtils.equals(SOURCE_ID, subject.getSourceId())) {
        String[] row = dbResultLookup.get(subject.getId());
        if (row != null) {
          //look through the attributes
          for (int i=0;i<columns.size();i++) {
            //add one to row index since first is id.  add if null or not, we need the attribute set
            subject.getAttributes().put(columns.get(0), GrouperUtil.toSet(row[i+1]));
          }
        }
      }
    }
    return subjects;
  }
  
}

sdf

  • No labels