Converting Legacy CSV Entitlement Provisioning into ISC Roles

Background

Currently in my company environment we have a legacy provisioning process that grants Epic Entitlements based off a CSV feed file generated by Workday. This data is then used to build a value we call the Role Based Provision Guide (RBPG) for short. This value is a concatenated string of Ministry Code | Department Code | Job Code. Using this value the process then does a look up to see which Epic Template should be applied to the user. This legacy process only works for joiners and leavers but does not handle movers.

Over the last year (June 2023 to August 2024) we built out the Epic connector to replace the legacy provisioning process. The team has now been tasked with converting the old process to roles that will be maintained in Identity Security Cloud. The legacy process did not maintain the values when they were no longer in use.

Business Ask

The application owners have asked us to implement automation of the provisioning process using ISC roles. They do not want managers to request Epic access manually anymore and want provisioning to happen via roles. Currently the process that is in place has over 2000 roles that they would like to have ISC manage. This would ensure that user access reflects any mover events that take place in the organization. The business wants to enhance the lifecycle management of access to Epic. The current process is managed by a spread sheet that multiple people have to look at to ensure that the access being granted is correct.

Team Ask

My team has been very hard at work in gathering the requirements for being able to move this information into ISC in a streamlined manner. They wanted a way to be able to create Shell roles with our base criteria and the ability to build a CSV file that can be used to build out the roles based on the requirements they collected.

Design Process

When looking at this from a high level, I needed to come up with a design that would be easy to use by the team. Not all members are technical users. The CSV file must be easy to build out quickly and easy to use to upload roles in a fully working order without having any permissions applied.

When building out the Shell roles design, I worked with one of my business analysts to gather the requirements that they wanted to see in these roles:

  • Name should be Shell{number} EX: Shell1
  • Owner set to the senior engineer
  • Description set to “Note this is a Shell Role”
  • LifecycleState set to Active OR Leave
  • AutoProvisioning is True OR ServiceNow Contains Provision
  • ServiceNow sys_class_name equals user

When building out the design for converting the CSV file, the requirements were:

  • The name should come from the CSV file
  • Owner set to the senior engineer
  • Description should come from the CSV file
  • LifecycleState is Active OR Leave
  • AutoProvisioning is True OR ServiceNow Contains Provision
  • ServiceNow sys_class_name equals user
  • All additional requirements would need to be populated from the CSV file
  • Create a CSV template file to be used when creating the roles

Other Requirements

  • Classes must be reusable
  • All methods need to have a predefined test that anyone can run
  • Limit the number of user inputs in order to run
  • Must be PowerShell cmdlets
  • Users should only have to enter the bare minimum information
  • Must follow Model View Controller (MVC) framework
  • No credentials stored locally

Solution

Building the Classes

RoleCsv

    public class RoleCSV
    {
        [Name("RoleName")]
        public string RoleName { get; set; }
        [Name("RoleDescription")]
        public string RoleDescription { get; set; }
        [Name("RoleCritera")]
        public string RoleCritera { get; set; }
    }

SailPointRoleModel

 public class SailPointRoleModel
 {
     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
     [JsonPropertyName("id")]
     public string? Id { get; set; }
     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
     [JsonPropertyName("name")]
     public string? Name { get; set; }
     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
     [JsonPropertyName("description")]
     public string? Description { get; set; }
     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
     [JsonPropertyName("owner")]
     public RoleOwner Owner { get; set; }
     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
     [JsonPropertyName("accessProfiles")]
     public List<AccessProfile> AccessProfile { get; set; }
     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
     [JsonPropertyName("membership")]
     public Memebership? Memebership { get; set; }
     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
     [JsonPropertyName("legacyMembershipInfo")]
     public string? LegacyMembershipInfo { get; set; }
     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
     [JsonPropertyName("enabled")]
     public bool? Enabled { get; set; } = false;
     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
     [JsonPropertyName("requestable")]
     public bool? Requestable { get; set; }
     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
     [JsonPropertyName("accessRequestConfig")]
     public AccessRequestConfig? AccessRequestConfig { get; set; }
     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
     [JsonPropertyName("revocationRequestConfig")]
     public RevocationRequestConfig? RevocationRequestConfig { get; set; }
     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
     [JsonPropertyName("segments")]
     public List<Segments>? Segments { get; set; }
 }

RoleOwner

public class RoleOwner
{
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
    [JsonPropertyName("type")]
    public string? Type { get; set; }
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
    [JsonPropertyName("id")]
    public string? Id { get; set; }
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
    [JsonPropertyName("name")]
    public string? Name { get; set; }
}

AccessProfile

 public class AccessProfile
 {
     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
     [JsonPropertyName("type")]
     public string? Type { get; set; }
     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
     [JsonPropertyName("id")]
     public string? Id { get; set; }
     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
     [JsonPropertyName("name")]
     public string? Name { get; set; }
 }

Membership

 public class Memebership
 {
     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
     [JsonPropertyName("type")]
     public string? Type { get; set; }
     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
     [JsonPropertyName("criteria")]
     public Criteria? Criteria { get; set; }
     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
     [JsonPropertyName("identities")]
     public List<Identities>? Identites { get; set; }
 }

Criteria

public class Criteria
{
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
    [JsonPropertyName("operation")]
    public string? Operation { get; set; }
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
    [JsonPropertyName("key")]
    public Key? Key { get; set; }
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
    [JsonPropertyName("stringValue")]
    public string? StringValue { get; set; }
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
    [JsonPropertyName("children")]
    public List<Children>? Children { get; set; }
}

Children

    public class Children
    {
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("operation")]
        public string? Operation { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("key")]
        public Key? Key { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("stringValue")]
        public string? StringVlaue { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("children")]
        public List<Children>? Child { get; set; }

    }

Key

    public class Key
    {
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("type")]
        public string? Type { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("property")]
        public string? Property { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("sourceId")]
        public string? SourceId { get; set; }
    }

AccessRequestConfig

    public class AccessRequestConfig
    {
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
        [JsonPropertyName("commentsRequired")]
        public bool? CommentRequired { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
        [JsonPropertyName("denialCommentsRequired")]
        public bool? DenialCommentsRequired { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("approvalSchemes")]
        public List<string>? ApprovalSchemas { get; set; }
    }

RevocationRequestConfig

    public class RevocationRequestConfig
    {
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("approvalSchemes")]
        public List<string>? ApprovalSchemas { get; set; }
    }

Segments

    public class Segments
    {

    }

Identities

    public class Identities
    {
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("type")]
        public string? Type { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("id")]
        public string? Id { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("name")]
        public string? Name { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("aliasName")]
        public string? AliasName { get; set; }
    }

RoleUpdate

    public class RoleUpdate
    {
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("op")]
        public string? Op { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("path")]
        public string? Path { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("value")]
        public RoleValue Value { get; set; }
    }

RoleValue

    public class RoleValue
    {
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("type")]
        public string? Type { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("id")]
        public string? Id { get; set; }
    }

