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
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.
- Open Visual Studio
- Create a new project
- Project type is Class Library
- Name the project
- Target .NET 6.0
- Click Create
- Create files and folders
- Right click on the project
- Click Build
Visual Studio Code with .NET CLI
-
Install Powershell 7
-
Install .NET SDK https://learn.microsoft.com/en-us/dotnet/core/install/windows
-
Open Powershell 7
-
Run:
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.
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.