Authorization after an Action

Something that has come up from time to time on the FIM forum is the need to trigger an AuthZ workflow based on some change made by an Action workflow (or by the Sync Service). This is not possible in the FIM Service today and I don’t see any evidence that it will be possible in the future either – I guess it must mess up the workflow processing on some fundamental level.

The gereral consensus on the forum has been that you need to start a new request from outside the Portal – perhaps by using a powershell script. This new request can then follow the full AuthN -> AuthZ -> Action progression. But how to trigger it?

I have now worked through this idea and it’s working, though did need quite a few policy objects in the Portal as well as the script.

The problem I needed to solve

Users need to be able to request access to a system and the access must be approved.

To simplify the user creation process in the Portal I want to include the option to request access on the user create form, but I don’t want an approval holding up creation of the person object. The person should be created and then the approval should kick off.

In fact it’s not just for convenience – the administrators of the target system need forewarning that an approved access request is coming. So they should receive info about the new person via the Sync Service, and they should also receive info about the access request. Later on when the request is approved they will of course get to see that as well.

Here’s what it looks like

So here’s what the end result is looking like in my lab. Notice I’ve pasted over some of the text as this is a customer lab with some real names in it (not Elvis obv).

I should also add I was aiming to solve this using out of the box functionality only. I could perhaps do something more elegant with custom workflows – but I’m sure my client is not the only one who prefers to avoid that.

I added an extra tab to the User Create form where the access request is made. The tab also appears on the User Edit form so you can equally request access for an already existing user.  
The user is created straight away and without any approval. If I go check the Access Requests tab now I can see the access was requested, and who did it.  
I then have a bit of powershell magic going on in the background. This script:

  • detects the request,
  • sets another attribute (“AccessApproved”) that has an Approval AuthZ workflow associated with it, and
  • changes the request status to “Manager approval requested”.
 
The user’s manager receives the Approval request, which they can approve in the FIM Portal or using the Outlook client, if installed.  
Now when the person’s details are checked we see their Approved status. The Sync Service does whatever needs to be done with this information.  

FIM Policy Objects

I had to create quite a few policy objects to support all of this.

Powershell User

I created a dedicated user account to run the powershell script. I explicitly blocked this user from the “All People” set so it wouldn’t accidentally trigger other workflows.

Schema

I now have three attributes to handle the request:

  • AccessRequested (boolean)
  • AccessRequestStatus (indexed string)
  • AccessApproved (boolean)

Sets

I created the following Sets:

  • “Access Requested”: users where “AccessRequested” is true.
  • “Access Approved”: users where “AccessApproved” is true.
  • “Access Requested and Not Approved”: user in “Access Requested” and not in “Access Approved”. I use this to display the request status at this point.
  • “Access Request Ready”: the state the user should be in for an access request to make sense. In my case the user is not a member of the Approved or Requested sets, but they are a member of the “All People with a Manager” set.

Workflows

I have one AuthZ workflow:

  • “Request Approval”: triggers an Approval process when the “AccessApproved” flag is set by the powershell script.

