/ Development  

Grab your knowledge on "users-selected" security

Hi there AppWorks fans,

Welcome to a new installment of AppWorks tips.

This time the explanation of an experience I will never forget! Tell me, tell me…Can we all learn from it!? For sure! Imagine yourself that the analyst guy (or gal) steps to your desk with a requirement like this:

1
2
3
As a case manager, I want to mark a case as 'confidential' 
and select related users so I can give specific users explicit
access to confidential cases.

You’re thinking! WHAT?…How in earth? Is this even possible? Yes, my friend…Don’t worry…It’s possible, but not how you would expect it! It’s easier than you think. Watch and learn…


Let get right into it…

So, where to start thinking? We need a ‘Case’ entity, we need an ‘Is confidential’ flag (a Boolean?), we need a multi-‘User’-selection thing (a ‘toMany’ relation?), and we need a conditional security (in the ‘Security’ BB of the case…I guess?); This condition must meet the information for our current user. From experience, I know the current ‘UserId’ is retrievable with a function named targetUser() or $(targetUser()) (depending on the place of usage).
User kind of validation is always a pain in the ass. Why? Well, because normally you require to work with the ‘UserId’ of the current user session. Only, this ‘UserId’ value does not match the entity related User-‘ItemId’…watch this example:

Get a grip of the value behind targetUser() via a Rule BB on the ‘Case’ looking like this (with name e_on_init_get_user_id):

sec_001

The value I’m retrieving is: 080027c2-550f-a1ed-937a-4e272cc69cc1

When you do a service call (via the ‘Web Service Interface Explorer’ artifact) on service GetUserDetails in the interface Method Set Notification 1.0 like this:

1
2
3
4
5
<SOAP:Envelope>
<SOAP:Body>
<GetUserDetails />
</SOAP:Body>
</SOAP:Envelope>

You will receive your current user information matching the ‘UserId’:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<data>
<GetUserDetailsResponse>
<User>
<UserDN>cn=awdev@awp,cn=organizational users,o=appworks_tips,cn=cordys,cn=defaultInst,o=22.3.com</UserDN>
<UserDisplayName>AppWorks Developer</UserDisplayName>
<UserId>080027c2-550f-a1ed-937a-4e272cc69cc1</UserId><!-- This is a match! -->
<CanQueryOrgUnits>true</CanQueryOrgUnits>
<ManagerFor>
<Target>
<Id>cn=Entity Workflow Template Developer,cn=OpenText Entity Workflow Template Components,cn=cordys,cn=defaultInst,o=22.3.com</Id>
<Name>Entity Workflow Template Developer</Name>
<Type>role</Type>
</Target>
</ManagerFor>
</User>
</GetUserDetailsResponse>
</data>

Only, when you play with a relation to a ‘User’ entity, you can receive data like this (see the ReadUser webservice); Only this service call requires the input of an ‘ItemId’ of the ‘User’ entity instance which we don’t have available (at that time we only have data of ‘GetUserDetailsResponse’!):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<data>
<wstxns1:ReadUserResponse>
<wstxns2:User>
<wstxns3:Name>awdev@awp</wstxns3:Name>
<FullName>awdev</FullName>
<wstxns4:IdentityDisplayName>AppWorks Developer</wstxns4:IdentityDisplayName>
<wstxns5:Description />
<UserId>awdev@awp</UserId>
<memberid>67874404-a366-103c-9e46-53e6fc752408</memberid>
<toPerson>
<wstxns6:Person-id>
<Id>2</Id>
<ItemId>F8B156B4FF8F11E6E6562305FE2BDF32.2</ItemId>
</wstxns6:Person-id>
</toPerson>
<wstxns7:Identity-id>
<Id>3</Id>
<ItemId>F8B156E1037111E6E9CB0FBF3334FBBF.3</ItemId>
</wstxns7:Identity-id>
<wstxns8:Title>
<Value>awdev@awp</Value>
</wstxns8:Title>
</wstxns2:User>
</wstxns1:ReadUserResponse>
</data>