SailPointToken

 public class SailPointToken
 {
     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
     [JsonPropertyName("access_token")]
     public string? AccessToken { get; set; }

     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
     [JsonPropertyName("token_type")]
     public string? TokenType { get; set; }

     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
     [JsonPropertyName("expires_in")]
     public int ExpiresIn { get; set; }

     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
     [JsonPropertyName("scope")]
     public string? Scope { get; set; }

     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
     [JsonPropertyName("tenant_id")]
     public string? TenantId { get; set; }

     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
     [JsonPropertyName("pod")]
     public string? Pod { get; set; }

     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
     [JsonPropertyName("strong_auth_supported")]
     public bool? StrongAuthSupported { get; set; }

     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
     [JsonPropertyName("org")]
     public string? Org { get; set; }

     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
     [JsonPropertyName("identity_id")]
     public string? IdentityId { get; set; }

     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
     [JsonPropertyName("user_name")]
     public string? UserName { get; set; }

     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
     [JsonPropertyName("strong_auth")]
     public bool? StrongAuth {  get; set; }

     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
     [JsonPropertyName("jit")]
     public string? JTI {  get; set; }
 }

SailPointSearchRole

public class SailPointSearchRole
{
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)][JsonPropertyName("indices")] public List<string>? Indices { get; set; }
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)][JsonPropertyName("queryType")] public string? QueryType { get; set; }
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)][JsonPropertyName("queryVersion")] public string? QueryVersion { get; set; }
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)][JsonPropertyName("query")] public SearchQuery? Query { get; set; }
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)][JsonPropertyName("queryDsl")] public string? QueryDsl { get; set; }
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)][JsonPropertyName("textQuery")] public SearchTextQuery? TextQuery { get; set; }
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)][JsonPropertyName("typeAheadQuery")] public SearchTypeAheadQuery? TypeAheadQuery { get; set; }
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)][JsonPropertyName("includeNested")] public bool? IncludeNested = true;
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)][JsonPropertyName("queryResultFilter")] public SearchQueryResultFilter? QueryResultFilter { get; set; }
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)][JsonPropertyName("aggregationsType")] public string? AggregationsType { get; set; }
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)][JsonPropertyName("aggregationsVersion")] public string? AggregationsVersion { get; set; }
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)][JsonPropertyName("aggregationsDsl")] public SearchAggregationsDsl? AggregationsDsl { get; set; }
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)][JsonPropertyName("aggregations")] public SearchAggregations? Aggregations { get; set; }
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)][JsonPropertyName("sort")] public List<string>? Sort { get; set; }
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)][JsonPropertyName("searchAfter")] public List<string>? SearchAfter { get; set; }
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)][JsonPropertyName("filters")] public SearchFilters? Filters { get; set; }
}

SearchQuery

public class SearchQuery
{
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)][JsonPropertyName("query")] public string? Query { get; set; }
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)][JsonPropertyName("fields")] public List<string>? Fields { get; set; }
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)][JsonPropertyName("timeZone")] public string? TimeZone { get; set; }
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)][JsonPropertyName("innerHit")] public SearchInnerHit? InnerHit { get; set; }
}

SearchInnerHit

public class SearchInnerHit
{
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)][JsonPropertyName("query")] public string? Query { get; set; }
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)][JsonPropertyName("type")] public string? Type { get; set; }
}

SearchTextQuery

public class SearchTextQuery
{
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)][JsonPropertyName("terms")] public List<string>? Terms { get; set; }
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)][JsonPropertyName("fields")] public List<string>? Fields { get; set; }
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)][JsonPropertyName("matchAny")] public bool? MatchAny { get; set; }
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)][JsonPropertyName("contains")] public bool? Contains { get; set; }
}

SearchTypeAheadQuery

public class SearchTypeAheadQuery
{
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)][JsonPropertyName("query ")] public string? Query { get; set; }
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)][JsonPropertyName("field ")] public string? Field { get; set; }
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)][JsonPropertyName("nestedType ")] public string? NestedType { get; set; }
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)][JsonPropertyName("maxExpansions")] public Int32? MaxExpansions { get; set; }
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)][JsonPropertyName("size")] public Int32? Size { get; set; }

}

SearchQueryResultFilter

public class SearchQueryResultFilter
{
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)][JsonPropertyName("includes")] public List<string>? Includes { get; set; }
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)][JsonPropertyName("excludes")] public List<string>? Excludes { get; set; }
}

SearchAggregationsDsl

    public class SearchAggregationsDsl
    {

    }

SearchAggregations

    public class SearchAggregations
    {
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)][JsonPropertyName("nested")] public SearchNested? Nested { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)][JsonPropertyName("metic")] public SearchMetric? Metric { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)][JsonPropertyName("filter")] public SearchFilter? Filter { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)][JsonPropertyName("bucket")] public SearchBucket? Bucket { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)][JsonPropertyName("subAggregation")] public SearchSubAggregation? SubAggregation { get; set; }
    }

SearchNested

    public class SearchNested
    {
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)][JsonPropertyName("name")] public string? Name { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)][JsonPropertyName("type")] public string? Type { get; set; }
    }

SearchMetric

public class SearchMetric
{
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)][JsonPropertyName("name")] public string? Name { get; set; }
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)][JsonPropertyName("type")] public string? Type { get; set; }
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)][JsonPropertyName("field")] public string? Field { get; set; }
}

SearchFilter

public class SearchFilter
{
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)][JsonPropertyName("name")] public string? Name { get; set; }
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)][JsonPropertyName("type")] public string? Type { get; set; }
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)][JsonPropertyName("field")] public string? Field { get; set; }
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)][JsonPropertyName("value")] public string? Value { get; set; }
}

SearchBucket

public class SearchBucket
{
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)][JsonPropertyName("name")] public string? Name { get; set; }
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)][JsonPropertyName("type")] public string? Type { get; set; }
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)][JsonPropertyName("field")] public string? Field { get; set; }
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)][JsonPropertyName("size")] public Int32? Size { get; set; }
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)][JsonPropertyName("minDocCount ")] public Int32? MinDocCount { get; set; }
}

SearchSubAggregation

public class SearchSubAggregation
{
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)][JsonPropertyName("nested")] public SearchNested? Nested { get; set; }
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)][JsonPropertyName("metric")] public SearchMetric? Metric { get; set; }
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)][JsonPropertyName("filter")] public SearchFilter? Filter { get; set; }
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)][JsonPropertyName("bucket")] public SearchBucket? Bucket { get; set; }
}

SearchFilters

   public class SearchFilters
   {

   }

SearchResults

public class SearchResults
{
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
    [JsonPropertyName("accessProfiles")]
    public List<AccessProfiles>? AccessProfiles { get; set; }
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
    [JsonPropertyName("accessProfileCount")]
    public int AccessProfileCount { get; set; }
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
    [JsonPropertyName("segments")]
    public List<Segment>? Segments { get; set; }
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
    [JsonPropertyName("segmentCount")]
    public int SegmentCount { get; set; }
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
    [JsonPropertyName("created")]
    public string? Created { get; set; }
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
    [JsonPropertyName("modified")]
    public string? Modified { get; set; }
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
    [JsonPropertyName("synced")]
    public string? Synced { get; set; }
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
    [JsonPropertyName("enabled")]
    public bool? Enabled { get; set; }
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
    [JsonPropertyName("requestable")]
    public bool? Requestable { get; set; }
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
    [JsonPropertyName("requestCommentsRequired")]
    public bool? RequestCommentsRequired { get; set; }
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
    [JsonPropertyName("owner")]
    public SearchResultOwner? Owner { get; set; }
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
    [JsonPropertyName("id")]
    public string? Id { get; set; }
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
    [JsonPropertyName("name")]
    public string? Name { get; set; }
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
    [JsonPropertyName("pod")]
    public string? Pod { get; set; }
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
    [JsonPropertyName("org")]
    public string? Org { get; set; }
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
    [JsonPropertyName("_type")]
    public string? _Type { get; set; }
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
    [JsonPropertyName("type")]
    public string? Type { get; set; }
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
    [JsonPropertyName("_version")]
    public string? _Version { get; set; }
}

