The Grouper Custom UI can be applied per group in your Grouper system to provide special capabilities, including:

  • Helps end users and administrators view and troubleshoot access state and problems
  • (optional) Allows end users to easily opt in or opt out of a group without all the bells and whistles of the Grouper UI
  • When the user joins/leaves, or when a manager adds/removes someone, an optional custom email can be sent to the user

Grouper Custom UI is a new feature in API patch 2.4.96+ and included in Grouper v2.5+

Grouper Custom Templates via GSH are a different feature from Grouper Custom  UI.
See this page to learn about Custom Templates via GSH. 


To use Grouper Custom UI, configure this in the grouper.properties or on the Miscellaneous → Custom UI screen

Then there is link in the More Actions menu if you have the custom UI attributes assigned on the group


That link goes to the custom ui, here is an example from penn:


If a user is a manager (READ/UPDATE), then they will be able to pull users up, see the screen they see, see the answers to all their variables, and enroll or unenroll those users




The configuration used to be done with JSON from simple javabeans, but now is done in grouper.properties configuration or from the UI.  See the Atlassian Jira Custom UI example for the current way to configure


User query config bean

These configs identify variables that can be used in the screen to conditionally set text, adjust email text, etc.  They are set from some operation like checking a membership in a group or an LDAP or SQL call or something

The queries are of type: userQueryType which is from the enum: CustomUiUserQueryType: 

  • grouper: check a membership or privilege
  • ldap: execute an ldap filter
  • sql: run a sql query against grouper or another database
  • expressionLanguage: some expression (could call java)
  • azure: check an azure membership or user object
  • zoom: check zoom api and make attributes available (added 2.5.35+)

The queries assign variables which must be prefixed with "cu_" for "Custom UI"

You can configure a default that will fill in values for all config beans (maybe useful if there are a bunch of similar ldap calls)

FieldTypeRequired for typeOptional for typeDescription
allowAssignFromUrlBoolean
allIf a URL param can assign this URL for all users
attributeDefIdString
expressionLanguage, grouper, sqluuid of attribute def to look up
azureGroupIdStringazure
if hardcoding the uuid of group in azure
bindVar0String
sqlbind var for sql
bindVar0typeString
sqlbind var type in sql: string or integer
bindVar1String
sqlbind var for sql
bindVar1typeString
sqlbind var type in sql: string or integer
bindVar2String
sqlbind var for sql
bindVar2typeString
sqlbind var type in sql: string or integer
configIdStringazure, duo, ldap, zoomsqlid in grouper config for azure, duo, ldap, or sql
duoGroupNameStringduo
if checking a group membership in duo, this is the name of the group in duo
enabledBoolean
azure, duo, expressionLanguage, grouper, ldap, sql, zoomtrue or false if this var is enabled
errorLabelStringazure, zoomexpressionLanguage, duo, grouper, ldap, sqllabel on screen for the error variable 
fieldNamesString
groupercomma separated privs in grouper, e.g. members, readers, admins, viewers, updaters, optins, optouts, groupAttrReaders, groupAttrUpdaters, creators, stemAdmins, stemAttrReaders, stemAttrUpdaters, attrReaders, attrUpdaters, attrDefAttrReaders, attrDefAttrUpdaters, attrOptins, attrOptouts, attrAdmins
forLoggedInUserBoolean
azure, duo, expressionLanguage, grouper, ldap, sqltrue if should run this rule for the logged in user (if manager using screen), or by default its the user being acted on (which might be the user logged in)
groupIdString
azure, expressionLanguage, grouper, ldap, sqlgroup uuid to look up a group
groupNameString
azure, expressionLanguage, grouper, ldap, sqlgroup name to look up a group
labelStringazure, duo, expressionLanguage, grouper, ldap, sql, zoom
label to see on screen when variables are displayed
ldapAttributeToRetrieveStringldap
which attribute in ldap to retrieve
ldapFilterStringldap
ldap filter to run
ldapSearchDnString
ldapif not using the default dn in connection, search in this dn
nameOfAttributeDefString
expressionLanguage, grouper, sqlname of attribute definition to lookup
orderInteger
azure, duo, expressionLanguage, grouper, ldap, sql, zoominteger and rules will be ordered by this integer, when displayed on screen
queryStringsql
sql query to execute
scriptStringexpressionLanguageazure(2.5.30+), duo, zoomEL expression to run
stemIdString
expressionLanguage, grouper, sqluuid of stem to lookup
stemNameString
expressionLanguage, grouper, sqlname of stem to lookup
userQueryTypeStringazure, duo, expressionLanguage, grouper, ldap, sql, zoom
identify the type of query, enter either: azure, expressionLanguage, grouper, ldap, sql. dropdown.
variableToAssignStringazure, duo, expressionLanguage, grouper, ldap, sql, zoom
name of variable must start with cu_
if the value is "default" then each attribute value will be the default for any bean without that set where its a valid value. dropdown.
you cant have two variables with the same name
variableToAssignOnErrorStringazure, duo, zoomexpressionLanguage, grouper, ldap, sqlname of variable to assign on error, must start with cu_

