Automatic cloning of SAS metadata user groups and roles

In this blog post, we tackle automating the SAS Intelligence Platform administrator’s task of replicating (cloning) metadata users’ groups and roles from one user to another.

As more people in an organization become new SAS users or move to new projects or teams, their data and applications access credentials need to be set up or modified in SAS Metadata. In this scenario, a typical request to SAS platform administrators looks like one of the following:

Add Mr. A Smith as SAS user and make him a member of the groups and roles matching those of Ms. B. Brown.

For an existing SAS user Mr. A. Smith, replace his memberships of the groups and roles to mimic those of Ms. B. Brown.

For an existing SAS user Mr. A. Smith, add to his memberships of the groups and roles those that are missing from those of Ms. B. Brown.

SAS administrators routinely fulfill such requests by manually clicking through SAS Users plug-in in SAS Management Console. However, when a number of groups/roles to assign becomes rather large, this seemingly straightforward process can become overwhelming and error-prone.

Let's change the conventional practice of manually replicating group memberships from one user to another. Let's do it programmatically, which will make it easy, repeatable, fast, consistent, efficient and reliable.

Using SAS metadata functions to GET user-group associations

While you can employ macro %MDUEXTR to produce a list of groups for metadata users, here we are going to dive deeper and implement this task by using SAS data step metadata functions.

First, we connect to the SAS metadata server:

/* Connect to the SAS metadata server */
options
metaserver = 'your_metadata_server'
metaport = 8561
metarepository = Foundation
metauser = 'sasadm@saspw'
metapass = '{SAS002}encrypted_password'
;

Then, let’s run the following code:

/* User Name (not Display Name) */
%let user_name = B Brown;

data USER_GROUPS;
length UserName UserURI GroupURI GroupName GroupDisplayName $256;
call missing(of _char_);
UserName = "&user_name";
UserURI = "omsobj:Person?@Name='&user_name'";
do j=1 by 1 while(metadata_getnasn(UserURI, 'IdentityGroups', j, GroupURI)>0);
rc = metadata_getattr(GroupURI, 'Name', GroupName);
rc = metadata_getattr(GroupURI, 'DisplayName', GroupDisplayName);
output;
end;
run;

Code highlights

In this code, we loop through user’s groups while the following condition is true:

metadata_getnasn(UserURI, 'IdentityGroups', j, GroupURI) > 0

It is important to realize that function METADATA_GETNASN returns two values. Besides its return value (number of associated objects) referenced by the function itself, its 4-th argument (GroupURI) also returns a value of the j-th associated object of the specified association ('IdentityGroups') for the specified object (UserURI). When j becomes greater than the number of groups associated with the user, this function returns value of -4 ( “j is out of range” ) which effectively stops further iterations of the do-loop.

Within the do-loop, we use function METADATA_GETATTR which returns the value (GroupName) of the specified attribute ('Name') for the specified object (GroupURI). Successful completion is indicated by its return value rc=0.

Similarly, the second call of the METADATA_GETATTR function returns GroupDisplayName.

Output

As a result, we are going to get the following USER_GROUPS table:

Using SAS metadata functions to SET user-group associations

Analogous to retrieving user groups associations using METADATA_GETNASN function, we can SET (or assign) such associations using METADATA_SETASSN function. For example, the following statement will modify existing list of members for the group referenced by the first argument (pguri) by adding a new user referenced by the 4-th argument (curi):

rc = metadata_setassn(pguri, 'MemberIdentities', 'Merge', curi);

SAS macro to automatically clone group memberships from one user to another

Now, let’s cut all the exploratory code and wrap everything together into a little nice macro:

%macro mm_clone_user_groups
(
child_name=, /* Child's User Name (not a Display Name) */
parent_name=, /* Parent's User Name (not a Display Name) */
mod=ADD /* Modifier (optional, values – Add or Replace, default=Add */
);

data _null_;
length curi cguri puri pguri $256;
call missing(of _char_);
curi = "omsobj:Person?@Name='&child_name'"; /* Child's URI */
puri = "omsobj:Person?@Name='&parent_name'"; /* Parent's URI */

/* Conditionally remove existing Child's Groups */
if upcase("&mod")="REPLACE" then
/* Loop through Child's Groups */
do i=1 by 1 while(metadata_getnasn(curi, 'IdentityGroups', i, cguri)>0);
/* Remove Child User from each Child's Group */
rc = metadata_setassn(cguri, 'MemberIdentities', 'Remove', curi);
end;

/* Loop through Parent's Groups */
do j=1 by 1 while(metadata_getnasn(puri, 'IdentityGroups', j, pguri)>0);
/* Add Child User to each Parent's Group */
rc = metadata_setassn(pguri, 'MemberIdentities', 'Merge', curi);
end;
run;

%mend mm_clone_user_groups;

Code highlights

The first do-loop executes conditionally only when Modifier is REPLACE (case-insensitive). It iterates through all the groups that child user belongs to and remove that child user from each such a group. Please note that removing a user from a group’s “Current Members” list is equivalent to removing this group from the user’s “Member of” list.

Similarly, adding a group to a user’s “Member of” list is equivalent to adding this user to the group’s “Current Members” list. We use this fact in the second do-loop when for each group associated with the parent we add the child as the group member.

Unless we need to report on the groups we are “cloning” from the parent, we may skip capturing their attributes (like Name or Display Name) using METADATA_GETATTR function as we did in the previous section.

Macro usage

Here are a couple examples of the macro invocation:

Example 1

%mm_clone_user_groups(child_name=A. Smith, parent_name=B Brown, mod=replace)

In this example, the macro will remove all the child’s group associations and replace them by those of the parent user.

Example 2

%mm_clone_user_groups(child_name=A. Smith, parent_name=B Brown)

Here, by default mod=ADD and the macro will augment the child’s group associations by those of the parent user.

Note, that these two examples cover all three scenarios of the group membership cloning outlined in the beginning of this post.

Acknowledgement

Great thanks to Carl Sommer of SAS Technical Support for his invaluable assistance and guidance in navigating through the SAS metadata functions and metadata object associations.

Questions? Thoughts? Comments?

Do you find this blog post useful? How do you administer user group identities/memberships in SAS metadata? Do you have questions, concerns, suggestions, or comments? Please share with us below in the Comments section.

Additional Resources

Metadata management - Building Blocks

SAS administrators tip: Keeping track of SAS users

Duplicating or Copying SAS Users

SASjs MacroCore Library: Metadata Aware Macros

Automatic cloning of SAS metadata user groups and roles was published on SAS Users.