AccessProfiles

    public class AccessProfiles
    {
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)][JsonPropertyName("id")] public string? Id { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)][JsonPropertyName("name")] public string? Name { get; set; }
    }

Segment

public class Segment { }

SerachResultOwner

    public class SearchResultOwner
    {
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)][JsonPropertyName("email")] public string? Email { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)][JsonPropertyName("type")] public string? Type { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)][JsonPropertyName("id")] public string? Id { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)][JsonPropertyName("name")] public string? Name { get; set; }
    }

SailPointRoleFound

    public class SailPointRoleFound
    {
        public string? RoleName { get; set; }
        public string? Mesage { get; set; }
    }

RoleCSVTemplate

public class RoleCsvTemplate
{
    [Name("RBPG_Position_Code")]
    public string? RBPGPositionCode { get; set; }
    [Name("Role_Name")]
    public string? RoleName { get; set; }
}

Screenshot of Files and Classes

Building the Methods

BaseRoleCriteraController

using SailPointRoleMaintance.Private.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SailPointRoleMaintance.Private.Controllers
{
    public class BaseRoleCriteraController
    {
        public static List<Children> BuildBaseRoleCritera(string Tenant)
        {
            //Building first group for LifeCycleState Critera
            List<Children> children = new List<Children>();
            Children lifeCycleStateCritera = new Children();
            Children lifeCycleCriteria = new Children();
            lifeCycleCriteria.Operation = "EQUALS";
            Key lifeCycleKey = new Key();
            lifeCycleKey.Type = "IDENTITY";
            lifeCycleKey.Property = "attribute.cloudLifecycleState";
            lifeCycleKey.SourceId = null;
            lifeCycleCriteria.Key = lifeCycleKey;
            lifeCycleCriteria.StringVlaue = "active";

            Children lifeCycleLeaveCriteria = new Children();
            lifeCycleLeaveCriteria.Operation = "EQUALS";
            Key lifeCycleLeaveKey = new Key();
            lifeCycleLeaveKey.Type = "IDENTITY";
            lifeCycleLeaveKey.Property = "attribute.cloudLifecycleState";
            lifeCycleLeaveKey.SourceId = null;
            lifeCycleLeaveCriteria.Key = lifeCycleLeaveKey;
            lifeCycleLeaveCriteria.StringVlaue = "leave";

            lifeCycleStateCritera.Operation = "OR";
            lifeCycleStateCritera.Key = null;
            lifeCycleStateCritera.StringVlaue = null;
            lifeCycleStateCritera.Child = new List<Children>();
            lifeCycleStateCritera.Child.Add(lifeCycleCriteria);
            lifeCycleStateCritera.Child.Add(lifeCycleLeaveCriteria);

            //Building Secound Group for AutoProvisioning
            Children AutoProvisionCriteria = new Children();
            AutoProvisionCriteria.Operation = "OR";
            AutoProvisionCriteria.Key = null;
            AutoProvisionCriteria.StringVlaue = null;

            Children autoProvisionSnowCriteria = new Children();
            autoProvisionSnowCriteria.Operation = "CONTAINS";
            Key autoProvisionSnowKey = new Key();
            autoProvisionSnowKey.Type = "IDENTITY";
            autoProvisionSnowKey.Property = "attribute.snowAccount";
            autoProvisionSnowKey.SourceId = null;
            autoProvisionSnowCriteria.Key = autoProvisionSnowKey;
            autoProvisionSnowCriteria.StringVlaue = "Provision";


            Children autoProvisionSecZettaCriteria = new Children();
            autoProvisionSecZettaCriteria.Operation = "EQUALS";
            Key autoProvisionSecZettaKey = new Key();
            autoProvisionSecZettaKey.Type = "IDENTITY";
            autoProvisionSecZettaKey.Property = "attribute.automateProvisioning";
            autoProvisionSecZettaKey.SourceId = null;
            autoProvisionSecZettaCriteria.Key = autoProvisionSecZettaKey;
            autoProvisionSecZettaCriteria.StringVlaue = "True";

            AutoProvisionCriteria.Child = new List<Children>();
            AutoProvisionCriteria.Child.Add(autoProvisionSnowCriteria);
            AutoProvisionCriteria.Child.Add(autoProvisionSecZettaCriteria);

            //Building thrid group for user type
            Children userTypeCriteria = new Children();
            userTypeCriteria.Operation = "EQUALS";
            Key userTypeKey = new Key();
            userTypeKey.Type = "ACCOUNT";
            userTypeKey.Property = "attribute.sys_class_name";
            if(Tenant == "Redacted")
            {
                userTypeKey.SourceId = "Redacted";
            }
            else if (Tenant == "Redacted")
            {
                userTypeKey.SourceId = "Redacted";
            }
            
            userTypeCriteria.Key = userTypeKey;
            userTypeCriteria.StringVlaue = "user";

            //Add all groups into one object
            children.Add(lifeCycleStateCritera);
            children.Add(AutoProvisionCriteria);
            children.Add(userTypeCriteria);

            //return object to be used
            return children;
        }
    }
}

BuildBaseRoleCriteraListController

using SailPointRoleMaintance.Private.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SailPointRoleMaintance.Private.Controllers
{
    public class BuildBaseRoleCriteraListController
    {
        public static List<Children> BuildBaseLifeCycleCriteraList()
        {
            List<Children> children = new List<Children>();
            Children lifeCycleActive = new Children();
            Children lifeCycleLeave = new Children();

            lifeCycleActive.Operation = "EQUALS";
            Key LifeCycleActiveKey = new Key();
            LifeCycleActiveKey.Type = "IDENTITY";
            LifeCycleActiveKey.Property = "attribute.cloudLifecycleState";
            LifeCycleActiveKey.SourceId = null;
            lifeCycleActive.Key = LifeCycleActiveKey;
            lifeCycleActive.StringVlaue = "active";

            lifeCycleLeave.Operation = "EQUALS";
            Key lifeCycleLeaveKey = new Key();
            lifeCycleLeaveKey.Type = "IDENTITY";
            lifeCycleLeaveKey.Property = "attribute.cloudLifecycleState";
            lifeCycleLeaveKey.SourceId = null;
            lifeCycleLeave.Key = lifeCycleLeaveKey;
            lifeCycleLeave.StringVlaue = "leave";

            children.Add(lifeCycleActive);
            children.Add(lifeCycleLeave);
            return children;

        }

        public static List<Children> BuildBaseAutoProvisionCriteraList()
        {
            List<Children> children = new List<Children>();
            Children SnowAction = new Children();
            Children AutoProvision = new Children();
            SnowAction.Operation = "CONTAINS";
            Key SnowActionKey = new Key();
            SnowActionKey.Type = "IDENTITY";
            SnowActionKey.Property = "attribute.snowAccount";
            SnowActionKey.SourceId = null;
            SnowAction.Key = SnowActionKey;
            SnowAction.StringVlaue = "Provision";

            AutoProvision.Operation = "EQUALS";
            Key AutoProvisionKey = new Key();
            AutoProvisionKey.Type = "IDENTITY";
            AutoProvisionKey.Property = "attribute.automateProvisioning";
            AutoProvisionKey.SourceId = null;
            AutoProvision.Key = AutoProvisionKey;
            AutoProvision.StringVlaue = "True";

            children.Add(SnowAction); 
            children.Add(AutoProvision);
            return children;
        }

        public static List<Children> BuildBaseUserTypeCriteraList(string Tenant)
        {
            List<Children> children = new List<Children>();
            Children userTypeCriteria = new Children();
            userTypeCriteria.Operation = "EQUALS";
            Key userTypeKey = new Key();
            userTypeKey.Type = "ACCOUNT";
            userTypeKey.Property = "attribute.sys_class_name";
            if (Tenant == "Redacted")
            {
                userTypeKey.SourceId = "Redacted";
            }
            else if (Tenant == "Redacted")
            {
                userTypeKey.SourceId = "Redacted";
            }

            userTypeCriteria.Key = userTypeKey;
            userTypeCriteria.StringVlaue = "user";

            children.Add(userTypeCriteria);
            return children;
        }
    }
}