you cant have two variables with the same name
variableTypeString
expressionLanguage, grouper, ldap, sql, azure, duo, zoomtype of variable: boolean, integer, string

Examples

Default user query bean to set some LDAP settings

{
   "variableToAssign":"default",
   "ldapSearchDn":"DC=one,DC=upenn,DC=edu",
   "ldapFilter":"(&(objectclass=user)(employeeID=${subject.getId()})(memberof=CN=${group.getName()},OU=Grouper,OU=365Groups,DC=one,DC=upenn,DC=edu))",
   "ldapAttributeToRetrieve":"employeeID"
}

Check if a user is in a group

{
   "variableToAssign":"cu_o365twoStepSelfEnrolled",
   "fieldNames":"members",
   "userQueryType":"grouper",
   "variableType":"boolean",
   "groupName":"penn:isc:ait:apps:O365:twoStepProd:simpleEnrollUnenroll:o365twoStepSelfEnrolled",
   "label":"${textContainer.text['penn_o365twoStep_cu_o365twoStepSelfEnrolled']}",
   "order":20
}

Note the label in the grouper.text.en.us.properties

penn_o365twoStep_cu_o365twoStepSelfEnrolled = Self-enrolled in PennO365 Two-Step Verification


If the person using the app (logged in user) is a manager (reader and updater on the group)

{
   "variableToAssign":"cu_o365twoStepAllowedToManage",
   "fieldNames":"updaters,readers",
   "userQueryType":"grouper",
   "variableType":"boolean",
   "groupName":"penn:isc:ait:apps:O365:twoStepProd:simpleEnrollUnenroll:o365twoStepSelfEnrolled",
   "label":"${textContainer.text['penn_o365twoStep_cu_o365twoStepAllowedToManage']}",
   "order":100,
   "forLoggedInUser":true
}

Note the label in the grouper.text.en.us.properties

penn_o365twoStep_cu_o365twoStepAllowedToManage = Manager of PennO365 Two-Step self enrollments

Find the min required date (custom attribute on group) that the operated on user is a member of

{
   "variableToAssign":"cu_o365twoStepRequiredDate",
   "bindVar0":"${subject.id}",
   "userQueryType":"sql",
   "variableType":"string",
   "configId":"grouper",
   "bindVar0type":"string",
   "query":"select min(value_string) from authz_o365_twostep_req_date_v where subject_id = ?",
   "label":"${textContainer.text['penn_o365twoStep_cu_o365twoStepRequiredDate']}",
   "order":50
}

The view is:

CREATE OR REPLACE VIEW penngrouper.authz_o365_twostep_req_date_v AS
 SELECT gaagv.value_string,
    gmlv.subject_id
   FROM grouper_aval_asn_group_v gaagv,
    grouper_memberships_lw_v gmlv
  WHERE gaagv.group_name = gmlv.group_name 
AND gmlv.list_name = 'members' 
AND gmlv.subject_source = 'pennperson' 
AND gaagv.enabled = 'T' 
AND gaagv.group_name like 'penn:isc:ait:apps:O365:twoStepProd:simpleEnrollUnenroll:o365twoStepGroupsToBeRequired:%' 
AND gaagv.attribute_def_name_name = 'penn:isc:ait:apps:O365:twoStepProd:simpleEnrollUnenroll:o365twoStepRequiredDate';


Ldap query (note: depends on default bean above)

{
   "variableToAssign":"cu_o365twoStepInLdap",
   "userQueryType":"ldap",
   "variableToAssignOnError":"cu_o365twoStepInLdapError",
   "groupName":"penn:isc:ait:apps:O365:twoStepProd:o365_two_step_prod",
   "label":"${textContainer.text['penn_o365twoStep_cu_o365twoStepInLdap']}",
   "errorLabel":"${textContainer.text['penn_o365twoStep_cu_o365twoStepInLdapError']}",
   "order":70,
   "configId":"oneProdAd"
}


Built-in variables

These variables are there for you to key off.  Note: you should not start your variable names with "cu_grouper" since that namespace is for built in variables

Variable nameDescription
cu_grouperEnrolltrue if the user clicked the enroll button, false if the user clicked the unenroll button (e.g. for email templates)
cu_grouperTurnOffManagerurl variable if you do not want to see the manager panel


Text config beans

These are a little misnamed, they are for text or for other decisions about the screen.  Could be a boolean result.  It is strongly encouraged to externalize text in the UI externalized text config