And two Action workflows:

  • “Set Access Status”: uses the Function Evaluator to write “Requested by [//Requestor/DisplayName]” into the “AccessRequestStatus” attribute.
  • “Clear Access Request”: uses the Function Evaluator to set “AccessRequested” to “false”, and clear the “AccessReqeustStatus”. This is run after the approval and clears the request, whether it was approved or rejected.

Additionally I changed the workflow I run whenever a new user transitions in to the All People set, that sets a few default values. When working with booleans in the Portal it is always better if they have a value – null booleans have a habit of setting themselves to ‘false’ when an object is edited – sometimes resulting in “Access Denied” messages. So to this workflow I add:

  • AccessRequested = IIF(IsPresent(AccessRequested),AccessRequested,’false’)
  • AccessApproved = ‘false’

MPRs

“All People may request access”:

  • Modify, Create and Read to attributes “AccessRequested” and “AccessRequestStatus”,
  • Set before “Access Request Ready”; set after “Access Requested”,
  • Action WF “Set Access Status”.

“All People may read pending access request status”:

  • Read attributes “AccessRequested” and “AccessRequestStatus”,
  • Target set “Access Requested and Not Approved”.

“All People may read approved request status”:

  • Read attribute “AccessApproved”,
  • Target set “Access Approved”.

“Powershell user can trigger Access Approval”:

  • Read and Modify attribute “AccessApproved”,
  • Set before “Access Requested”, set after “All People”,
  • AuthZ WF “Request Approval”,
  • Action WF “Clear Access Request”.

“Powershell user may set Access Request details”:

  • Read and Modify attributes “AccessRequested”, “AccessApproved” and “AccessRequestStatus”,
  • Target set “All People”.

RCDC

I modified the User Create and User Edit RCDCs to include the new fields. It is important to include the “my:Rights” parameter to allow the MPRs to display and hide the fields appropriately.

Email Templates

I also created some specific email templates for the Approval workflow. It is quite handy to include the AccessRequestStatus attribute in the template as it includes the name of the person who made the original request. This is something you would have with a direct approval, but is lost by having the powershell user trigger the approval.

Powershell Script

The powershell script is run under the special user account I created for it and sync’d to the Portal. It should run on a regular schedule in the background.

if(@(get-pssnapin | where-object {$_.Name -eq "FIMAutomation"} ).count -eq 0) {add-pssnapin FIMAutomation}
$DefaultUri = "http://localhost:5725"

function ModifyImportObject
{
    PARAM([string]$TargetIdentifier, $ObjectType = "Resource")
    END
    {
        $importObject = New-Object Microsoft.ResourceManagement.Automation.ObjectModel.ImportObject
        $importObject.ObjectType = $ObjectType
        $importObject.TargetObjectIdentifier = $TargetIdentifier
        $importObject.SourceObjectIdentifier = $TargetIdentifier
        $importObject.State = 1 # Put
        $importObject
    }
}

function AddImportChangeToImportObject
{
    PARAM($ImportChange, $ImportObject)
    END
    {
        if ($ImportObject.Changes -eq $null)
        {
            $ImportObject.Changes = (,$ImportChange)
        }
        else
        {
            $ImportObject.Changes += $ImportChange
        }
    }
}

function CreateImportChange
{
    PARAM($AttributeName, $AttributeValue, $Operation)
    END
    {
        $importChange = New-Object Microsoft.ResourceManagement.Automation.ObjectModel.ImportChange
        $importChange.Operation = $Operation
        $importChange.AttributeName = $AttributeName
        $importChange.AttributeValue = $AttributeValue
        $importChange.FullyResolved = 1
        $importChange.Locale = "Invariant"
        $importChange
    }
}

function SetSingleValue
{
    PARAM($ImportObject, $AttributeName, $NewAttributeValue, $FullyResolved=1)
    END
    {
        $ImportChange = CreateImportChange -AttributeName $AttributeName -AttributeValue $NewAttributeValue -Operation 1
        $ImportChange.FullyResolved = $FullyResolved
        AddImportChangeToImportObject $ImportChange $ImportObject
    }
}

function ConvertResourceToHashtable
{
    PARAM([Microsoft.ResourceManagement.Automation.ObjectModel.ExportObject]$ExportObject)
    END
    {
        $hashtable = @{"ObjectID" = "Not found"}
        foreach($attribute in $exportObject.ResourceManagementObject.ResourceManagementAttributes)
        {
            if ($attribute.IsMultiValue -eq 1)
            {
                $hashtable[$attribute.AttributeName] = $attribute.Values
            }
            else
            {
                $hashtable[$attribute.AttributeName] = $attribute.Value
            }
        }
        $hashtable
    }
}

# Find users with the trigger attribute set
$objects = export-fimconfig -customconfig ("/Person[AccessRequestStatus != 'Manager approval requested']")

# Referenced objects also returned so make sure we get one with the attribute set
foreach ($object in $objects)
{
  $hash = ConvertResourceToHashtable -exportobject $object
  if ($hash.Contains('AccessRequestStatus'))
  {
    $hash

    $importObject = ModifyImportObject -TargetIdentifier $hash.Item('ObjectID') -objecttype $hash.Item('ObjectType')
    $importChanges = SetSingleValue -importobject $importObject -attributename 'AccessApproved' -newattributevalue $true
    import-fimconfig -importObject $importObject

    $importObject = ModifyImportObject -TargetIdentifier $hash.Item('ObjectID') -objecttype $hash.Item('ObjectType')
    $importChanges = SetSingleValue -importobject $importObject -attributename 'AccessRequestStatus' -newattributevalue 'Manager approval requested'
    import-fimconfig -importObject $importObject

  }
}

And if you want to do this more than once?

I’ve shown you how to triger an authorization following a completed action, using a powershell script and a whole bunch of policy objects. In my case I actually have two types of access to request and there may be more in the future. However I couldn’t find a suitable way to roll them all into the one set of policy objects. Using a multi-value attribute to send messages to the powershell script was briefly appealing, but I had to forget it due to limitations with FIM xpath and the Function Evaluator. So for this approach, the answer for multiple request requirements is to replicate the whole system of policy objects for each case.

10 Replies to “Authorization after an Action”

  1. Carol, why do you run this PS in a background usign scheduled tasks?
    I would do it using a FIM PowerShell activity and run import-fimconfig.

  2. As I said, doing it with OOB activities. I also don’t think it would have saved me much work. Perhaps the need to have the Status attribute which I search on, but actually I quite like that as it gives feedback to the user.

  3. I have a similar request, but based on employeeEndDate attribute. When a contractor is near to employeeEndDate I need to trigger the approval to extend employeeEndDate. Is it possible to use this example?

    Thanks in Advanced
    Best regards

  4. Yes but I’ think you’ll need an extra step.
    The original “person is close to expiring” will be based on a set transition and you can only run an Action off that, but you can tie an approval to that action in a separate MPR.

    MPR1: Set Transition into “People close to Expiring”. Sets a flag to True “RequestExtension”.
    MPR2: Get approval when RequestExtension is changed and the final set is “People with RequestExtension set to True”

    Then make sure that the WF run by MPR1 is a custom activity setting the ApplyAuthorizationPolicy parameter as described in this post.

  5. (Sorry, the link you indicated in your answer point me to this same post)
    I receive an error in powershell script:
    Import-FIMConfig : While creating a new object, the web service reported the object is pending authorization. The import cannot continue until the object exists. Please approve the object and then replace all subsequent references to this object
    with its object id. Once the references are up to date, please resume the import by providing the output from this stream as input.

    Request ID = urn:uuid:d4a61f21-136c-4bba-81a2-9280d9754995
    ObjectType = Person
    SourceObjectID = urn:uuid:c6ff45ef-a933-47d7-8d9b-20c533cc0bc8
    At C:\Software\Script\Busca_Extensiones_Contratados.ps1:97 char:21
    + import-fimconfig <<<< -importObject $importObject
    + CategoryInfo : InvalidOperation: (:) [Import-FIMConfig], InvalidOperationException
    + FullyQualifiedErrorId : ImportConfig,Microsoft.ResourceManagement.Automation.ImportConfig

    Is it because I am using FIM 2010 R2?

    Thanks!

Leave a Reply

Your email address will not be published. Required fields are marked *


*