BuildOwner

using SailPointRoleMaintance.Private.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SailPointRoleMaintance.Private.Controllers
{
    public class BuildOwner
    {
        public static RoleOwner BuildRoleOwner(string Tenant)
        {
            if(Tenant == "ssmhc")
            {
                RoleOwner roleOwner = new RoleOwner();
                roleOwner.Name = "Redacted";
                roleOwner.Type = "IDENTITY";
                roleOwner.Id = "Redacted";
                return roleOwner;
            }
            else if (Tenant == "ssmhc-sb")
            {
                RoleOwner roleOwner = new RoleOwner();
                roleOwner.Name = "Redacted";
                roleOwner.Type = "IDENTITY";
                roleOwner.Id = "Redacted";
                return roleOwner;
            }
            else
            {
                throw new Exception("Tenant Not Found");
            }

        }
    }
}

BuildRBPGCriteraRoleController

using SailPointRoleMaintance.Private.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SailPointRoleMaintance.Private.Controllers
{
    public class BuildRBPGCriteraRoleController
    {
        public static  List<Children>  BuildRBPGRoleCritera(string FilePath, string RoleName)
        {
            if (File.Exists(FilePath))
            {
                
                List<RoleCSV> rolesCSV = ReadRoleCSVFileController.ReadRoleCSVFile(FilePath: FilePath);
                List<Children> childrens = new List<Children>();

                    var RolesToBuild = from Role in rolesCSV
                                       where Role.RoleName == RoleName
                                       select Role;

                    foreach (var RoleToBuild in RolesToBuild)
                    {
                        Children child = new Children();
                        child.Operation = "EQUALS";
                        Key key = new Key();
                        key.Property = "attribute.rbpgPositionCode";
                        key.SourceId = string.Empty;
                        key.Type = "IDENTITY";
                        child.Key = key;
                        child.StringVlaue = RoleToBuild.RoleCritera;
                        child.Child = null;
                        childrens.Add(child);
                    }
                return childrens;
            }
            else
            {
                throw new FileNotFoundException();
            }
        }
    }
}

BuildRoleCriteraController

using SailPointRoleMaintance.Private.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SailPointRoleMaintance.Private.Controllers
{
    public class BuildRoleCriteraController
    {
        public static List<Children> BuildSailPointRoleCriteraList(List<RoleCSV> TemplatedRoles)
        {
            List<Children> RoleCritera = new List<Children>();
            foreach (RoleCSV TemplatedRole in TemplatedRoles)
            {
                string SourceID = string.Empty;
                Children child = new Children();
                child.Operation = "EQUALS";
                Key key = new Key();
                key.SourceId = SourceID;
                key.Type = "IDENTITY";
                key.Property = "attribute.rbpgPositionCode";
                child.Key = key;
                child.StringVlaue = TemplatedRole.RoleCritera;
                child.Child = null;
                RoleCritera.Add(child);
            }
            return RoleCritera;
        }
    }
}

BuildSailPointRoleFromCSVFile

using SailPointRoleMaintance.Private.Models;
using SailPointRoleMaintance.Private.Controllers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SailPointRoleMaintance.Private.Controllers
{
    public class BuildSailPointRoleFromCSVFile
    {
        public static List<SailPointRoleModel> BuildSailPointRolesFromCSVFile(string FilePath, string Tenant)
        {
            //Console.WriteLine("Reading CSV File");
            List<RoleCSV> roles = ReadRoleCSVFileController.ReadRoleCSVFile(FilePath);
            //Console.WriteLine("Creating list of Role Names");
            List<string> RoleNames = GetRoleNameList.GetRoleNames(roles);
            //Console.WriteLine("Create empty list");
            List<SailPointRoleModel> sailPointRoles = new List<SailPointRoleModel>();

            Console.WriteLine("Loop over each RoleName");
            foreach (string RoleName in RoleNames)
            {
                //Console.WriteLine("Pull all critera based on a role name");
                var roleData = from Role in roles
                               where Role.RoleName == RoleName
                               select Role;                      ;

                //Console.Write(roleData.ToString());
                //Console.WriteLine("Create empty class");
                SailPointRoleModel sailPointRoleModel = new SailPointRoleModel();

                //Console.WriteLine($"Set Name for Role");
                sailPointRoleModel.Name = RoleName;
                //Console.WriteLine("Build collection and make it enum");
                List<string> roleDescriptions = new List<string>();
                foreach (RoleCSV role in roleData) 
                {
                    roleDescriptions.Add(role.RoleDescription);
                }
                sailPointRoleModel.Description = roleDescriptions[0];
                //Console.WriteLine("Build Owner");
                RoleOwner roleOwner = BuildOwner.BuildRoleOwner(Tenant);

                //Console.WriteLine("Set Owner to Role");
                sailPointRoleModel.Owner = roleOwner;

                //Console.WriteLine("Build Role Critera");
                Criteria criteria = new Criteria();
                criteria.Operation = "AND";
                criteria.Key = null;
                criteria.StringValue = null;
                
                //build children objects
                Children lifeCycleState = new Children();
                lifeCycleState.Operation = "OR";
                lifeCycleState.Key = null;
                lifeCycleState.StringVlaue = null;
                lifeCycleState.Child = BuildBaseRoleCriteraListController.BuildBaseLifeCycleCriteraList();

                Children AuotProvision = new Children();
                AuotProvision.Operation = "OR";
                AuotProvision.Key = null;
                AuotProvision.StringVlaue = null;
                AuotProvision.Child = BuildBaseRoleCriteraListController.BuildBaseAutoProvisionCriteraList();

                Children UserType = new Children();
                UserType.Operation = "OR";
                UserType.Key = null;
                UserType.StringVlaue = null;
                UserType.Child = BuildBaseRoleCriteraListController.BuildBaseUserTypeCriteraList(Tenant);

                Children RBPG = new Children();
                RBPG.Operation = "OR";
                RBPG.Key = null;
                RBPG.StringVlaue = null;
                RBPG.Child = BuildRBPGCriteraRoleController.BuildRBPGRoleCritera(FilePath: FilePath, RoleName: RoleName);

                criteria.Children = new List<Children>();
                criteria.Children.Add(lifeCycleState);
                criteria.Children.Add(AuotProvision);
                criteria.Children.Add(UserType);
                criteria.Children.Add(RBPG);

                //Console.WriteLine("Build Membership");
                Memebership memebership = new Memebership();
                memebership.Type = "STANDARD";
                memebership.Criteria = criteria;
                memebership.Identites = null;
                //Console.WriteLine("Set Membership of role");
                sailPointRoleModel.Memebership = memebership;
                sailPointRoleModel.LegacyMembershipInfo = null;
                sailPointRoleModel.Enabled = false;
                sailPointRoleModel.Requestable = false;

                //Console.WriteLine("Add Role to List");
                sailPointRoles.Add(sailPointRoleModel);
            }
            //Console.WriteLine("Return List");
            return sailPointRoles;
        }
    }
}

