logo

IsRoleEnabled Function for Non-Explicit ACL Entries

Here's a painful lesson I learnt by not testing code thoroughly before delivery. I'd written some code that checked whether the current user had a role enabled in the ACL using the IsRoleEnabled method. All worked well until the customer started to use the code and it broke.

In my testing I had been logged in as users who had explicit ACL entries. The customer was using groups to organise the ACL. The IsRoleEnabled method only works for explicit entries (bug?). To get round this I had to quickly write a custom function to do the job. Here it is:

Function UserHasRole(db As NotesDatabase, UserName As String, 
 RoleName As String )
        Dim found As Variant
        Found = False
        
        roles = db.QueryAccessRoles(UserName)
        
        Forall role In roles
                If role = RoleName Then
                        Found = True
                End If
        End Forall
        
        UserHasRole = Found
End Function

It was a quick fix that worked. Now, I'm no LotusScript expert and I don't doubt there's a better way to do this. However, when Notes throws it in your face like this, you just want to get it fixed. Note that I didn't dim the roles variant, as Notes wouldn't let me. The whole thing smells of bugs to me.

Comments

  1. In script, I always Evaluate "@IsMember("[Role]"; @UserRoles)". Never had a problem with it.

    • avatar
    • Jake
    • Wed 3 May 2006 05:01 AM

    Jan. That assumes the agent is "run as web user". Unfortunately, mine weren't.

  2. IsRoleEnabled method is under NotesACLEntry Class and helps says that it "Represents a single entry in an access control list. An entry may be for a person, a group, or a server."

    So it's only for an explicit entry not for computed ACL for a person.

    Also from designer help: "If the name you specify is listed explicitly in the ACL, then QueryAccessRoles returns the roles for that ACL entry and does not check groups."

    Since its documented, it doesn't sound like a bug to me.

    • avatar
    • Jan Van Puyvelde
    • Wed 3 May 2006 06:33 AM

    I think your bypass is very good; I don't think there's another way for server based agents.

  3. I would suggest that IsRoleEnabled is doing exactly what it says on the tin.

    flag = notesACLEntry.IsRoleEnabled( name$ )

    If you have the ACLEntry for an explicit entry in the ACL then it will return a true/false depending on the named role.

    But this is NOT what you actually need. Your QueryAccessRoles *is* what you need. So this is a case of using the right call for the job. Smells of user bugs rather than system bugs to me. Time for an air freshener I think. (^_^)

    • avatar
    • Jake
    • Wed 3 May 2006 07:25 AM

    Dragon. But surely the fact that you can get a handle on a valid NotesACLEntry for any user who is in a group, which is listed in the ACL, means that the IsRoleEnabled method should work. It doesn't say it won't as far as I can see.

  4. But with IsRoleEnabled you're not checking if such a user is *in* the group are you? All you are doing is reading what settings you have in the ACL and checking against the roles for that entry. Sure if you are searching for a particular named entry and it happens to coincide with a username, you would get the valid roles, but you're not explicitly checking the users entry. In fact it could be searching for any user - not necessarily that of the person using the database.

    What QueryAccessRoles is doing is actively evaluating the situation. Say as an administrator determining what a given user can/cannot do.

    I stand by my statement. It was the wrong tool for the job at hand. Your replacement code does, however, rectify the situation.

    • avatar
    • Niel
    • Wed 3 May 2006 09:23 AM

    Maybe a CFD field on the form with the results of the @IsMember?

  5. Dragon, IMHO the only thing I can think of is, QueryAccessRoles could have been a part of ACL class instead of database class - sounds more logical that way.

    • avatar
    • Jake
    • Wed 3 May 2006 09:51 AM

    Dragon. I'm not convinced. Say you have a user called Joe Bloggs who is listed in the ACL by way of a group and not listed by name at all. The code to get to IsRoleEnabled would look something like this:

    Dim entry As NotesACLEntry

    Set acl = db.ACL

    Set entry = acl.GetEntry( "Joe Bloggs" )

    msgbox entry.IsRoleEnabled( "[Supervisor]" )

    So, in this case, "entry" is a valid NotesACLEntry that we've gotten using the user's name. Now, as far as I'm concerned, I'd expect any method of the NotesACLEntry class to work on that object, whether it was obtained by user name, group or whatever.

    If it's meant to function the way you say then I'd think it was a little counter-intuitive.

    • avatar
    • Yuval
    • Wed 3 May 2006 10:16 AM

    One more option:

    create hidden single value field 'UserRoles' in your htmlheader subform or directly on the form with the following formula:

    @Implode(@Unique(@UserRoles:@UserNamesList);",")

    Then in your agent code you can do this:

    If Instr(1,doc.UserRoles(0),"[RoleName]") Then

    Your Code here ...

    End If

    • avatar
    • Scot Haberman
    • Wed 3 May 2006 10:19 AM

    What about using the @UserNamesList function?

  6. Jake, you're right on the mark with QueryAccessRoles. The only thing I'd change is to replace the For loop with Arraygetindex:

    If Not IsNull(Arraygetindex(roles,RoleName)) Then

    Found = True

    End If

  7. Oh, and the behaviour of IsRoleEnabled isn't a bug -- that's the way it's documented as behaving. It does, after all, belong to NotesACLEntry, so you can only check the entry. Finding out which entries apply to the user, well, that's another story. The only real way to do this before 6.5 was to use @UserRoles, and that generally meant using a computed field and the NotesDocument parameter in an Evaluate (for the not as web user case).

    • avatar
    • Jake
    • Thu 4 May 2006 03:35 AM

    Stan. I normally take anything you say as gospel, but I'm having real trouble accepting this one as "as designed".

    Let's say I'm the code monkey developer who designs and test the database, but never gets to see the ACL. I write the code like so:

    Set entry = acl.GetEntry( session.username )

    msgbox entry.IsRoleEnabled( "[Supervisor]" )

    As I said earlier "entry" is a bona fide NotesACLEntry. Whether or not the user is actually in the ACL is irrelevant to me, the developer. How should I be expected to know that when coding anyway?! The fact is I've got a handle on the NotesACLEntry for that user and I'd expect any method of that class to work. The fact that this one doesn't might be "as designed" but it makes no sense at all to me.

  8. Sorry Jake, but I'm on the side of Stan here.

    acl.GetEntry is working EXACTLY as advertised. You're just using the wrong tool for the job.

    • avatar
    • Jake
    • Thu 4 May 2006 06:55 AM

    Mmmm. I think I might have just realised where I was going wrong. I was assuming that getentry worked but isroleenabled didn't. So, you're saying that GetEntry wouldn't have returned a valid NotesACLEntry for the user in a group? My code was breaking one line earlier than I though, by the sounds of it. Doh. Now, where's that Delete Blog button gone...

  9. I think it is much easier than that:

    roles = Evaluate("@UserRoles")

    forall r in roles

    if CStr(r)="[MyRole]" then

    found=true

    end if

    end forall

    • avatar
    • Keith
    • Thu 4 May 2006 10:15 AM

    To get a collection of user roles, I've always used the method that pablosang describes. Works like a charm. It's amazing what you can leverage from Formula language using Evaluate.

  10. By Jove, I think he's got it.

    Thats the problem with Domino. There is just so much going on. The documentation leaves a lot to be desired in many places. The fact that they use the same parameters for the various calls but they mean different things.

    Set notesACLEntry = notesACL.GetEntry( name$ )

    vs

    roles = notesDatabase.QueryAccessRoles( name$ )

    I've often thought of writing a quickie guide (ala Visibone {Link} ) to detail the structure of LotusScript calls. A bit of colour coding would go a long way to improving the understanding.

  11. Evaluate is slow. Not the execution of the formula itself, but switching between LotusScript run-time and formula execution is (which almost rules out it's use inside of loops). So, evaluate is great, where one line of formula code can replace multiple, possibly complicated lines of LotusScript, especially if it can replace a lengthly loop. And of course, where there's no alternative.

    In this case, we do have a native LS alternative that works just as well. Why would you even bother ot introduce potential extra complexity (using an additional language) and potential extra error-proneness (no syntax checking)? In the case of a single @UserRoles this might not be too much of an issue. But I have seen really cruel examples of formula abuse in LS with my own eyes. My current favorite:

    currentDate = Evaluate("@Today")

    Written by a well known book author in a current Notes 6.5 application ...

    • avatar
    • Richard Shergold
    • Fri 5 May 2006 02:35 AM

    Jake

    Is there any reason why you declared your Found variable as a variant rather than boolean? I can always remember being told in a Notes class I attended (back in 1952 or something like that) to avoid declaring variants unless absolutely neccessary because of memory allocation issues. Just a thought...

    • avatar
    • Jake
    • Fri 5 May 2006 02:43 AM

    Richard. That's just the way I've always dealt with booleans in LS. In fact I didn't even realise there was the option to declare it as an actual boolean. Doh! That's what you get for never having been to any classes.

    • avatar
    • Jono
    • Fri 5 May 2006 03:17 AM

    I believe the boolean data type is new as of Notes 6, previous to that I always used Integer - as false and true equate to a zero and one.

    • avatar
    • Patrix
    • Fri 5 May 2006 07:37 AM

    Yes the boolean type is new with N6. Before N6 you could only use a variant and set it to true/false.

    -

    In a Forall loop you cannot previosly declare the holding variable. And the holding variable is always of type variant. That is how it is supposed to work.

    /Patrix - NotesScript kiddie(tm)

  12. Patrix,

    Before N6 (at least on R5!) you could, as Jono said, use an integer var, instead of a variant, and set it to True or False.

    Jono,

    On Lotusscript, True is -1 , not 1 :)

    • avatar
    • Patrix
    • Wed 10 May 2006 08:29 AM

    Pedro, You you could/can but it will not be a boolean type variable then. It will be an integer. To get a real boolean before R6 you had to use the variant type. The code below illustrates the difference. However in practice this doesent matter much.

    /Patrik

    Sub Initialize

    Dim itg As Integer

    Dim vari As Boolean

    Dim bool As Boolean

    itg = True

    vari = True

    bool = True

    Msgbox itg 'Displays -1

    Msgbox vari 'Displays True

    Msgbox bool 'Displays True

    End Sub

    • avatar
    • Patrix
    • Wed 10 May 2006 08:31 AM

    Correction:

    Dim vari as Boolean

    should of course read

    Dim vari as Variant

Your Comments

Name:
E-mail:
(optional)
Website:
(optional)
Comment:


About This Page

Written by Jake Howlett on Wed 3 May 2006

Share This Page

# ( ) '

Comments

The most recent comments added:

Skip to the comments or add your own.

You can subscribe to an individual RSS feed of comments on this entry.

Let's Get Social


About This Website

CodeStore is all about web development. Concentrating on Lotus Domino, ASP.NET, Flex, SharePoint and all things internet.

Your host is Jake Howlett who runs his own web development company called Rockall Design and is always on the lookout for new and interesting work to do.

You can find me on Twitter and on Linked In.

Read more about this site »

More Content