Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Include Page
spaceKeyGrouper
pageTitleNavigation

Panel
borderColor#ccc
bgColor#FcFEFF
titleColorwhite
titleBGColor#00a400

Image Removed  This topic is discussed in the Lite UI - External Users training video.


Grouper has support for external or federated users (invitation, registry, self-serve attributes, etc).  In order for Grouper to be used in a Federated environment, external subjects must be able to be fed into a subject source.  In this case federated or external is similar, but the intent is that the subject id would be an eppn (whether or not the IdP's are in a federation).  However, the subject id's do not have to be an eppn, but they do need to be unique in the source.  This should be handled by an Identity management system, though generally it is not.  So to prevent many institutions from having to create their own custom process, Grouper has a built-in that can be used.  Note, a custom or IdM solution could be used instead, or it could be disabled entirely.  COmanage has a solution for this, and this solution is similar to theirs.

...

grouper.properties


Code Block

#####################################
## External subjects
#####################################


externalSubjects.desc.el = ${grouperUtil.appendIfNotBlankString(externalSubject.name, ' - ', externalSubject.institution)}
# true if the description should be managed via EL (config above)
externalSubjects.desc.manual = false

# quartz cron where subjects are recalculated if necessary (empty means dont run), e.g. everyday at 3am
externalSubjects.calc.fields.cron = 0 0 3 * * ?

externalSubjects.name.required = true
externalSubjects.email.required = false
externalSubjects.email.enabled = true

# these field names (uuid, institution, identifier, uuid, email, name) or attribute names
# will be toLowered, and appended with comma separators
externalSubjects.searchStringFields = name, institution, identifier, uuid, email, jabber

externalSubjects.institution.required = false
externalSubjects.institution.enabled = true

# note, this must be only alphanumeric lower case or underscore
# (valid db column name, subject attribute name)
externalSubjects.attributes.jabber.systemName = jabber
externalSubjects.attributes.jabber.required = false
# comment on column in DB (no special characters allowed)
externalSubjects.attributes.jabber.comment = The jabber ID of the user

# if wheel or root can edit external users
externalSubjects.wheelOrRootCanEdit = true

# group which is allowed to edit external users
externalSubjects.groupAllowedForEdit =

# if the view on the external subjects should be created.
# turn this off if it doesnt compile, othrewise should be fine
externalSubjects.createView = true

#name of external subject source, defaults to grouperExternal
externalSubject.sourceName = grouperExternal

# grouper can auto create a jdbc2 source for the external subjects
externalSubjects.autoCreateSource = false

# put in fully qualified classes to add to the EL context.  Note that they need a default constructor
# comma separated.  The alias will be the simple class name without a first cap.
# e.g. if the class is test.Test the alias is "test"
externalSubjects.customElClasses =

# change these to affect the storage where external subjects live (e.g. to store in ldap),
# must implement each respective storable interface
externalSubjects.storage.ExternalSubjectStorable.class = edu.internet2.middleware.grouper.externalSubjects.ExternalSubjectDbStorage
externalSubjects.storage.ExternalSubjectAttributeStorable.class = edu.internet2.middleware.grouper.externalSubjects.ExternalSubjectAttributeDbStorage

# you can use the variables $newline$, $inviteLink$.  Note, you need to change this default message...
externalSubjectsInviteDefaultEmail = Hello,$newline$$newline$This is an invitation to register at our site to be able to access our applications.  This invitation expires in 7 days.  Click on the link below and sign in with your InCommon credentials.  If you do not have InCommon credentials you can register at a site like protectnetwork.com and use those credentials.$newline$$newline$$inviteLink$$newline$$newline$Regards.

# default subject for email
externalSubjectsInviteDefaultEmailSubject = Register to access applications

# numner of days after which this request will expire.  If -1, then will not expire
externalSubjectsInviteExpireAfterDays = 7

#put some group names comma separated for groups to auto add subjects to
externalSubjects.autoaddGroups=
#should be insert, or update, or insert,update
externalSubjects.autoaddGroupActions=insert,update
#if a number is here, expire the group assignment after a certain number of days
externalSubjects.autoaddGroupExpireAfterDays=

#add multiple group assignment actions by URL param: externalSubjectInviteName
externalSubjects.autoadd.testingLibrary.externalSubjectInviteName=library
#comma separated groups to add for this type of invite
externalSubjects.autoadd.testingLibrary.groups=
#should be insert, update, or insert,update
externalSubjects.autoadd.testingLibrary.actions=insert,update
#should be insert, update, or insert,update
externalSubjects.autoadd.testingLibrary.expireAfterDays=

#if registrations are only allowed if invited or existing...
externalSubjects.registerRequiresInvite=true

#make sure the identifier when logging in is like an email address or eppn, e.g. username@school.edu
externalSubjects.validateIndentiferLikeEmail=true

#put regexes here, increment the 0 for multiple entries, e.g. restrict your own institution
#note, the extensions must be sequential (dont skip), regex e.g. ^.*@myschool.edu$
externalSubjects.regexForInvalidIdentifier.0=

...

The built in subject fields can be enabled/disabled or required or not.  Things like email are in the subject table since they might be common in deployments.


Code Block

externalSubjects.name.required = true
externalSubjects.email.required = false
externalSubjects.email.enabled = true
externalSubjects.institution.required = false
externalSubjects.institution.enabled = true

...

Grouper allows configuration of which external subject attributes to keep for all external users.  E.g. phone, email, jabber, firstName, lastName, etc.  This is in the grouper.properties. You can configure the friendly name, comment, if required, etc


Code Block

# this can change, and is shown on screen
externalSubjects.attributes.jabber.friendlyName = Jabber ID
# note, this must be only alphanumeric lower case or underscore
# (valid db column name, subject attribute name)
externalSubjects.attributes.jabber.systemName = jabber
externalSubjects.attributes.jabber.required = false
# comment on column in DB (no special characters allowed)
externalSubjects.attributes.jabber.comment = The jabber ID of the user

...

Auto provision into groups no matter the URL


Code Block

#put some group names comma separated for groups to auto add subjects to
externalSubjects.autoaddGroups=someStem:someGroup
#should be insert, or update, or insert,update
externalSubjects.autoaddGroupActions=insert,update
#if a number is here, expire the group assignment after a certain number of days
externalSubjects.autoaddGroupExpireAfterDays=

...

If you want to add to a group based on URL, use these configs (you can add multiple):


Code Block

#add multiple group assignment actions by URL param: externalSubjectInviteName
externalSubjects.autoadd.testingLibrary.externalSubjectInviteName=library
#comma separated groups to add for this type of invite
externalSubjects.autoadd.testingLibrary.groups=someStem:libraryUsers
#should be insert, update, or insert,update
externalSubjects.autoadd.testingLibrary.actions=insert,update
#should be insert, update, or insert,update
externalSubjects.autoadd.testingLibrary.expireAfterDays=365

#add multiple group assignment actions by URL param: externalSubjectInviteName
externalSubjects.autoadd.testingWhatever.externalSubjectInviteName=otherApplication
#comma separated groups to add for this type of invite
externalSubjects.autoadd.testingWhatever.groups=someStem:otherApplication
#should be insert, update, or insert,update
externalSubjects.autoadd.testingWhatever.actions=insert,update
#should be insert, update, or insert,update
externalSubjects.autoadd.testingWhatever.expireAfterDays=

...

If you are using shib, you can make sure the identifier is in the format a@b.c, and you can also filter out some regexes (e.g. filter out from your own institution by suffix).


Code Block

#make sure the identifier when logging in is like an email address or eppn, e.g. username@school.edu
externalSubjects.validateIndentiferLikeEmail=true

#put regexes here, increment the 0 for multiple entries, e.g. restrict your own institution
#note, the extensions must be sequential (dont skip), regex e.g. ^.*@myschool.edu$
externalSubjects.regexForInvalidIdentifier.0=

...

The storage is pluggable also so someone could use a different storage e.g. ldap.   Implement the external subject storable interfaces (one for subject, one for attribute).  The built in implementation just call the Grouper DAO to store in the Grouper DB


Code Block

# change these to affect the storage where external subjects live (e.g. to store in ldap),
# must implement each respective storable interface
externalSubjects.storage.ExternalSubjectStorable.class = edu.internet2.middleware.grouper.externalSubjects.ExternalSubjectDbStorage
externalSubjects.storage.ExternalSubjectAttributeStorable.class = edu.internet2.middleware.grouper.externalSubjects.ExternalSubjectAttributeDbStorage

...

Here are examples of the interfaces:


Code Block

package edu.internet2.middleware.grouper.externalSubjects;

import java.util.Set;

import edu.internet2.middleware.grouper.internal.dao.QueryOptions;

/**
 * implement this to change how external subjects are stored
 * @author mchyzer
 */
public interface ExternalSubjectStorable {

  /**
   * find all external subjects which have a disabled date which are not disabled
   * @return the set of subjects
   */
  public Set<ExternalSubject> findAllDisabledMismatch();

  /**
   * find all external subjects
   * @return the set of subjects
   */
  public Set<ExternalSubject> findAll();

  /**
   * find an external subject by identifier
   * @param identifier
   * @param exceptionIfNotFound
   * @param queryOptions
   * @return the external subject or null or exception
   */
  ExternalSubject findByIdentifier(String identifier, boolean exceptionIfNotFound, QueryOptions queryOptions);

  /**
   * delete an external subject and all its attributes
   * @param externalSubject
   */
  void delete(ExternalSubject externalSubject);

  /**
   * insert or update an external subject to the DB
   * @param externalSubject
   */
  void saveOrUpdate( ExternalSubject externalSubject );

}



Code Block

package edu.internet2.middleware.grouper.externalSubjects;

import java.util.Set;

import edu.internet2.middleware.grouper.internal.dao.QueryOptions;

/**
 * interface to implement to keep external subjects somewhere besides in the Grouper DB
 * @author mchyzer
 */
public interface ExternalSubjectAttributeStorable {
  /**
   * delete an external subject and all its attributes
   * @param externalSubjectAttribute
   */
  void delete(ExternalSubjectAttribute externalSubjectAttribute);

  /**
   * insert or update an external subject attribute to the DB
   * @param externalSubjectAttribute
   */
  void saveOrUpdate( ExternalSubjectAttribute externalSubjectAttribute );

  /**
   * find an external subject attribute by identifier
   * @param uuid
   * @param exceptionIfNotFound
   * @param queryOptions
   * @return the external subject or null or exception
   */
  ExternalSubjectAttribute findByUuid(String uuid, boolean exceptionIfNotFound, QueryOptions queryOptions);

  /**
   * find attributes by subject, order by system name
   * @param subjectUuid
   * @param queryOptions
   * @return the external subject or null or exception
   */
  Set<ExternalSubjectAttribute> findBySubject(String subjectUuid, QueryOptions queryOptions);

}

...

Some fields are or can be calculated.  The search string (string where subject searches are based on) is based on certain fields, and the description can be based on expression language.  These calculations will be recalculated whenever a subject is changed (or attributes), or when the daemon runs.


Code Block

externalSubjects.desc.el = ${grouperUtil.appendIfNotBlankString(externalSubject.name, ' - ', externalSubject.institution)}
# true if the description should be managed via EL (config above)
externalSubjects.desc.manual = false

# these field names (uuid, institution, identifier, uuid, email, name) or attribute names
# will be toLowered, and appended with comma separators
externalSubjects.searchStringFields = name, institution, identifier, uuid, email, jabber

...

For the expression language, you can add custom classes


Code Block

# put in fully qualified classes to add to the EL context.  Note that they need a default constructor
# comma separated.  The alias will be the simple class name without a first cap.
# e.g. if the class is test.Test the alias is "test"
externalSubjects.customElClasses =

...

Grouper can auto create a jdbc subject source 2 view for the subjects and attributes, and it can auto create a source for that view.  Or you can disabled that and do it yourself.


Code Block

# if the view on the external subjects should be created.
# turn this off if it doesnt compile, othrewise should be fine
externalSubjects.createView = true

# grouper can auto create a jdbc2 source for the external subjects
externalSubjects.autoCreateSource = true

...

The view will look like this (depending on which fields and attributes are enabled/included):


Code Block

CREATE VIEW grouper_ext_subj_v (uuid, name, identifier, description, institution, email, search_string_lower, jabber)
AS SELECT ges.uuid, ges.name, ges.identifier, ges.description , ges.institution , ges.email , ges.search_string_lower ,
(SELECT gesa.attribute_value FROM grouper_ext_subj_attr gesa WHERE gesa.subject_uuid = ges.uuid AND gesa.attribute_system_name = 'jabber' ) AS jabber
FROM grouper_ext_subj ges WHERE ges.enabled = 'T';

...

Wheel or root can be enabled to edit external subjects, or a group can be configured:


Code Block

# if wheel or root can edit external users
externalSubjects.wheelOrRootCanEdit = true

# group which is allowed to edit external users
externalSubjects.groupAllowedForEdit =

...

There is a daemon which runs in the grouper-loader on a cron which will recalculate the calculated fields (e.g. nightly).  This needs to be done if the configuration changes, or if data changes without recalculating (not sure why this would happen).  Just set the quartz cron in the grouper.properties


Code Block

# quartz cron where subjects are recalculated if necessary (empty means dont run), e.g. everyday at 3am
externalSubjects.calc.fields.cron = 0 0 3 * * ?

...

If you want to kick off the recalc manually (e.g. if you change how subjects look), you can do that in GSH:


Code Block

gsh 0% grouperSession = GrouperSession.startRootSession();
gsh 1% ExternalSubject.internal_daemonCalcFields();

...

These commands can create, edit, and delete external subjects


Code Block

gsh 0% grouperSession = GrouperSession.startRootSession();
edu.internet2.middleware.grouper.GrouperSession: 552eb161e98f47c4b98876528e36a409,'GrouperSystem','application'

//create a new external subject
gsh 1% externalSubject = new ExternalSubject();
edu.internet2.middleware.grouper.externalSubjects.ExternalSubject:
gsh 2% externalSubject.setIdentifier("abcd@school.edu");
gsh 3% externalSubject.setInstitution("My Institution");
gsh 4% externalSubject.setName("My Name");
gsh 5% externalSubject.setEmail("a@b.c");
gsh 6% externalSubject.store();

//assign an attribute
gsh 7% externalSubject.assignAttribute("jabber", "e@r.t");
true

//find the object to edit it
gsh 8% externalSubject = GrouperDAOFactory.getFactory().getExternalSubject().findByIdentifier("abcd@school.edu", true, null);
edu.internet2.middleware.grouper.externalSubjects.ExternalSubject: uuid: 759cf0f0b6874cef886a4f3138244125, identifier: abcd@school.edu, name: My Name, description: My Name - My Institution,

//search by subject in grouper by the subject api
gsh 9% subject = SubjectFinder.findByIdentifier("abcd@school.edu", true);
subject: id='759cf0f0b6874cef886a4f3138244125' type='person' source='grouperExternal' name='My Name'
gsh 10% subject.getName();
My Name

//edit the external subject
gsh 11% externalSubject.setName("My Name2");
gsh 12% externalSubject.store();

//find by any substring of the subject
gsh 14% subject = SubjectFinder.findAll("naMe2 mY INSTITUTION").iterator().next();
subject: id='759cf0f0b6874cef886a4f3138244125' type='person' source='grouperExternal' name='My Name2'

//note that all attributes are exposed by the subject api
gsh 15% subject.getAttributeValue("jabber");
e@r.t

//delete the external subject
gsh 16% externalSubject.delete();

//note its not there
gsh 17% subject = SubjectFinder.findByIdentifier("abcd@school.edu", false);

//recalculate descriptions (e.g. if the format has changed)
gsh 18% ExternalSubject.internal_daemonCalcFields();
24
gsh 19%

...

There is a new external servlet so that external users can be protected by Shib (or whatever), and the rest of the UI can be protected by a local singl sign on system.  Or both.  Or you could run the UI twice.  The URL of the external part is e.g.


Code Block

http://localhost:8090/grouper/grouperExternal/appHtml/grouper.html?operation=ExternalSubjectSelfRegister.externalSubjectSelfRegister

...

From grouper.properties


Code Block

#if registrations are only allowed if invited or existing...
externalSubjects.registerRequiresInvite=true

...

Configure this in the grouper.properties:


Code Block

#implement an external subject hook by extending edu.internet2.middleware.grouper.hooks.ExternalSubjectHooks
#hooks.externalSubject.class=edu.yourSchool.it.YourSchoolExternalSubjectHooks

...

The class you extend looks like this:


Code Block

public abstract class ExternalSubjectHooks {

  //*****  START GENERATED WITH GenerateMethodConstants.java *****//

  /** constant for method name for: postEditExternalSubject */
  public static final String METHOD_POST_EDIT_EXTERNAL_SUBJECT = "postEditExternalSubject";


  //*****  END GENERATED WITH GenerateMethodConstants.java *****//
  /**
   * called right after an edit of external subject (same transaction)
   * @param hooksContext
   * @param editBean
   */
  public void postEditExternalSubject(HooksContext hooksContext, HooksExternalSubjectBean editBean) {

  }
}

...

Starting with Grouper v2.4, External Subjects and Invites are handled by default in the New UI, and the Lite UI is no longer available in the standard setup.

...

You can restrict who has access to the invite screen with this setting in the media.properties (blank means everyone has access):


Code Block

#users must be in this group to invite external users to grouper
require.group.for.inviteExternalSubjects.logins=etc:externalSubjectInviters

...

You can allow wheel groups to have invites (normally this is not allowed)


Code Block

# if the wheel group is allowed to be invited
inviteExternalMembers.allowWheelInInvite = false

...

You can enable or disable the invite or registration screen (default is to not be enabled):


Code Block

# if the registration screen is enabled
externalMembers.enabledRegistration = false

# if the invitation screen is enabled
inviteExternalMembers.enableInvitation = false

...

The default template is in grouper.properties if an email message or subject is not specified in the invite:


Code Block

# you can use the variables $newline$, $inviteLink$.  Note, you need to change this default message...
externalSubjectsInviteDefaultEmail = Hello,$newline$$newline$This is an invitation to register at our site to be able to access our applications.  This invitation expires in 7 days.  Click on the link below and sign in with your InCommon credentials.  If you do not have InCommon credentials you can register at a site like protectnetwork.org and use those credentials.$newline$$newline$$inviteLink$$newline$$newline$Regards.
# default subject for email
externalSubjectsInviteDefaultEmailSubject = Register to access applications

...

An example of this email looks like this:


Code Block

From: "groupersystem@gmail.com" <groupersystem@gmail.com>    <-- this is configurable in grouper.properties
To: someuser@yahoo.com
Sent: Sun, November 28, 2010 10:42:21 AM
Subject: TEST:Register to access applications

Hello,

This is an invitation to register at our site to be able to access our applications.  This invitation expires in 7 days.  Click on the link below and sign in with your InCommon credentials.  If you do not have InCommon credentials you can register at a site like protectnetwork.org and use those credentials.

https://server.school.edu/grouper/grouperExternal/appHtml/grouper.html?operation=ExternalSubjectSelfRegister.externalSubjectSelfRegister&externalSubjectInviteId=55a61c064d62499d97650866824693cc

Regards.

...

If there are email addresses filled in to the invite screen, then people can be notified when people register (one email sent as each person registers).  The email format is specified in the grouper.properties:


Code Block

# you can use the variables $newline$, $inviteeIdentifier$, $inviteeEmailAddress$.  Note, you need to change this default message...
externalSubjectsNotifyInviterEmail = Hello,$newline$$newline$This is a notification that user $inviteeIdentifier$ from email address $inviteeEmailAddress$ has registered with the identity management service.  They can now use applications at this institution.$newline$$newline$Regards.
externalSubjectsNotifyInviterSubject = $inviteeIdentifier$ has registered

...

The email to the those people looks like this (depending on the template):


Code Block

From: "groupersystem@gmail.com" <groupersystem@gmail.com>    <-- note, this is configurable in grouper.properties
To: someone@someschool.edu
Sent: Sun, November 28, 2010 10:01:31 AM
Subject: TEST:user1@school.edu has registered                <-- in non prod env's a prefix can be specified in grouper.properties

Hello,

This is a notification that user user1@school.edu from email address person@yahoo.com has registered with the identity management service.  They can now use applications at this institution.

Regards.

...

Starting with Grouper v2.4, External Subjects and Invites are handled by default in the New UI, and the Lite UI is no longer available in the standard setup.

...

Set this in the media.properties:


Code Block

#if we should allow invite by identifier
inviteExternalMembers.allowInviteByIdentifier = true

...

Starting with Grouper v2.4, External Subjects and Invites are handled by default in the New UI, and the Lite UI is no longer available in the standard setup.

...

There is a link from the Admin UI group membership screen.  Enable this in the media.properties:


Code Block

inviteExternalPeople.link-from-admin-ui = true

...

Configure the button text and tooltip in nav.properties


Code Block

ui-lite.invite-link=Invite external people
tooltipTargetted.ui-lite.invite-link=Invite external people who are not already registered to be a member of this group.<br />Note: the systems that use this group must be ready to use external people<br/>(Opens in a new window)

...

Enable lite UI link from media.properties:


Code Block

inviteExternalPeople.link-from-lite-ui

...

Configure the button text and tooltip in nav.properties


Code Block

ui-lite.invite-menu=Invite external people
ui-lite.invite-menuTooltip=Invite external people who are not already registered to be a member of this group.  Note: the systems that use this group must be ready to use external people.

...

You can customize the text in the nav.properties:


Code Block

ui-lite.fromInvite-link=Lite UI
tooltipTargetted.ui-lite.fromInvite-link=Return to the lite membership management UI for this group

ui-lite.fromInvite-admin-link=Admin UI
tooltipTargetted.ui-lite.fromInvite-admin-link=Return to the admin UI for this group

...

If you enable admin emails for invites in media.properties:


Code Block

#if admins should be emailed after each action, put comma separated addresses here
inviteExternalMembers.emailAdminsAddressesAfterActions = mchyzer@isc.upenn.edu

...

Then this email is sent to the admins after each invitation:


Code Block

-----Original Message-----
From: noreply@yourschool.edu [mailto:noreply@yourschool.edu]   <-- this is configurable in grouper.properties
Sent: Monday, November 29, 2010 2:24 PM
To: Chris Hyzer
Subject: TEST:Grouper external person invitation


Hey,

The Grouper external subject invite screen was used by Subject id: GrouperSystem, sourceId: g:isa - GrouperSysAdmin

Email addresses to invite: someone@yahoo.com
Email addresses to notify once registered: inviter@school.edu
Email subject: test subject
Message to users: test body
Group Names (ID Path) to assign: etc:webServiceClientUsers
Group UUIDs to assign: 9662c7ee3e4d4e089a2ea8e376483601
Invitation expires on: 2010-12-06 14:24:16.826

Regards.

...

You can get emails to admins on registrations too, configure in media.properties


Code Block

#if admins should be emailed after each action, put comma separated addresses here
externalMembers.emailAdminsAddressesAfterActions = mchyzer@isc.upenn.edu

...

Here is an example of an email


Code Block

-----Original Message-----
From: noreply@school.edu                         <--- this is configurable in grouper.properties
Sent: Monday, November 29, 2010 3:40 PM
To: Chris Hyzer
Subject: TEST:Grouper external person registration


Hey,

The Grouper external person registration screen was used by user1@school.edu

User: uuid: 2e4b40f631b442ae828d669da3e14007, identifier: user1@school.edu, name: User One, description: User One - institution,
From invite? true
Edit type: update
Valid identifier: true
Message to user on screen: Success: your invitation from GrouperSysAdmin has been processed.  You were added to the roles: webServiceClientUsers.<br /><br />

Regards.

...

At Penn we didnt want external users all throughout our registry, we just wanted them in certain folders.  So at the root folder we disallowed external subjects, and in the app, we allowed them:


Code Block

 RuleApi.vetoSubjectAssignInFolderIfNotInGroup(SubjectFinder.findRootSubject(), rootStem, null, false, "grouperExternal", Stem.Scope.SUB, "rule.entity.cannot.be.external", "Person cannot be assigned if an external user");

RuleApi.vetoSubjectAssignInFolderIfNotInGroup(SubjectFinder.findRootSubject(), allowedStem, null, true, "grouperExternal", Stem.Scope.SUB, "rule.entity.can.be.external", "Person can be external");

...