FindSailPointRoleController

using RestSharp;
using SailPointRoleMaintance.Private.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json.Serialization;
using System.Text.Json;
using System.Threading.Tasks;

namespace SailPointRoleMaintance.Private.Controllers
{
    public class FindSailPointRoleController
    {
        public static RestResponse FindSailPointRole(string Tenant, string BearerToken, RoleCSV roleCSV)
        {
            string hostURL = $"https://{Tenant}.api.identitynow.com";
            string endPoint = $"/v3/search";
            List<RestResponse> restResponses = new List<RestResponse>();
            SailPointSearchRole sailPointSearchRole = new SailPointSearchRole();
            sailPointSearchRole.Indices = new List<string>();
            sailPointSearchRole.Indices.Add("roles");
            sailPointSearchRole.Query = new SearchQuery();
            sailPointSearchRole.Query.Query = $"\"{roleCSV.RoleName}\"";

            var options = new System.Text.Json.JsonSerializerOptions
            {
                PropertyNameCaseInsensitive = true,
                NumberHandling = JsonNumberHandling.AllowReadingFromString,
                IncludeFields = true
            };

            RestClient restClient = new RestClient(hostURL);
            RestRequest restRequest = new RestRequest(endPoint, Method.Post);
            restRequest.AddHeader("Content-Type", "application/json");
            restRequest.AddHeader("Accept", "application/json");
            restRequest.AddHeader("Authorization", $"Bearer {BearerToken}");
            restRequest.AddBody(JsonSerializer.Serialize<SailPointSearchRole>(sailPointSearchRole, options));

            RestResponse restResponse = restClient.Execute(restRequest);

            return restResponse;
        }
    }
}

GetRoleNameList

using SailPointRoleMaintance.Private.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SailPointRoleMaintance.Private.Controllers
{
    public class GetRoleNameList
    {
        public static List<string> GetRoleNames(List<RoleCSV> RoleData)
        {
            List<string> RoleNames = RoleData.Select(RoleObject => RoleObject.RoleName).Distinct().ToList();
            return RoleNames;
        }
    }
}

NewSailPointRoleController

using CsvHelper;
using RestSharp;
using SailPointRoleMaintance.Private.Models;
using SailPointRoleMaintance.Public.Views;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;

namespace SailPointRoleMaintance.Private.Controllers
{
    public class NewSailPointRoleController
    {
        public static List<SailPointRoleModel> NewSailPointRolesObjects(int NumberOfRoles, int StartWith, string Tenant)
        {
            List< SailPointRoleModel > Roles = new List< SailPointRoleModel >();

            int CurrentCount = 1;
            while (CurrentCount <= NumberOfRoles)
            {
                SailPointRoleModel sailPointRole = new SailPointRoleModel();
                sailPointRole.Name = "Shell " + StartWith.ToString();
                sailPointRole.Description = "This is a shell role for being able to quickly build out a base config.";


                //Configure the base default owner of this role.
                RoleOwner roleOwner = BuildOwner.BuildRoleOwner(Tenant);

                sailPointRole.Owner = roleOwner;


                Criteria criteria = new Criteria();
                criteria.Operation = "AND";
                criteria.Key = null;
                criteria.StringValue = null;

                criteria.Children = BaseRoleCriteraController.BuildBaseRoleCritera(Tenant);

                Memebership memebership = new Memebership();
                memebership.Type = "STANDARD";
                memebership.Criteria = criteria;
                memebership.Identites = null;
                sailPointRole.Memebership = memebership;
                sailPointRole.LegacyMembershipInfo = null;
                sailPointRole.Enabled = false;
                sailPointRole.Requestable = false;

                AccessRequestConfig accessRequestConfig = new AccessRequestConfig();
                accessRequestConfig.CommentRequired = false;
                accessRequestConfig.DenialCommentsRequired = false;
                accessRequestConfig.ApprovalSchemas = null;

                sailPointRole.AccessRequestConfig = accessRequestConfig;
                sailPointRole.RevocationRequestConfig = null;
                sailPointRole.Segments = null;

                Roles.Add(sailPointRole);
                CurrentCount++;
                StartWith++;
            }
            return Roles;
        }
    }
}

ReadRoleCSVFileController

using CsvHelper;
using SailPointRoleMaintance.Private.Models;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SailPointRoleMaintance.Private.Controllers
{
    public class ReadRoleCSVFileController
    {
        public static List<RoleCSV> ReadRoleCSVFile(string FilePath)
        {
            List<RoleCSV> templetRoles = new List<RoleCSV>();
            using (var reader = new StreamReader(FilePath))
            {
                using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))
                {
                    var records = csv.GetRecords<RoleCSV>().ToList();
                    foreach (var record in records)
                    {
                        RoleCSV roleCSV = new RoleCSV();
                        roleCSV.RoleName = record.RoleName;
                        roleCSV.RoleDescription = record.RoleDescription;
                        roleCSV.RoleCritera = record.RoleCritera;
                        templetRoles.Add(roleCSV);
                    }
                }
            }
            return templetRoles;
        }
    }
}

SailPointBearerTokenController

using RestSharp;
using SailPointRoleMaintance.Private.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json.Serialization;
using System.Text.Json;
using System.Threading.Tasks;

namespace SailPointRoleMaintance.Private.Controllers
{
    public class SailPointBearerTokenController
    {
        public static SailPointToken? NewBearerToken(string Tenant, string ClientId, string ClientSecert)
        {
            string AuthUrl = $"https://{Tenant}.api.identitynow.com/oauth/token?grant_type=client_credentials&client_id={ClientId}&client_secret={ClientSecert}";

            RestClient restClient = new RestClient($"https://{Tenant}.api.identitynow.com");
            RestRequest restRequest = new RestRequest($"/oauth/token?grant_type=client_credentials&client_id={ClientId}&client_secret={ClientSecert}", Method.Post);

            restRequest.AddHeader("Accept", "application/json");
            restRequest.AddHeader("Content-Type", "application/json");
            RestResponse restResponse = restClient.Execute(restRequest);


            Console.Write(restResponse.Content.ToString());
            var options = new System.Text.Json.JsonSerializerOptions
            {
                PropertyNameCaseInsensitive = true,
                NumberHandling = JsonNumberHandling.AllowReadingFromString,
                IncludeFields = true
            };

            SailPointToken bearerToken = JsonSerializer.Deserialize<SailPointToken>(restResponse.Content, options);
            return bearerToken;
        }
    }
}

