OneStream Security – ‘Child Groups and Users’

Recently I have been talking a lot to clients and colleagues about Security and it seems to me that the nesting of security groups is not widely understood. By ‘nesting of security groups’ I am talking about the ‘Child Groups and Users’ section in the Security module:

I admit that I struggled for a long time with this bit of security. I knew what to add to the section, it’s just that I didn’t understand why. It was not logical – put the parent entity group in the child section. And when the OneStream Security training course refers to this as ‘counterintuitive’ I know I am not the only one who was confused. In this blog I hope to explain the ‘Child Groups and Users’ section so that it is easy to understand.

The nesting of the security groups is all about inheritance of security. That means a user is assigned to a group and that user automatically inherits security from another security group. This is usually linked to a hierarchy and the most common example given is for the Entity hierarchy where if a user has access to a parent Entity, they also have access to all its children. The key to understanding ‘Child Groups’ is that this inheritance stream can go up a hierarchy as well as down. The descriptions on the Security module assume that the child inherits the access of the parent but the only reference/example in the OneStream documentation is the other way around: the parent inherits the access of the child. Let me explain with a couple of examples.

For Workflows, the user must have access from the root member of the hierarchy down to the base level. If the parent Workflow security groups include the security groups of the child Workflows as ‘Child Groups’, the user only needs to be assigned to the base security group and access from the root is automatically included. The security inheritance flows from the parent to the child; if you have access to the child, you also have access to the parent. ‘Child Groups and Users’ makes sense.

For Entities, the access tree is the other way around. If a user has access to a parent, they will want to have access to its children (probably). In this case the parent security groups are added as ‘Child Groups’. The security inheritance flows from the child to the parent; if you have access to the parent, you also have access to the child. ‘Child Groups and Users’ does not make sense.

In the diagram above, Europe will need access to France and Germany for Entities but for Workflows it will need access to Total Geography. While this might clarify the setup, it does little to help with understanding and remembering. The difference between the assignment of child and parent groups is that users can be assigned as a child but not a parent. In effect, the child groups are equivalent to the user access. When you consider the Workflow security, the user access is assigned to the base item and then pushed up the hierarchy. Another way to look at it is, you start at the top member and pull up the security access from the children i.e. Total Geography has Europe as a child. Entity security is the other way round. User access is assigned to a parent and then it is pulled down to the base i.e. Europe has Total Geography as a child because Total Geography should also have access to Europe.

There are many ways of looking at security: you can look at it from a child or member or parent point of view and you can think of a member as giving security or taking it. In my experience of running courses on security, most people have one preferred way of looking at how security works. I will talk about two ways: ‘Give & Take’ and the ‘Can See Circle’.

Give and Take

One way of looking at the security groups is that the current security group gives access to the Child groups and it takes from the parent groups. For Workflows, Europe gives the security to France and Germany while taking access from Total Geography. For Entities, Europe gives the security to Total Geography and takes it from France and Germany.

Can See Circle

This is my preferred way of looking at security; it works for me but you might like another way. Start at the child groups: they can see the member and the member can then see the parents. (I know it’s not a circle but it has a nice ring to it).

OneStream – Error messages with debug information

One of the frustrations of coding VB.NET in OneStream is that while the error messages contain a lot of information only the first line or so is useful (and that is putting a positive spin on it). Consequently we tend to add debug information which is printed to the error log and then commented out or removed when the script goes live. Sometimes you will see a debug flag to control whether to print these messages to the log. I think that is a bit messy; first of all you get a load of messages in the log and secondly if the script is already in production there might be audit problems, you might not have access etc.

With all that in mind, I decided to see if I could find a better way of presenting more information when an error occurs. I wanted a method that gave me more information about what was happening when the error occurred, did not fill up the error log with messages, was easy to manage and did not have a large impact on performance.

The way I now code a script is:
1. Create a variable that will hold debug information: eg Dim CurrentPosition as String
This variable must be created BEFORE the Try block.
2. Update this variable through the script eg CurrentPosition = “Getting the XFFolderEx object…”
3. Write that to the error log eg BRApi.ErrorLog.LogMessage(si, CurrentPosition)

The next step is to add this ‘CurrentPosition’ information to any errors that are thrown. I do that by creating a new instance of the exception object and add the CurrentPosition variable to the end of the exception message:

Catch ex As Exception
    Dim MHEx As Exception = Activator.CreateInstance(ex.GetType(), ex.Message & Environment.NewLine & "ProcName ERROR - Position: " & CurrentPosition & Environment.NewLine, ex)
    Throw ErrorHandler.LogWrite(si, New XFException(si, MHex))

When the script is working I then comment out all the lines that print to the error log leaving the variable assignments in place.

Here is a simple example. This piece of code will throw an error because the script is trying to insert a value too large for the int16 variable:

Dim varNoDaysSmall As Int16 = 10
Dim varNoDaysLarge As Integer = 1000

For tempNumber As Int16 = 1 To 10
  varNoDaysSmall *= tempNumber
  varNoDaysLarge *= tempNumber
Next

This is the error you get (running from an Extender type Rule):

Now add the CurrentPosition debug information and the updated exception message:

For tempNumber As Int16 = 1 To 10
  CurrentPosition = String.Format("varNoDaysSmall value [{0}] tempNumber [{1}]", varNoDaysSmall.ToString, tempNumber.ToString)
  varNoDaysSmall *= tempNumber
  CurrentPosition = String.Format("varNoDaysLarge value [{0}] tempNumber [{1}]", varNoDaysLarge.ToString, tempNumber.ToString)
  varNoDaysLarge *= tempNumber
Next

And this is the error message you get:

This is just a simple example of getting debug information into an error message. No logs in the error log, very little overhead, simple to implement. I hope you find it useful – over the past few months it has saved me a huge amount of time debugging errors.

Here is the full demo script:

Public Function Main(ByVal si As SessionInfo, ByVal globals As BRGlobals, ByVal api As Object, ByVal args As ExtenderArgs) As Object
  Dim CurrentPosition As String = "Start"
  Try
    Dim varNoDaysSmall As Int16 = 10
    Dim varNoDaysLarge As Integer = 1000

    For tempNumber As Int16 = 1 To 10
      CurrentPosition = String.Format("varNoDaysSmall value [{0}]", varNoDaysSmall.ToString)
      varNoDaysSmall *= tempNumber
      CurrentPosition = String.Format("varNoDaysLarge value [{0}]", varNoDaysLarge.ToString)
      varNoDaysLarge *= tempNumber
    Next

  Catch ex As Exception
    Dim MHEx As Exception = Activator.CreateInstance(ex.GetType(), ex.Message & Environment.NewLine & "DemoError ERROR - Position: " & CurrentPosition & Environment.NewLine, ex)
    Throw ErrorHandler.LogWrite(si, New XFException(si, MHex))

  End Try
End Function