These generally take the variables defined above, and use boolean logic to decide which text to show or who is allowed to do certain things

The type of text is the decision or text to compute.  The engine will run through the config and append the ones that match, unless the one that matches says to stop processing.  You can also have defaults

Every text bean returns a string, but if its "true" or "false" it will be interpreted as a boolean.

Text typeTypeDescription
canAssignVariablesbooleanif the screen allows variables to be assigned in the URL for testing.  e.g. to simulate various users and see how the screen responds
note: only allow trusted users to be able to do this.  Only Grouper admins can do this by default
canSeeScreenStatebooleanif the screen state analysis should be displayed on the screen to help the user understand why access exists or not. 
By default only Grouper admins can see screen state.  Note that more columns of the user environment will also display
canSeeUserEnvironmentbooleanif the user variables and results should display.  By default group readers and updaters can see this.  Note that these are
abbreviated if the user cannoy also see screen state
emailBccGroupNameStringif there are emails and a group should be bcc'ed then return the group name here
emailBodyStringif there are emails then this is the body.  Note you can have a template that is dynamic, or different templates in different scenarios
emailSubjectStringif there are emails then this is the subject.  Note you can have a template that is dynamic, or different templates in different scenarios
emailToUserbooleantrue if an email should be sent to user.  Note you can send under certain circumstances if you like
enrollButtonShowbooleantrue if the enroll button should show.  Note that the user cant enroll if they dont have optin on the group
enrollButtonTextStringButton text of enroll button.  Defaults to: Enroll
enrollmentLabelStringText above the enrollment button that shows the state of the enrollment or whatever else
headerStringThe H1 of the page
helpLinkStringLink where the help button goes
instructions1StringInstructions at the top of the page
logoStringLink for logo
managerInstructionsStringInstructions to appear for readers/updaters who are managing users in this group
unenrollButtonShowbooleantrue if the unenroll button should show.  Note that the user cant enroll if they dont have optout on the group.  Note that
the enroll and unenroll button will not show at once
unenrollButtonTextStringButton text of unenroll button.  Defaults to: Unenroll
manageMembershipboolean(v2.5.38+) true or false, if the button will add the user to the group or remove them
redirectToUrlString(v2.5.38+) URL that the user should be redirected to after clicking button
gshScriptString(v2.5.38+) GSH script that should be run.  Note this is a stripped down version of GSH, so only use Java (no GSH functions), and 
fully qualify anything not in the base grouper or util java package.  This is for performance reasons.


GSH script to generate a text bean

    customUiTextConfigBean = new edu.internet2.middleware.grouper.ui.customUi.CustomUiTextConfigBean();

    customUiTextConfigBean.setCustomUiTextType(CustomUiTextType.instructions1.name());
    customUiTextConfigBean.setIndex(10);
    customUiTextConfigBean.setScript("${cu_o365twoStepRequiredToEnroll}");
    customUiTextConfigBean.setText("${textContainer.text['penn_o365twoStep_instructions_requiredToEnroll']}");
    customUiTextConfigBean.setEndIfMatches(true);

    System.out.println(GrouperUtil.jsonConvertTo(customUiTextConfigBean, false));

JSON which is generated

{
   "variableToAssign":"cu_o365twoStepRequiredToEnroll",
   "fieldNames":"members",
   "userQueryType":"grouper",
   "variableType":"boolean",
   "groupName":"penn:isc:ait:apps:O365:twoStepProd:o365_two_step_prod_policy",
   "label":"${textContainer.text['penn_o365twoStep_cu_o365twoStepRequiredToEnroll']}",
   "order":40
}

Examples

Always show header, note, this is the default for "header"

{
   "customUiTextType":"header",
   "defaultText":true,
   "text":"${textContainer.text['penn_o365twoStep_header']}"
}

In grouper.text.en.us.properties

penn_o365twoStep_header = <h1>PennO365 Two-Step Verification</h1>

For instructions1, if the operated on user is not required to enroll, if the are enrolled in o365/mfa, if they have a mailbox, and the are allowed to enroll/unenroll

{
   "endIfMatches":true,
   "customUiTextType":"instructions1",
   "index":20,
   "defaultText":false,
   "text":"${textContainer.text['penn_o365twoStep_instructions_willBeRequiredToEnroll']}",
   "script":"${ !cu_o365twoStepRequiredToEnroll && cu_o365twoStepEnrolled && cu_o365hasMailbox && cu_o365twoStepCanEnrollUnenroll}"
}

In grouper.text.en.us.properties.  Note the required date, calculated in a variable, is displayed on the screen

penn_o365twoStep_instructions_willBeRequiredToEnroll = To improve Penn's data security, you will be required to enroll in Two-Step Verification for O365 on <b>${cu_o365twoStepRequiredDate}</b>.<br /><br />