Building the Views

NewSailPointRole

using RestSharp;
using System;
using System.Collections.Generic;
using System.Management.Automation;
using System.Text;
using System.Text.Json.Serialization;
using System.Text.Json;
using SailPointRoleMaintance.Private.Models;
using System.Reflection.Metadata.Ecma335;
using SailPointRoleMaintance.Private.Controllers;
using System.Runtime.CompilerServices;

namespace SailPointRoleMaintance.Public.Views
{
    [Cmdlet(VerbsCommon.New, "SailPointRoleTemplate")]
    [OutputType(typeof(SailPointRoleModel))]
    public class NewSailPointRoleTemplate : PSCmdlet
    {
        [Parameter()]
        public string Tenant {get;set;}
        [Parameter()]
        public string ClientId { get;set;}
        [Parameter()]
        public string ClientSecert { get;set;}
        [Parameter()]
        public int NumberOfRoles { get;set;}
        [Parameter()]
        public int StartWith { get;set;}

        protected override void BeginProcessing()
        {
            base.BeginProcessing();
        }
        protected override void ProcessRecord() 
        {

            List< SailPointRoleModel> sailPointRoles = NewSailPointRoleController.NewSailPointRolesObjects(NumberOfRoles: NumberOfRoles, StartWith: StartWith, Tenant: Tenant);
            SailPointToken bearerToken = SailPointBearerTokenController.NewBearerToken(Tenant: Tenant, ClientId: ClientId, ClientSecert: ClientSecert);
            DateTime Year = DateTime.Now;

            WriteObject(bearerToken);
            foreach (SailPointRoleModel sailPointRole in sailPointRoles)
            {
                RestClient restClient = new RestClient($"https://{Tenant}.api.identitynow.com");
                RestRequest restRequest = new RestRequest($"v{Year.Year.ToString()}/roles", Method.Post);
                restRequest.AddHeader("Accept", "application/json");
                restRequest.AddHeader("Content-Type", "application/json");
                restRequest.AddHeader("Authorization", $"{bearerToken.TokenType} {bearerToken.AccessToken}");

                var options = new System.Text.Json.JsonSerializerOptions
                {
                    PropertyNameCaseInsensitive = true,
                    NumberHandling = JsonNumberHandling.AllowReadingFromString,
                    IncludeFields = true
                };


                WriteObject(JsonSerializer.Serialize<SailPointRoleModel>(sailPointRole, options));
                restRequest.AddBody(JsonSerializer.Serialize<SailPointRoleModel>(sailPointRole, options));
                RestResponse restResponse = restClient.Execute(restRequest);
                WriteObject(restResponse);

                WriteObject(JsonSerializer.Deserialize<SailPointRoleModel>(restResponse.Content, options));
            }
             
        }
        protected override void EndProcessing() { }
    }
 }

NewSailPointRoleFromFile

using RestSharp;
using SailPointRoleMaintance.Private.Controllers;
using SailPointRoleMaintance.Private.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Management.Automation;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;

namespace SailPointRoleMaintance.Public.Views
{
    [Cmdlet(VerbsCommon.New, "SailPointRoleFromFile")]
    [OutputType(typeof(SailPointRoleModel))]
    public class NewSailPointRoleFromFile : PSCmdlet
    {
        [Parameter()]
        public string Tenant {  get; set; }
        [Parameter()]
        public string ClientID {  get; set; }
        [Parameter()]
        public string ClientSecret { get; set; }
        [Parameter()]
        public string FilePath { get; set; }

        protected override void BeginProcessing()
        {
            base.BeginProcessing();
        }

        protected override void ProcessRecord()
        {
            List<SailPointRoleModel> sailPointRoleModels = BuildSailPointRoleFromCSVFile.BuildSailPointRolesFromCSVFile(FilePath: FilePath, Tenant:Tenant);
            SailPointToken sailPointBearerToken = SailPointBearerTokenController.NewBearerToken(Tenant: Tenant, ClientId: ClientID, ClientSecert: ClientSecret);

            foreach (SailPointRoleModel sailPointRoleModel in sailPointRoleModels)
            {
                var options = new System.Text.Json.JsonSerializerOptions
                {
                    PropertyNameCaseInsensitive = true,
                    NumberHandling = JsonNumberHandling.AllowReadingFromString,
                    IncludeFields = true
                };
                WriteObject(JsonSerializer.Serialize<SailPointRoleModel>(sailPointRoleModel, options));
                RestClient restClient = new RestClient($"https://{Tenant}.api.identitynow.com");
                RestRequest restRequest = new RestRequest("/v3/roles", Method.Post);
                restRequest.AddHeader("Content-Type", "application/json");
                restRequest.AddHeader("Accept", "application/json");
                restRequest.AddHeader("Authorization", $"Bearer {sailPointBearerToken.AccessToken}");
                restRequest.AddBody(JsonSerializer.Serialize<SailPointRoleModel>(sailPointRoleModel, options));
                RestResponse restResponse = restClient.Execute(restRequest);
                WriteObject(restResponse);
                SailPointRoleModel returnObject = JsonSerializer.Deserialize<SailPointRoleModel>(restResponse.Content, options);
                WriteObject(returnObject);
            }
        }

        protected override void EndProcessing()
        {
            base.EndProcessing();
        }
    }
}

Building the Tests

TestBaseRoleCriteraControllerTest

using SailPointRoleMaintance.Private.Controllers;
using SailPointRoleMaintance.Private.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Management.Automation;
using System.Text;
using System.Threading.Tasks;

namespace SailPointRoleMaintance.Public.Tests
{
    [Cmdlet(VerbsDiagnostic.Test, "BaseRoleCriteraController")]
    [OutputType(typeof(List<Children>))]
    public class TestBaseRoleCriteraControllerTest : PSCmdlet
    {

        protected override void BeginProcessing()
        {
            base.BeginProcessing();
        }
        protected override void ProcessRecord()
        {
            BaseRoleCriteraController baseRoleCriteraController = new BaseRoleCriteraController();
            List<Children> testData = BaseRoleCriteraController.BuildBaseRoleCritera("ssmhc-sb");
            WriteObject(testData);
        }
        protected override void EndProcessing()
        {
            base.EndProcessing();
        }

    }
}

BuildRBPGCriteraRoleControllerTest

using SailPointRoleMaintance.Private.Controllers;
using SailPointRoleMaintance.Private.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Management.Automation;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace SailPointRoleMaintance.Public.Tests
{
    [Cmdlet(VerbsDiagnostic.Test, "BuildRBPGCriteraRoleController")]
    [OutputType(typeof(List<Children>))]
    public class BuildRBPGCriteraRoleControllerTest : PSCmdlet
    {
        protected override void BeginProcessing()
        {
            base.BeginProcessing();
        }
        protected override void ProcessRecord()
        {
            Console.WriteLine($"{Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), @"RoleBuilderTemplate.csv")}");
            List<Children> testData = BuildRBPGCriteraRoleController.BuildRBPGRoleCritera(FilePath: $"{Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), @"RoleBuilderTemplate.csv")}", RoleName: "Automated Role Testing");
            WriteObject(testData);
        }
        protected override void EndProcessing()
        {
            base.EndProcessing();
        }
    }
}