So, we need another webservice call where we receive a ‘User’ entity, not based on an ‘ItemId’, but based on the username! We have such service available with name FindUserByName. The input will be awdev@awp (which we have available in the ‘UserDN’ tag of the ‘GetUserDetailsResponse’). This call will return the same ‘User’ entity instance with ‘ItemId’ and makes our circle complete! 🤔

So, how can we make the security condition work for us with the above information? Well, how about saving the UserIds of the selected related users (in a comma-separated list via a BPM) to a ‘temp’ property on the case (via an ‘onRelationChange’ rule) and validate it with current ‘UserId’ with a condition like this: item.Properties.relatedUserIds contains targetUser()? Well, this is not working! I also tried things like this: item.Properties.relatedUserIds.split(';').contains(targetUser()). Also, not working although the expression is valid! It looks like targetUser() is not resolving to a value here; Using $(targetUser()) here resolves in an error!? So, now what?? #RTFM! The low-code guide has a small section with the name ‘Security for ad hoc viewers’ within the section named ‘Configuring security’. Right, this guide is talking about a condition like this: (item.Viewers[].Properties.UserId).contains(USER.Properties.UserId)!

I get a moment of “Ahaaaaaa”!! 🤗 So, in our case it should look like this: (item.to_many_user[].Properties.UserId).contains(USER.Properties.UserId)

Don’t forget the curly brackets around item.to_many_user[].Properties.UserId before calling the contains() function!


The correct implementation

Time for some craftsmanship based on the above knowledge! Dive into your project within your favorite organization, and create yourself a new entity ‘Case’ with two properties (case_name and case_is_confidential):

sec_002

Things I do after initial creation:

  • Update the entity properties with a display name
  • Rename the generated BBs to your naming conventions
  • Update the Boolean property with valid values (true|false)
  • Make the ‘Create’ form nice and cozy!

Next step is adding a ‘toMany’ relation to the ‘User’ entity (of the Identity package) and make sure to drag & drop this relation on the ‘Create’ form of the ‘Case’ entity; I make it a grid with related properties of the ‘User’ entity and the supported list All Users Lookup. After a publication, we can create a new ‘Case’ instance like this (I do this with my developer rolled account awdev):

sec_003

Nice…Now the security part! Add the ‘Security’ BB to the ‘Case’ entity and add the role ‘Identity User’ (applied to all regular users!). Make sure you can only view/update information on the ‘Case’:

sec_004

Do a publication, get an incognito tab, and try it out in runtime with a regular account (in my case awtest with only the internal role ‘Entity Runtime User’). You can’t create/delete case instances, but you can view/update one; which is fine for now.

Our next step is to further restrict that confidentiality setting!…So, when there is no confidential flag, our awtest account can view/update; when the confidential flag is set to TRUE we need to check the list of valid users to have a view/update! Go back to the ‘Security’ BB of the case and add a new condition on the ‘View’ action of the entity:

sec_005

Give it a name like is_confidential and give it an advanced expression like this:
!item.Properties.case_is_confidential || (item.to_many_user[].Properties.UserId).contains(User.Properties.UserId)

Well, what does it do? In dummy-talk…A regular entity runtime user can VIEW our entity when it’s NOT confidential (watch the exclamation mark!). When you select the confidentiality (which is the OR-statement), our current ‘UserId’ must have a value match on UserIds in the ‘toMany’ relation list! Yes, my friend…Read that statement again! After the “quarter falls into place”, have yourself a publication and an evaluation in runtime. It’s a party on my side! 🎉

To not make your entity inaccessible (!?), you can add the ‘Identity Administrator’ role to the ‘Security’ BB with still full access for your awdev account to revert the confidentiality flag.


That’s a well-earned “DONE” on a fascinating feature to make an entity confidential for ad hoc viewers selected on a ‘Case’ entity instance! A feature I will never forget as it was a long and bumpy road before we concluded we could make the complex as simple as this post! Time for weekend…And have a good one…I see you next week.

Don’t forget to subscribe to get updates on the activities happening on this site. Have you noticed the quiz where you find out if you are also “The AppWorks guy”?