For instructions1, if the user operated on is not required to enroll, and not enrolled, and can enroll and unenroll, and is not enrolled in mfa

{
   "endIfMatches":true,
   "customUiTextType":"instructions1",
   "index":30,
   "defaultText":false,
   "text":"${textContainer.text['penn_o365twoStep_instructions_needsTwoStep']}",
   "script":"${!cu_o365twoStepRequiredToEnroll && !cu_o365twoStepEnrolled && cu_o365twoStepCanEnrollUnenroll && !cu_twoStepUsers}"
}

In grouper.text.en.us.properties.  

penn_o365twoStep_instructions_needsTwoStep = You are not enrolled in Two-Step Verification.  <a href="https://twostep.apps.upenn.edu/twoFactor/twoFactorUi/app/UiMain.index">Enroll now</a>.<br /><br />

Show the enroll button if not enrolled in o365/mfa, if can enroll/unenroll, if in mfa, if has a mailbox, show the button

{
   "endIfMatches":true,
   "customUiTextType":"enrollButtonShow",
   "index":0,
   "text":"${!cu_o365twoStepEnrolled && cu_o365twoStepCanEnrollUnenroll && cu_twoStepUsers && cu_o365hasMailbox}"
}

User environment

You can show the variables, and results, and links to the grouper admin, the group manager (default), an arbitrary group, or the end user.  This explains the access (if the user needs something, if the access is provisioned, etc)

Note, you can control who has access to the user environment table with the customUiTextType: canSeeUserEnvironment

Text state

You can show another level of debugging by showing the text state.  By default only grouper admins can see this but you can show it to an arbitrary group with the customUiTextType: canSeeScreenState.  This explains why the decisions and text were made and shown the way they were.

This shows three things:

  1. More columns in the user environment
  2. The decisions for the current user
  3. All text rules and how they are evaluated for the current user



Assign variables

If you are a Grouper admin (by default), or are allowed since customUiTextType: canAssignVariables.  You can send variables in the URL to simulate how the screen would look, to test all the rules

Someone to enroll
https://grouper.server.school.edu/grouper/grouperUi/app/UiV2Main.indexCustomUi?operation=UiV2CustomUi.customUiGroup&groupId=61bcaad67d57438ab1fea11c426c2f64&cu_o365twoStepAllowedToManage=false&cu_grouperTurnOffManager=true&cu_o365twoStepInAzure=false&cu_o365twoStepInAzureError=false&cu_o365twoStepCanEnrollUnenroll=true&cu_o365twoStepSelfEnrolled=false&cu_o365twoStepEnrolled=false&cu_o365twoStepRequiredToEnroll=false&cu_twoStepUsers=true&cu_o365hasMailbox=true&cu_o365twoStepInLdap=false&cu_o365twoStepInLdapError=false&cu_o365twoStepRequiredDate=2020/05/01

Someone enrolled
https://grouper.server.school.edu/grouper/grouperUi/app/UiV2Main.indexCustomUi?operation=UiV2CustomUi.customUiGroup&groupId=61bcaad67d57438ab1fea11c426c2f64&cu_o365twoStepAllowedToManage=false&cu_grouperTurnOffManager=true&cu_o365twoStepInAzure=true&cu_o365twoStepInAzureError=false&cu_o365twoStepCanEnrollUnenroll=true&cu_o365twoStepSelfEnrolled=true&cu_o365twoStepEnrolled=true&cu_o365twoStepRequiredToEnroll=false&cu_twoStepUsers=true&cu_o365hasMailbox=true&cu_o365twoStepInLdap=true&cu_o365twoStepInLdapError=false&cu_o365twoStepRequiredDate=2020/05/01

Azure membership

Configure an azure connection in grouper.properties

grouper.azureConnector.myAzure.loginEndpoint = https://login.microsoftonline.com
grouper.azureConnector.myAzure.DirectoryID = 6c4dxxx0d
grouper.azureConnector.myAzure.client_id = fd805xxxxdfb
grouper.azureConnector.myAzure.client_secret = ******************
grouper.azureConnector.myAzure.resource = https://graph.microsoft.com
grouper.azureConnector.myAzure.graphEndpoint = https://graph.microsoft.com
grouper.azureConnector.myAzure.graphVersion = v1.0
grouper.azureConnector.myAzure.groupLookupAttribute = displayName
grouper.azureConnector.myAzure.groupLookupValueFormat = ${group.getName()}
grouper.azureConnector.myAzure.requireSubjectAttribute = PENNNAME
grouper.azureConnector.myAzure.subjectIdValueFormat = ${subject.getAttributeValue("PENNNAME")}@upenn.edu


Run a membership check


See Also
Customizing UI (security headers)

Grouper Custom Template via GSH

  • No labels