BuildRoleCriteraControllerTest

using SailPointRoleMaintance.Private.Controllers;
using SailPointRoleMaintance.Private.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Management.Automation;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace SailPointRoleMaintance.Public.Tests
{
    [Cmdlet(VerbsDiagnostic.Test, "BuildRoleCriteraController")]
    [OutputType(typeof(List<Children>))]
    public class BuildRoleCriteraControllerTest : PSCmdlet
    {
        protected override void BeginProcessing()
        {
            base.BeginProcessing();
        }
        protected override void ProcessRecord()
        {
            List<Children> testData = BuildRoleCriteraController.BuildSailPointRoleCriteraList(ReadRoleCSVFileController.ReadRoleCSVFile(FilePath: $"{Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), @"RoleBuilderTemplate.csv")}"));
            WriteObject(testData);
        }
        protected override void EndProcessing()
        {
            base.EndProcessing();
        }
    }
}

BuildSailPointRoleFromCSVFileTest

using SailPointRoleMaintance.Private.Controllers;
using SailPointRoleMaintance.Private.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Management.Automation;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace SailPointRoleMaintance.Public.Tests
{
    [Cmdlet(VerbsDiagnostic.Test, "BuildSailPointRoleFromCSVFile")]
    [OutputType(typeof(SailPointRoleModel))]
    public class BuildSailPointRoleFromCSVFileTest : PSCmdlet
    {
        protected override void BeginProcessing()
        {
            base.BeginProcessing();
        }
        protected override void ProcessRecord()
        {
            List<SailPointRoleModel> sailPointRoles = BuildSailPointRoleFromCSVFile.BuildSailPointRolesFromCSVFile(FilePath: $"{Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), @"RoleBuilderTemplate.csv")}", Tenant: "Redacted");
            foreach(SailPointRoleModel sailPointRole in sailPointRoles)
            {
                WriteObject(sailPointRole);
            }
        }
        protected override void EndProcessing()
        {
            base.EndProcessing();
        }
    }
}

FindSailPointRoleControllerTest

using RestSharp;
using SailPointRoleMaintance.Private.Controllers;
using SailPointRoleMaintance.Private.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Management.Automation;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace SailPointRoleMaintance.Public.Tests
{
    [Cmdlet(VerbsDiagnostic.Test, "FindSailPointRoleController")]
    [OutputType(typeof(SailPointRoleModel))]
    public class FindSailPointRoleControllerTest : PSCmdlet
    {
        protected override void BeginProcessing()
        {
            base.BeginProcessing();
        }
        protected override void ProcessRecord()
        {
            List<RestResponse> restResponses = new List<RestResponse>();
            SailPointToken token = SailPointBearerTokenController.NewBearerToken(Tenant: "Redacted", ClientId: "Redacted", ClientSecert: "Redacted");
            List<RoleCSV> roles = ReadRoleCSVFileController.ReadRoleCSVFile(FilePath: $"{Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), @"RoleBuilderTemplate.csv")}");
            foreach (RoleCSV role in roles)
            {
                RestResponse restResponse = FindSailPointRoleController.FindSailPointRole(Tenant: "Redacted", BearerToken: token.AccessToken, roleCSV: role);
                restResponses.Add(restResponse);
            }
            WriteObject(restResponses);
        }
        protected override void EndProcessing()
        {
            base.EndProcessing();
        }
    }
}

GetRoleNameListTest

using SailPointRoleMaintance.Private.Controllers;
using SailPointRoleMaintance.Private.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Management.Automation;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace SailPointRoleMaintance.Public.Tests
{
    [Cmdlet(VerbsDiagnostic.Test, "GetRoleNameList")]
    [OutputType(typeof(SailPointToken))]
    public class GetRoleNameListTest : PSCmdlet
    {
        protected override void BeginProcessing()
        {
            base.BeginProcessing();
        }
        protected override void ProcessRecord()
        {
            var testData = GetRoleNameList.GetRoleNames((ReadRoleCSVFileController.ReadRoleCSVFile(FilePath: $"{Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), @"RoleBuilderTemplate.csv")}")));
            WriteObject(testData);
        }
        protected override void EndProcessing()
        {
            base.EndProcessing();
        }
    }
}

SailPointRoleControllerTest

using SailPointRoleMaintance.Private.Controllers;
using SailPointRoleMaintance.Private.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Management.Automation;
using System.Text;
using System.Threading.Tasks;

namespace SailPointRoleMaintance.Public.Tests
{
    [Cmdlet(VerbsDiagnostic.Test, "SailPointRoleController")]
    [OutputType(typeof(SailPointToken))]
    public class SailPointRoleControllerTest : PSCmdlet
    {
        protected override void BeginProcessing()
        {
            base.BeginProcessing();
        }
        protected override void ProcessRecord()
        {
            List<SailPointRoleModel> testData = NewSailPointRoleController.NewSailPointRolesObjects(StartWith: 1, NumberOfRoles: 2, Tenant: "Redacted");
            WriteObject (testData);

        }
        protected override void EndProcessing()
        {
            base.EndProcessing();
        }
    }
}

ReadRoleCSVFileControllerTest

using SailPointRoleMaintance.Private.Controllers;
using SailPointRoleMaintance.Private.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Management.Automation;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace SailPointRoleMaintance.Public.Tests
{
    [Cmdlet(VerbsDiagnostic.Test, "ReadRoleCSVFileController")]
    [OutputType(typeof(RoleCSV))]
    public class ReadRoleCSVFileControllerTest : PSCmdlet
    {
        protected override void BeginProcessing()
        {
            base.BeginProcessing();
        }
        protected override void ProcessRecord()
        {
            List<RoleCSV> roles = ReadRoleCSVFileController.ReadRoleCSVFile(FilePath: $"{Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), @"RoleBuilderTemplate.csv")}");
            foreach( RoleCSV role in roles )
            {
                WriteObject( role );
            }
        }
        protected override void EndProcessing()
        {
            base.EndProcessing();
        }
    }
}

SailPointBearerTokenControllerTest

using RestSharp;
using SailPointRoleMaintance.Private.Controllers;
using SailPointRoleMaintance.Private.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Management.Automation;
using System.Text;
using System.Threading.Tasks;

namespace SailPointRoleMaintance.Public.Tests
{
    [Cmdlet(VerbsDiagnostic.Test, "SailPointBearerTokenController")]
    [OutputType(typeof(SailPointToken))]
    public class SailPointBearerTokenControllerTest : PSCmdlet
    {

        protected override void BeginProcessing()
        {
            base.BeginProcessing();
        }
        protected override void ProcessRecord()
        {
            SailPointToken token = SailPointBearerTokenController.NewBearerToken(Tenant: "Redacted", ClientId: "Redacted", ClientSecert: "Redacted");
            WriteObject(token);
        }
        protected override void EndProcessing()
        {
            base.EndProcessing();
        }
    }
}

CSV Template

image

Compiling the Code

This can be done with either Visual Studio or Visual Studio Code depending on which tool you have access to. Steps for each are laid out below.

Visual Studio 2022

NOTE: This requires a Visual Studio Professional/Enterprise License based on Microsoft License terms.

  1. Open Visual Studio
  2. Create a new project
  3. Project type is Class Library
  4. Name the project
  5. Target .NET 6.0
  6. Click Create
  7. Create files and folders
  8. Right click on the project
  9. Click Build
    image

Visual Studio Code with .NET CLI

dotnet new -i Microsoft.PowerShell.Standard.Module.Template
  • Run:
cd "location where you want to save your project"
  • Run:
dotnet new psmodule -n NameOfProject
  • Create files and folders
  • Run:
dotnet build 

Running the Code

PowerShell

Importing the module

Import-Module .\SailPointRoleMaintance\bin\Debug\net6.0\SailPointRoleMaintance.dll

Creating the Shell Roles
This cmdlet will create roles in a base configuration. This was designed to quickly add the criteria that every role shares. Then a team member will go back and update the role for the exact requirements needed to build the role. This helps ensure that all roles are built in the same manner.

New-SailPointRoleTemplate -Tenant "Redacted" -ClientId "Redacted" -ClientSecert "Redacted" -NumberOfRoles 3 -StartWith 1

Creating Roles from CSV File
This cmdlet will take the CSV template file that was created and build out the requirements. It then combines the base requirements to build out a full role. A team member will then have to go back and enable the role and add any access required by that role. It was built this way by design to ensure that roles are validated prior to turning them on.

New-SailPointRoleFromFile -Tenant "Redacted" -ClientID "Redacted" -ClientSecret "Redacted" -FilePath "Redacted"

Output of Creating Roles from CSV File

Request             : RestSharp.RestRequest
ContentType         : application/json
ContentLength       : 2164
ContentEncoding     : {}
Content             : {"id":"Redacted","name":"Automated Role Testing3","created":"2024-09-11T12:56:07.093412Z","modified":"2024-09-11T12:56:07.093412Z","description":"This is just to test the payload","owner":{"type":"IDENTITY","id":"Redacted","name":"Redacted"},"entitlements":[],"accessProfiles":[],"membership":{"type":"STANDARD","criteria":{"operation":"AND","key":null,"stringValue":null,"children":[{"operation":"OR","key":null,"stringValue":null,"children":[{"operation":"EQUALS","ke
                      y":{"type":"IDENTITY","property":"attribute.cloudLifecycleState","sourceId":null},"stringValue":"active","children":null},{"operation":"EQUALS","key":{"type":"IDENTITY","property":"attribute.cloudLifecycleState","sourceId":null},"stringValue":"leave","children":null}]},{"operation":"OR","key":null,"stringValue":null,"children":[{"operation":"CONTAINS","key":{"type":"IDENTITY","property":"attribute.snowAccount","sourceId":null},"stringValue":"Provision","children":null},{"operation":"EQUALS","key":{"type":"IDENTITY",
                      "property":"attribute.automateProvisioning","sourceId":null},"stringValue":"True","children":null}]},{"operation":"OR","key":null,"stringValue":null,"children":[{"operation":"EQUALS","key":{"type":"ACCOUNT","property":"attribute.sys_class_name","sourceId":"Redacted"},"stringValue":"user","children":null}]},{"operation":"OR","key":null,"stringValue":null,"children":[{"operation":"EQUALS","key":{"type":"IDENTITY","property":"attribute.rbpgPositionCode","sourceId":""},"stringValue":"123|127|124"
                      ,"children":null},{"operation":"EQUALS","key":{"type":"IDENTITY","property":"attribute.rbpgPositionCode","sourceId":""},"stringValue":"123|128|125","children":null},{"operation":"EQUALS","key":{"type":"IDENTITY","property":"attribute.rbpgPositionCode","sourceId":""},"stringValue":"123|129|126","children":null}]}]},"identities":null},"legacyMembershipInfo":null,"enabled":false,"requestable":false,"accessRequestConfig":{"commentsRequired":null,"denialCommentsRequired":null,"approvalSchemes":[]},"revocationRequestConfi
                      g":{"approvalSchemes":[]},"segments":[],"dimensional":null,"dimensionRefs":null}
StatusCode          : Created
IsSuccessStatusCode : True
IsSuccessful        : True
StatusDescription   :
RawBytes            : {123, 34, 105, 100…}
ResponseUri         : https://Redacted.api.identitynow.com/v3/roles
Server              : nginx
Cookies             : {}
Headers             : {HeaderParameter { Name = Date, Value = Wed, 11 Sep 2024 12:56:07 GMT, Type = HttpHeader, Encode = False, ContentType =  }, HeaderParameter { Name = Transfer-Encoding, Value = chunked, Type = HttpHeader, Encode = False, ContentType =  }, HeaderParameter { Name = Connection, Value = keep-alive, Type = HttpHeader, Encode = False, ContentType =  }, HeaderParameter { Name = Server, Value = nginx, Type = HttpHeader, Encode = False, ContentType =  }…}
ContentHeaders      : {HeaderParameter { Name = Content-Type, Value = application/json, Type = HttpHeader, Encode = False, ContentType =  }, HeaderParameter { Name = Content-Length, Value = 2164, Type = HttpHeader, Encode = False, ContentType =  }}
ResponseStatus      : Completed
ErrorMessage        :
ErrorException      :
Version             : 1.1
RootElement         :
Name                    : Automated Role Testing3
Description             : This is just to test the payload
Owner                   : SailPointRoleMaintance.Private.Models.RoleOwner
AccessProfile           : {}
Memebership             : SailPointRoleMaintance.Private.Models.Memebership
LegacyMembershipInfo    :
Enabled                 : False
Requestable             : False
AccessRequestConfig     : SailPointRoleMaintance.Private.Models.AccessRequestConfig
RevocationRequestConfig : SailPointionRequestConfig
Segments                : {}

Results in ISC

Shell Roles


CSV File

How Role Assignment Criteria Is Populated
As you can see here the role Automated Role Testing has been populated with the exact criteria laid out in the CSV file.
image

Adding Access to the Role

Due to the nature of how roles work, this project was designed with a step for a human to verify that the role is being applied to the correct people. Once the role is verified then the admin will add the access profiles and entitlements manually. This could also be automated, but for the purposes of my organization the risk was too high, and this was how we decided to mitigate it.

Requirements

Required Libraries

  • CsvHelper (33.0.1)
  • PowerShellStandard.Library (5.1.1)
  • RestSharp (108.0.3)

Other Required Software

  • Visual Studio 2022 or Visual Studio Code
  • Nuget
  • .NET 6 SDK

Support OS

  • Window 10
  • Windows 11

Conclusion

This was a very tricky problem to solve. Being able to quickly build the required payload for the roles required a lot of mapping out of the data and understanding the children objects. This project is a massive time saver for my small team (5 team members supporting 60K users). The end result delivered on all requirements while being flexible enough to be used for any application. Based on our requirements, any of this code can be updated easily to fit in any environment. Having the test in place it makes it easy to validate the output of any of the methods. This was a fun little project that has added real value to my team.

2 Likes