Juggernaut1397 commited on
Commit
12bee28
·
1 Parent(s): 07951b7

Add all project files

Browse files
.env ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ OKTA_API_SPEC = "https://raw.githubusercontent.com/shivam13297/Data-connector/refs/heads/main/openapi.json"
2
+ IDENTITY_NOW_API_SPEC = "https://raw.githubusercontent.com/sailpoint-oss/api-specs/refs/heads/main/dereferenced/deref-sailpoint-api.v3.yaml"
3
+ IIQ_API_SPEC = "https://raw.githubusercontent.com/sailpoint-oss/api-specs/refs/heads/main/iiq/sailpoint-api.iiq.yaml"
.gitignore ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # Virtual environments
10
+ .venv
.gradio/certificate.pem ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
3
+ TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
4
+ cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4
5
+ WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu
6
+ ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY
7
+ MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc
8
+ h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+
9
+ 0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U
10
+ A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW
11
+ T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH
12
+ B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC
13
+ B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv
14
+ KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn
15
+ OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn
16
+ jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw
17
+ qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI
18
+ rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
19
+ HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq
20
+ hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL
21
+ ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ
22
+ 3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK
23
+ NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5
24
+ ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur
25
+ TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC
26
+ jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc
27
+ oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq
28
+ 4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA
29
+ mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d
30
+ emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=
31
+ -----END CERTIFICATE-----
.python-version ADDED
@@ -0,0 +1 @@
 
 
1
+ 3.13
README.md CHANGED
@@ -1,12 +0,0 @@
1
- ---
2
- title: DataConnector Demo
3
- emoji: 👀
4
- colorFrom: indigo
5
- colorTo: purple
6
- sdk: gradio
7
- sdk_version: 5.16.1
8
- app_file: app.py
9
- pinned: false
10
- ---
11
-
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
app.py ADDED
@@ -0,0 +1,469 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import requests
2
+ import yaml
3
+ from identityNow import handle_identitynow_call
4
+ from okta import handle_okta_call
5
+ from iiq import handle_iiq_call
6
+ from utils import extract_path_params, extract_query_params
7
+ import gradio as gr
8
+ import os
9
+ from dotenv import load_dotenv
10
+ from pathlib import Path
11
+
12
+ script_dir = Path(__file__).resolve().parent
13
+ env_path = script_dir / '.env'
14
+ load_dotenv(dotenv_path=env_path)
15
+
16
+ def fetch_api_endpoints_yaml(spec_url):
17
+ try:
18
+ response = requests.get(spec_url)
19
+ response.raise_for_status()
20
+ content = response.text
21
+ api_spec = yaml.safe_load(content)
22
+ except Exception as e:
23
+ print(f"Error fetching/parsing YAML spec from {spec_url}: {e}")
24
+ return {}
25
+
26
+ endpoints = {}
27
+ if "paths" not in api_spec:
28
+ print("No endpoints found in the specification.")
29
+ return {}
30
+
31
+ valid_methods = ['get', 'post', 'put', 'delete', 'patch', 'head', 'options']
32
+ for path, methods in api_spec["paths"].items():
33
+ endpoints[path] = {}
34
+ if not methods or not isinstance(methods, dict):
35
+ continue
36
+
37
+ # Get common parameters defined at path level
38
+ common_params = methods.get("parameters", [])
39
+
40
+ for method, details in methods.items():
41
+ if method.lower() not in valid_methods:
42
+ continue
43
+
44
+ # Combine path-level and method-level parameters
45
+ method_params = details.get("parameters", [])
46
+ all_params = common_params + method_params
47
+
48
+ endpoint_info = {
49
+ "summary": details.get("summary", ""),
50
+ "description": details.get("description", ""),
51
+ "parameters": all_params # Include all parameters
52
+ }
53
+ endpoints[path][method.lower()] = endpoint_info
54
+ return endpoints
55
+
56
+ def fetch_api_endpoints_json(spec_url):
57
+ try:
58
+ response = requests.get(spec_url)
59
+ response.raise_for_status()
60
+ api_spec = response.json()
61
+ except Exception as e:
62
+ print(f"Error fetching/parsing JSON spec from {spec_url}: {e}")
63
+ return {}
64
+
65
+ endpoints = {}
66
+ if "paths" not in api_spec:
67
+ print("No endpoints found in the specification.")
68
+ return {}
69
+
70
+ valid_methods = ['get', 'post', 'put', 'delete', 'patch', 'head', 'options']
71
+ for path, methods in api_spec["paths"].items():
72
+ endpoints[path] = {}
73
+ if not methods or not isinstance(methods, dict):
74
+ continue
75
+
76
+ # Get common parameters defined at path level
77
+ common_params = methods.get("parameters", [])
78
+
79
+ for method, details in methods.items():
80
+ if method.lower() not in valid_methods:
81
+ continue
82
+
83
+ # Combine path-level and method-level parameters
84
+ method_params = details.get("parameters", [])
85
+ all_params = common_params + method_params
86
+
87
+ endpoint_info = {
88
+ "summary": details.get("summary", ""),
89
+ "description": details.get("description", ""),
90
+ "parameters": all_params # Include all parameters
91
+ }
92
+ endpoints[path][method.lower()] = endpoint_info
93
+ return endpoints
94
+
95
+ def get_endpoints(spec_choice):
96
+ api_spec_options = {
97
+ "Okta (JSON)": os.getenv("OKTA_API_SPEC"),
98
+ "SailPoint IdentityNow (YAML)": os.getenv("IDENTITY_NOW_API_SPEC"),
99
+ "Sailpoint IIQ (YAML)": os.getenv("IIQ_API_SPEC")
100
+ }
101
+ spec_url = api_spec_options.get(spec_choice)
102
+ if not spec_url:
103
+ return {}
104
+ if "JSON" in spec_choice:
105
+ return fetch_api_endpoints_json(spec_url)
106
+ return fetch_api_endpoints_yaml(spec_url)
107
+
108
+ def group_endpoints(endpoints, spec_choice):
109
+ """Group endpoints with special handling for Okta API"""
110
+ groups = {}
111
+
112
+ # Special handling for Okta endpoints
113
+ if spec_choice == "Okta (JSON)":
114
+ for path, methods in endpoints.items():
115
+ # Remove /api/v1/ prefix and get the first segment
116
+ clean_path = path.replace('/api/v1/', '')
117
+ segments = clean_path.strip("/").split("/")
118
+ group_key = segments[0] if segments else "other"
119
+
120
+ if group_key not in groups:
121
+ groups[group_key] = {}
122
+ groups[group_key][path] = methods
123
+ else:
124
+ # Original grouping logic for other APIs
125
+ for path, methods in endpoints.items():
126
+ segments = path.strip("/").split("/")
127
+ group_key = segments[0] if segments[0] != "" else "other"
128
+ if group_key not in groups:
129
+ groups[group_key] = {}
130
+ groups[group_key][path] = methods
131
+
132
+ return groups
133
+
134
+ with gr.Blocks(
135
+ theme=gr.themes.Default(
136
+ primary_hue=gr.themes.colors.red,
137
+ secondary_hue=gr.themes.colors.gray,
138
+ neutral_hue="slate",
139
+ text_size="md",
140
+ radius_size="md",
141
+ font=gr.themes.GoogleFont("Inter")
142
+ )
143
+ ) as demo:
144
+ gr.Markdown("# Data Connector Demo")
145
+ gr.Markdown("Select an API spec, then click Refresh Endpoints to see available endpoints grouped in accordions.")
146
+
147
+ # Session state
148
+ session_id_state = gr.State("")
149
+ confirmed_endpoints_state = gr.State([])
150
+ display_values_state = gr.State([])
151
+
152
+ max_groups = 100
153
+
154
+ # API Spec Selection
155
+ with gr.Row():
156
+ spec_choice = gr.Radio(
157
+ label="Choose API Spec",
158
+ choices=["Okta (JSON)", "SailPoint IdentityNow (YAML)", "Sailpoint IIQ (YAML)"],
159
+ value="SailPoint IdentityNow (YAML)"
160
+ )
161
+ refresh_eps = gr.Button("Refresh Endpoints", variant="primary")
162
+
163
+ # Loading indicator
164
+ loading_status = gr.Markdown("Select an API spec and click 'Refresh Endpoints' to load available endpoints.")
165
+
166
+ # Create accordion placeholders
167
+ accordion_placeholders = []
168
+ with gr.Column():
169
+ for i in range(max_groups):
170
+ with gr.Accordion(label="", open=False, visible=False) as acc:
171
+ cb = gr.CheckboxGroup(label="", choices=[], value=[])
172
+ accordion_placeholders.append((acc, cb))
173
+
174
+ # Parameter group
175
+ with gr.Group(visible=False) as param_group:
176
+ param_header = gr.Markdown("### Parameters Required") # Default header
177
+ param_components = []
178
+ with gr.Column() as param_container:
179
+ for i in range(5):
180
+ with gr.Group(visible=False) as group:
181
+ display = gr.Markdown(visible=False)
182
+ input_box = gr.Textbox(
183
+ label="Parameter Value",
184
+ visible=False,
185
+ interactive=True
186
+ )
187
+ param_components.append((group, display, input_box))
188
+
189
+ # Authentication sections
190
+ with gr.Group() as identitynow_auth:
191
+ with gr.Row():
192
+ grant_type = gr.Textbox(label="Enter grant_type", value="client_credentials")
193
+ client_id = gr.Textbox(label="Enter client_id")
194
+ client_secret = gr.Textbox(label="Enter client_secret", type="password")
195
+
196
+ with gr.Group(visible=False) as okta_auth:
197
+ api_token = gr.Textbox(label="Enter Okta API Token", type="password")
198
+
199
+ with gr.Group(visible=False) as iiq_auth:
200
+ with gr.Row():
201
+ iiq_username = gr.Textbox(label="Enter IIQ Username")
202
+ iiq_password = gr.Textbox(label="Enter IIQ Password", type="password")
203
+
204
+ api_base_url = gr.Textbox(label="Enter API Base URL")
205
+
206
+ # Buttons
207
+ confirm_endpoints_btn = gr.Button("Submit and Confirm Endpoints", variant="primary")
208
+ call_api_btn = gr.Button("Call API", variant="primary")
209
+
210
+ # Output components
211
+ responses_out = gr.JSON(label="API Responses")
212
+ download_out = gr.File(label="Download Session Data (ZIP)")
213
+
214
+ def update_acc(spec_choice):
215
+ """Update accordions with endpoints"""
216
+ try:
217
+ endpoints = get_endpoints(spec_choice)
218
+
219
+ if not endpoints:
220
+ return [gr.update(visible=False)] * (max_groups * 2) + ["⚠️ No endpoints found"]
221
+
222
+ groups = group_endpoints(endpoints, spec_choice)
223
+ group_keys = list(groups.keys())
224
+
225
+ if not group_keys:
226
+ return [gr.update(visible=False)] * (max_groups * 2) + ["⚠️ No endpoint groups found"]
227
+
228
+ updates = []
229
+
230
+ # Always process exactly max_groups number of slots
231
+ for i in range(max_groups):
232
+ if i < len(group_keys):
233
+ group = group_keys[i]
234
+ choices = []
235
+ # Collect GET endpoints for this group
236
+ for ep, methods in groups[group].items():
237
+ if 'get' in methods:
238
+ summary = methods['get'].get('summary', 'No summary')
239
+ label = f"{ep} | GET - {summary}"
240
+ choices.append(label)
241
+
242
+ # Add updates regardless of choices
243
+ acc_update = gr.update(
244
+ label=f"Group: {group}" if choices else "",
245
+ visible=bool(choices),
246
+ open=(i == 0 and bool(choices))
247
+ )
248
+ cb_update = gr.update(choices=choices, value=[], visible=bool(choices))
249
+ updates.append((acc_update, cb_update))
250
+ else:
251
+ # Fill remaining slots with hidden updates
252
+ acc_update = gr.update(visible=False, label="")
253
+ cb_update = gr.update(visible=False, choices=[], value=[])
254
+ updates.append((acc_update, cb_update))
255
+
256
+ # Flatten updates and add status message
257
+ flattened = []
258
+ for up in updates:
259
+ flattened.extend(up) # This will give us exactly max_groups * 2 updates
260
+
261
+ visible_groups = sum(1 for group in groups.values() if any('get' in methods for methods in group.values()))
262
+ success_msg = f"✅ Loaded {visible_groups} groups with GET endpoints"
263
+ flattened.append(success_msg)
264
+
265
+ return flattened
266
+
267
+ except Exception as e:
268
+ error_msg = f"❌ Error loading endpoints: {str(e)}"
269
+ return [gr.update(visible=False)] * (max_groups * 2) + [error_msg]
270
+
271
+ def update_auth_fields(api_choice):
272
+ updates = {
273
+ "Okta (JSON)": [False, True, False],
274
+ "SailPoint IdentityNow (YAML)": [True, False, False],
275
+ "Sailpoint IIQ (YAML)": [False, False, True]
276
+ }
277
+ visibilities = updates.get(api_choice, [False, False, False])
278
+ return [
279
+ gr.update(visible=visibilities[0]),
280
+ gr.update(visible=visibilities[1]),
281
+ gr.update(visible=visibilities[2])
282
+ ]
283
+
284
+ def confirm_selected_endpoints(spec_choice, *checkbox_values):
285
+ """Collect and confirm all selected endpoints"""
286
+ all_selected = []
287
+
288
+ for checkbox_group in checkbox_values:
289
+ if isinstance(checkbox_group, list) and checkbox_group:
290
+ all_selected.extend(checkbox_group)
291
+
292
+ # Get the API spec
293
+ endpoints = get_endpoints(spec_choice)
294
+
295
+ # Process selected endpoints to find ones with parameters
296
+ endpoints_with_params = []
297
+ display_values = [] # Track display values
298
+ for selection in all_selected:
299
+ endpoint = selection.split(" | ")[0]
300
+ endpoint_spec = endpoints.get(endpoint, {}).get('get', {})
301
+
302
+ # Get both path and query parameters
303
+ path_params = extract_path_params(endpoint)
304
+ query_params = extract_query_params(endpoint_spec)
305
+
306
+ if path_params or query_params:
307
+ endpoints_with_params.append((endpoint, path_params, query_params))
308
+ display_values.append(f"Endpoint: {endpoint}")
309
+
310
+ # Create updates for all components
311
+ updates = []
312
+ updates.append(endpoints_with_params) # confirmed_endpoints_state
313
+ updates.append(display_values) # display_values_state
314
+
315
+ # Determine parameter types present
316
+ has_path_params = any(path_params for _, path_params, _ in endpoints_with_params)
317
+ has_query_params = any(query_params for _, _, query_params in endpoints_with_params)
318
+
319
+ # Set appropriate header based on parameter types
320
+ if has_path_params and has_query_params:
321
+ header_text = "### Path and Query Parameters Required"
322
+ elif has_path_params:
323
+ header_text = "### Path Parameters Required/Optional"
324
+ elif has_query_params:
325
+ header_text = "### Query Parameters Required/Optional"
326
+ else:
327
+ header_text = "### Parameters Required"
328
+
329
+ # Update group visibility and header text separately
330
+ updates.append(gr.update(visible=bool(endpoints_with_params))) # param_group visibility
331
+ updates.append(gr.update(value=header_text, visible=True)) # param_header update
332
+
333
+ # Then create updates for each parameter component
334
+ param_index = 0
335
+ for i in range(100): # Assuming max 5 endpoints
336
+ if i < len(endpoints_with_params):
337
+ endpoint, path_params, query_params = endpoints_with_params[i]
338
+
339
+ # Handle path parameters
340
+ for param_name in path_params: # Changed: iterate directly over parameter names
341
+ if param_index < 5: # Stay within component limit
342
+ updates.extend([
343
+ gr.update(visible=True),
344
+ gr.update(visible=True, value=f"Endpoint: {endpoint} - Path Parameter"),
345
+ gr.update(
346
+ visible=True,
347
+ label=f"Enter path parameter: {param_name}",
348
+ placeholder=f"Required path parameter for {endpoint}"
349
+ )
350
+ ])
351
+ param_index += 1
352
+
353
+ # Handle query parameters
354
+ for param in query_params: # Changed: handle query parameter tuples
355
+ _, name, required, description = param # Unpack all 4 values
356
+ if param_index < 5: # Stay within component limit
357
+ updates.extend([
358
+ gr.update(visible=True),
359
+ gr.update(visible=True, value=f"Endpoint: {endpoint} - Query Parameter"),
360
+ gr.update(
361
+ visible=True,
362
+ label=f"Enter query parameter: {name}" + (" (Required)" if required else " (Optional)"),
363
+ placeholder=description
364
+ )
365
+ ])
366
+ param_index += 1
367
+
368
+ # Fill remaining parameter components with hidden updates
369
+ while param_index < 5:
370
+ updates.extend([
371
+ gr.update(visible=False),
372
+ gr.update(visible=False, value=""),
373
+ gr.update(visible=False, label="")
374
+ ])
375
+ param_index += 1
376
+
377
+ return updates
378
+
379
+ def handle_api_call(spec_choice, api_base_url, session_id, grant_type, client_id, client_secret,
380
+ api_token, iiq_username, iiq_password, display_values, *args):
381
+ # Create param_values dictionary
382
+ num_params = len(param_components)
383
+ path_params = {}
384
+ query_params = {}
385
+
386
+ for i, display_value in enumerate(display_values):
387
+ if display_value and args[i]:
388
+ endpoint_text = display_value.split(": ")[1].split(" - ")[0]
389
+ param_type = "path" if "Path Parameter" in display_value else "query"
390
+ param_name = args[i].split(":")[0].strip()
391
+
392
+ if param_type == "path":
393
+ path_params[param_name] = args[i]
394
+ else:
395
+ query_params[param_name] = args[i]
396
+
397
+ checkbox_values = args[num_params:]
398
+
399
+ try:
400
+ if spec_choice == "Okta (JSON)":
401
+ return handle_okta_call(api_base_url, api_token, session_id, path_params, query_params, *checkbox_values)
402
+ elif spec_choice == "SailPoint IdentityNow (YAML)":
403
+ return handle_identitynow_call(api_base_url, grant_type, client_id, client_secret,
404
+ session_id, path_params, query_params, *checkbox_values)
405
+ else: # IIQ
406
+ return handle_iiq_call(api_base_url, iiq_username, iiq_password, session_id,
407
+ path_params, query_params, *checkbox_values)
408
+ except Exception as e:
409
+ print(f"Error in handle_api_call: {str(e)}")
410
+ return (
411
+ {"error": f"API call failed: {str(e)}"},
412
+ None,
413
+ session_id,
414
+ f"❌ Error: {str(e)}"
415
+ )
416
+
417
+ # Wire up the events
418
+ spec_choice.change(
419
+ fn=update_auth_fields,
420
+ inputs=[spec_choice],
421
+ outputs=[identitynow_auth, okta_auth, iiq_auth]
422
+ )
423
+
424
+ refresh_eps.click(
425
+ fn=update_acc,
426
+ inputs=spec_choice,
427
+ outputs=[*[accordion_placeholders[i][j] for i in range(max_groups) for j in range(2)], loading_status]
428
+ )
429
+
430
+ confirm_endpoints_btn.click(
431
+ fn=confirm_selected_endpoints,
432
+ inputs=[
433
+ spec_choice,
434
+ *[acc_cb[1] for acc_cb in accordion_placeholders]
435
+ ],
436
+ outputs=[
437
+ confirmed_endpoints_state,
438
+ display_values_state,
439
+ param_group,
440
+ param_header, # Add param_header to outputs
441
+ *[item for group, display, input_box in param_components
442
+ for item in (group, display, input_box)]
443
+ ]
444
+ )
445
+
446
+ call_api_btn.click(
447
+ fn=handle_api_call,
448
+ inputs=[
449
+ spec_choice,
450
+ api_base_url,
451
+ session_id_state,
452
+ grant_type,
453
+ client_id,
454
+ client_secret,
455
+ api_token,
456
+ iiq_username,
457
+ iiq_password,
458
+ display_values_state,
459
+ *[input_box for _, _, input_box in param_components],
460
+ *[acc_cb[1] for acc_cb in accordion_placeholders]
461
+ ],
462
+ outputs=[responses_out, download_out, session_id_state, loading_status]
463
+ )
464
+
465
+ if __name__ == "__main__":
466
+ demo.launch(
467
+ favicon_path="https://www.sailpoint.com/wp-content/uploads/2020/08/favicon.png",
468
+ show_error=True
469
+ )
identityNow.py ADDED
@@ -0,0 +1,141 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import requests
2
+ import datetime
3
+ import json
4
+ import os
5
+ import uuid
6
+ import traceback
7
+ from utils import zip_session_folder, handle_path_parameters
8
+
9
+ def fetch_identitynow_token(api_url, grant_type, client_id, client_secret):
10
+ """Fetch OAuth token for IdentityNow"""
11
+ token_endpoint = api_url.rstrip("/") + "/oauth/token"
12
+ payload = {
13
+ 'grant_type': grant_type,
14
+ 'client_id': client_id,
15
+ 'client_secret': client_secret
16
+ }
17
+ headers = {
18
+ 'Accept': 'application/json',
19
+ 'Content-Type': 'application/x-www-form-urlencoded'
20
+ }
21
+ try:
22
+ response = requests.post(token_endpoint, data=payload, headers=headers)
23
+ response.raise_for_status()
24
+ token_data = response.json()
25
+ access_token = token_data.get("access_token")
26
+ expires_in = token_data.get("expires_in", 0)
27
+ expiry_timestamp = datetime.datetime.now(datetime.timezone.utc).timestamp() + expires_in
28
+ return {
29
+ "access_token": access_token,
30
+ "expiry_timestamp": expiry_timestamp,
31
+ "success": True,
32
+ "message": None
33
+ }
34
+ except Exception as e:
35
+ return {
36
+ "access_token": None,
37
+ "expiry_timestamp": None,
38
+ "success": False,
39
+ "message": str(e)
40
+ }
41
+
42
+ def handle_identitynow_call(api_base_url, oauth_grant, oauth_client, oauth_secret, session_id, param_values, *checkbox_values):
43
+ """Handle IdentityNow API calls with parameter support"""
44
+ if not session_id:
45
+ session_id = str(uuid.uuid4())
46
+
47
+ # Get OAuth token
48
+ token_result = fetch_identitynow_token(
49
+ api_base_url,
50
+ oauth_grant,
51
+ oauth_client,
52
+ oauth_secret
53
+ )
54
+
55
+ if not token_result["success"]:
56
+ return (
57
+ {"error": f"Failed to get OAuth token: {token_result['message']}"},
58
+ None,
59
+ session_id,
60
+ "❌ OAuth token fetch failed"
61
+ )
62
+
63
+ # Format expiry time
64
+ expiry_dt = datetime.datetime.fromtimestamp(token_result["expiry_timestamp"], tz=datetime.timezone.utc)
65
+ expiry_str = expiry_dt.strftime("%Y-%m-%d %H:%M:%S UTC")
66
+ token_msg = f"✅ OAuth token received! Valid until: {expiry_str}"
67
+
68
+ # Process selected endpoints
69
+ responses = {}
70
+ base_save_folder = os.path.join("sessions", session_id, "IdentityNow")
71
+ os.makedirs(base_save_folder, exist_ok=True)
72
+
73
+ headers = {
74
+ 'Accept': 'application/json',
75
+ 'Authorization': f'Bearer {token_result["access_token"]}'
76
+ }
77
+
78
+ # Parse and call selected endpoints
79
+ for selections in checkbox_values:
80
+ if isinstance(selections, list):
81
+ for selection in selections:
82
+ try:
83
+ if " | " in selection:
84
+ endpoint, method_part = selection.split(" | ")
85
+ method = method_part.split(" - ")[0].lower()
86
+ else:
87
+ endpoint = selection
88
+ method = "get"
89
+
90
+ # Handle parameter replacement if needed
91
+ if any(char in endpoint for char in ['{', '}']):
92
+ full_url, error = handle_path_parameters(endpoint, f"{api_base_url}/v3", param_values)
93
+ if error:
94
+ responses[endpoint] = f"Error: {error}"
95
+ continue
96
+ else:
97
+ full_url = f"{api_base_url.rstrip('/')}/v3{endpoint}"
98
+
99
+ print(f"Calling endpoint: {full_url}")
100
+
101
+ r = requests.get(full_url, headers=headers)
102
+ r.raise_for_status()
103
+
104
+ data = r.json() if r.headers.get('content-type', '').startswith('application/json') else r.text
105
+ responses[endpoint] = data
106
+
107
+ # Save response data
108
+ save_response_data(data, endpoint, base_save_folder)
109
+
110
+ except Exception as e:
111
+ responses[endpoint] = f"Error: {traceback.format_exc()}"
112
+
113
+ # Create and return session zip
114
+ zip_filename = create_session_zip(session_id)
115
+ return responses, zip_filename, session_id, "✅ API calls complete!"
116
+
117
+ def save_response_data(data, endpoint, base_save_folder):
118
+ """Save API response data to file"""
119
+ safe_endpoint_name = endpoint.strip("/").replace("/", "_") or "root"
120
+ timestamp = datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
121
+ save_folder = os.path.join(base_save_folder, f"{safe_endpoint_name} ({timestamp})")
122
+ os.makedirs(save_folder, exist_ok=True)
123
+
124
+ filename = os.path.join(save_folder, "data.jsonl")
125
+ with open(filename, "w", encoding="utf-8") as f:
126
+ if isinstance(data, list):
127
+ for item in data:
128
+ f.write(json.dumps(item) + "\n")
129
+ elif isinstance(data, dict):
130
+ f.write(json.dumps(data) + "\n")
131
+ else:
132
+ f.write(str(data))
133
+
134
+ def create_session_zip(session_id):
135
+ """Create ZIP file of session data"""
136
+ session_folder = os.path.join("sessions", session_id)
137
+ zip_file = zip_session_folder(session_folder)
138
+ zip_filename = f"session_{session_id}.zip"
139
+ with open(zip_filename, "wb") as f:
140
+ f.write(zip_file.read())
141
+ return zip_filename
iiq.py ADDED
@@ -0,0 +1,110 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import requests
2
+ import datetime
3
+ import json
4
+ import os
5
+ import uuid
6
+ import traceback
7
+ from utils import zip_session_folder, handle_path_parameters
8
+
9
+ def validate_iiq_credentials(username, password):
10
+ """Validate IIQ credentials"""
11
+ try:
12
+ return {
13
+ "username": username,
14
+ "password": password,
15
+ "success": True,
16
+ "message": None
17
+ }
18
+ except Exception as e:
19
+ return {
20
+ "success": False,
21
+ "message": str(e)
22
+ }
23
+
24
+ def save_response_data(data, endpoint, base_save_folder):
25
+ """Save API response data to file"""
26
+ safe_endpoint_name = endpoint.strip("/").replace("/", "_") or "root"
27
+ timestamp = datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
28
+ save_folder = os.path.join(base_save_folder, f"{safe_endpoint_name} ({timestamp})")
29
+ os.makedirs(save_folder, exist_ok=True)
30
+
31
+ filename = os.path.join(save_folder, "data.jsonl")
32
+ with open(filename, "w", encoding="utf-8") as f:
33
+ if isinstance(data, list):
34
+ for item in data:
35
+ f.write(json.dumps(item) + "\n")
36
+ elif isinstance(data, dict):
37
+ f.write(json.dumps(data) + "\n")
38
+ else:
39
+ f.write(str(data))
40
+
41
+ def create_session_zip(session_id):
42
+ """Create ZIP file of session data"""
43
+ session_folder = os.path.join("sessions", session_id)
44
+ zip_file = zip_session_folder(session_folder)
45
+ zip_filename = f"session_{session_id}.zip"
46
+ with open(zip_filename, "wb") as f:
47
+ f.write(zip_file.read())
48
+ return zip_filename
49
+
50
+ def handle_iiq_call(api_base_url, username, password, session_id, param_values, *checkbox_values):
51
+ """Handle IIQ API calls with parameter support"""
52
+ if not session_id:
53
+ session_id = str(uuid.uuid4())
54
+
55
+ # Validate credentials
56
+ cred_result = validate_iiq_credentials(username, password)
57
+ if not cred_result["success"]:
58
+ return (
59
+ {"error": f"Failed to validate IIQ credentials: {cred_result['message']}"},
60
+ None,
61
+ session_id,
62
+ "❌ IIQ authentication failed"
63
+ )
64
+
65
+ responses = {}
66
+ base_save_folder = os.path.join("sessions", session_id, "IIQ")
67
+ os.makedirs(base_save_folder, exist_ok=True)
68
+
69
+ auth = (cred_result["username"], cred_result["password"])
70
+
71
+ # Process selected endpoints
72
+ for selections in checkbox_values:
73
+ if isinstance(selections, list):
74
+ for selection in selections:
75
+ try:
76
+ if " | " in selection:
77
+ endpoint, method_part = selection.split(" | ")
78
+ method = method_part.split(" - ")[0].lower()
79
+ else:
80
+ endpoint = selection
81
+ method = "get"
82
+
83
+ # Handle parameter replacement if needed
84
+ if any(char in endpoint for char in ['{', '}']):
85
+ full_url, error = handle_path_parameters(endpoint, api_base_url, param_values)
86
+ if error:
87
+ responses[endpoint] = f"Error: {error}"
88
+ continue
89
+ else:
90
+ full_url = f"{api_base_url.rstrip('/')}{endpoint}"
91
+
92
+ print(f"Calling IIQ endpoint: {full_url}")
93
+
94
+ r = requests.get(full_url, auth=auth)
95
+ r.raise_for_status()
96
+
97
+ data = r.json() if r.headers.get('content-type', '').startswith('application/json') else r.text
98
+ responses[endpoint] = data
99
+
100
+ # Save response data
101
+ save_response_data(data, endpoint, base_save_folder)
102
+
103
+ except Exception as e:
104
+ responses[endpoint] = f"Error: {traceback.format_exc()}"
105
+
106
+ # Create and return session zip
107
+ zip_filename = create_session_zip(session_id)
108
+ return responses, zip_filename, session_id, "✅ IIQ API calls complete!"
109
+
110
+ # Include save_response_data and create_session_zip functions (same as IdentityNow)
okta.py ADDED
@@ -0,0 +1,109 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import requests
2
+ import datetime
3
+ import json
4
+ import os
5
+ import uuid
6
+ import traceback
7
+ from utils import zip_session_folder, handle_path_parameters
8
+
9
+ def validate_okta_token(api_token):
10
+ """Validate Okta API token"""
11
+ try:
12
+ return {
13
+ "access_token": api_token,
14
+ "success": True,
15
+ "message": None
16
+ }
17
+ except Exception as e:
18
+ return {
19
+ "success": False,
20
+ "message": str(e)
21
+ }
22
+
23
+ def save_response_data(data, endpoint, base_save_folder):
24
+ """Save API response data to file"""
25
+ safe_endpoint_name = endpoint.strip("/").replace("/", "_") or "root"
26
+ timestamp = datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
27
+ save_folder = os.path.join(base_save_folder, f"{safe_endpoint_name} ({timestamp})")
28
+ os.makedirs(save_folder, exist_ok=True)
29
+
30
+ filename = os.path.join(save_folder, "data.jsonl")
31
+ with open(filename, "w", encoding="utf-8") as f:
32
+ if isinstance(data, list):
33
+ for item in data:
34
+ f.write(json.dumps(item) + "\n")
35
+ elif isinstance(data, dict):
36
+ f.write(json.dumps(data) + "\n")
37
+ else:
38
+ f.write(str(data))
39
+
40
+ def create_session_zip(session_id):
41
+ """Create ZIP file of session data"""
42
+ session_folder = os.path.join("sessions", session_id)
43
+ zip_file = zip_session_folder(session_folder)
44
+ zip_filename = f"session_{session_id}.zip"
45
+ with open(zip_filename, "wb") as f:
46
+ f.write(zip_file.read())
47
+ return zip_filename
48
+
49
+ def handle_okta_call(api_base_url, api_token, session_id, param_values, *checkbox_values):
50
+ """Handle Okta API calls with parameter support"""
51
+ if not session_id:
52
+ session_id = str(uuid.uuid4())
53
+
54
+ if not api_base_url.startswith('https://'):
55
+ api_base_url = f"https://{api_base_url}"
56
+
57
+ headers = {
58
+ "Accept": "application/json",
59
+ "Authorization": f"SSWS {api_token}"
60
+ }
61
+
62
+ responses = {}
63
+ base_save_folder = os.path.join("sessions", session_id, "Okta")
64
+ os.makedirs(base_save_folder, exist_ok=True)
65
+
66
+ # Process selected endpoints
67
+ for selections in checkbox_values:
68
+ if isinstance(selections, list):
69
+ for selection in selections:
70
+ try:
71
+ if " | " in selection:
72
+ endpoint, method_part = selection.split(" | ")
73
+ method = method_part.split(" - ")[0].lower()
74
+ else:
75
+ endpoint = selection
76
+ method = "get"
77
+
78
+ # Ensure endpoint starts with /api/v1
79
+ if not endpoint.startswith('/api/v1'):
80
+ endpoint = f"/api/v1{endpoint}"
81
+
82
+ # Handle parameter replacement if needed
83
+ if any(char in endpoint for char in ['{', '}']):
84
+ full_url, error = handle_path_parameters(endpoint, api_base_url, param_values)
85
+ if error:
86
+ responses[endpoint] = f"Error: {error}"
87
+ continue
88
+ else:
89
+ full_url = f"{api_base_url.rstrip('/')}{endpoint}"
90
+
91
+ print(f"Calling Okta endpoint: {full_url}")
92
+
93
+ r = requests.get(full_url, headers=headers)
94
+ r.raise_for_status()
95
+
96
+ data = r.json() if r.headers.get('content-type', '').startswith('application/json') else r.text
97
+ responses[endpoint] = data
98
+
99
+ # Save response data
100
+ save_response_data(data, endpoint, base_save_folder)
101
+
102
+ except Exception as e:
103
+ responses[endpoint] = f"Error: {traceback.format_exc()}"
104
+
105
+ # Create and return session zip
106
+ zip_filename = create_session_zip(session_id)
107
+ return responses, zip_filename, session_id, "✅ Okta API calls complete!"
108
+
109
+ # Include save_response_data and create_session_zip functions (same as IdentityNow)
pyproject.toml ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [project]
2
+ name = "data-connector"
3
+ version = "0.1.0"
4
+ description = "Add your description here"
5
+ readme = "README.md"
6
+ requires-python = ">=3.13"
7
+ dependencies = [
8
+ "gradio>=5.16.0",
9
+ "python-dotenv>=1.0.1",
10
+ "pyyaml>=6.0.2",
11
+ "requests>=2.32.3",
12
+ "streamlit>=1.42.0",
13
+ ]
session_28e04043-15a7-4e6b-8dd9-eeddbee5428b.zip ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e6c541638a8ffe93b319ba2254c384b464ee41a58bf110596b776fee40b8ba92
3
+ size 42401
sessions/28e04043-15a7-4e6b-8dd9-eeddbee5428b/IdentityNow/sod-policies (2025-02-19_10-32-26)/data.jsonl ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ {"description": "Admin Accountants", "ownerRef": {"type": "IDENTITY", "id": "9ed56b09b0f548ff888d2f73783dbd3b", "name": "adam.creaney"}, "creatorRef": null, "externalPolicyReference": "https://my.sharepointonline.com/references/myurl", "policyQuery": "attributes.department:\"Accounting\" AND @access(\"WindowsAdministration\")", "compensatingControls": "Accountants with Administrative rights must have MFA and be a part of a regular review of access (certification). Additionally, privileged credentials must be accessed through our PAM provider.", "correctionAdvice": "When no longer appropriate, please revoke the administrative credentials from the individual.", "tags": ["INTERNALCONTROLS", "CUSTOM123", "SOX"], "state": "ENFORCED", "scheduled": false, "creatorId": "95a9b731946541a5b95728d054f880ca", "modifierId": null, "violationOwnerAssignmentConfig": {"assignmentRule": "MANAGER", "ownerRef": null}, "type": "GENERAL", "conflictingAccessCriteria": null, "id": "e166fae0-2252-4f8f-8a54-9e6e5a2963b7", "name": "Admin Accountants", "created": "2024-09-09T15:33:37Z", "modified": "2024-09-09T15:33:37Z"}
2
+ {"description": "Contractors with a CyberArk Account", "ownerRef": {"type": "IDENTITY", "id": "9ed56b09b0f548ff888d2f73783dbd3b", "name": "adam.creaney"}, "creatorRef": null, "externalPolicyReference": "https://my.sharepointonline.com/somecompany/policies/contractors", "policyQuery": "@accounts(source.name:\"CyberArk\") AND identityProfile.name:\"Contractors\"", "compensatingControls": "ACME Corp does not allow for contractors to have CyberArk access. In accordance, this individual must go through additional screening for ABCXYZ.", "correctionAdvice": "Remove access when no longer appropriate.", "tags": ["NIST", "CONTROLX"], "state": "ENFORCED", "scheduled": false, "creatorId": "95a9b731946541a5b95728d054f880ca", "modifierId": null, "violationOwnerAssignmentConfig": {"assignmentRule": "MANAGER", "ownerRef": null}, "type": "GENERAL", "conflictingAccessCriteria": null, "id": "91b5db2b-ea4b-42fe-a80b-ca2076b5a210", "name": "Contractors with a CyberArk Account", "created": "2024-09-09T15:33:37Z", "modified": "2024-09-09T15:33:37Z"}
3
+ {"description": "Non-IT Users with Privileged System Access", "ownerRef": {"type": "IDENTITY", "id": "9ed56b09b0f548ff888d2f73783dbd3b", "name": "adam.creaney"}, "creatorRef": null, "externalPolicyReference": "https://my.sharepointonline.com/policies/abcxyz", "policyQuery": "@access(name:WindowsAdministration) AND NOT attributes.Department:IT", "compensatingControls": "Users with SOD violations are subject to ...", "correctionAdvice": "Choose one", "tags": ["INTERNALCONTROL237", "SOX"], "state": "ENFORCED", "scheduled": false, "creatorId": "95a9b731946541a5b95728d054f880ca", "modifierId": null, "violationOwnerAssignmentConfig": {"assignmentRule": "MANAGER", "ownerRef": null}, "type": "GENERAL", "conflictingAccessCriteria": null, "id": "6891ca33-816a-4f73-a808-c129754dc8bf", "name": "Non-IT Users with Privileged System Access", "created": "2024-09-09T15:33:37Z", "modified": "2024-09-09T15:33:37Z"}
4
+ {"description": "Former contractors who have converted to regular employees should not retain contractor access.", "ownerRef": {"type": "IDENTITY", "id": "9ed56b09b0f548ff888d2f73783dbd3b", "name": "adam.creaney"}, "creatorRef": null, "externalPolicyReference": null, "policyQuery": "@access(name:\"Contractor Base Access\") AND NOT identityProfile.name:Contractors", "compensatingControls": "Review conversion date, determine whether Contractor access is still required", "correctionAdvice": "Revoke contractor access as soon as no longer required", "tags": [], "state": "ENFORCED", "scheduled": false, "creatorId": "95a9b731946541a5b95728d054f880ca", "modifierId": null, "violationOwnerAssignmentConfig": {"assignmentRule": "MANAGER", "ownerRef": null}, "type": "GENERAL", "conflictingAccessCriteria": null, "id": "3d34ebdc-7cf6-4144-ad23-9be21ba61c8c", "name": "Regular Employees with Contractor Access", "created": "2024-09-09T15:33:36Z", "modified": "2024-09-09T15:33:36Z"}
5
+ {"description": "This policy looks across multiple applications for Accounts Receivable and Payable conflicts in accordance with SOX 2.0 compliance controls.", "ownerRef": {"type": "IDENTITY", "id": "9ed56b09b0f548ff888d2f73783dbd3b", "name": "adam.creaney"}, "creatorRef": null, "externalPolicyReference": "https://www.sec.gov/info/smallbus/404guide/intro.shtml", "policyQuery": "@access(id:058f458ade634bcd9bf90db13e0d55c8) AND @access(id:acdcb0f71b4842f1807a19446ea0c3bf)", "compensatingControls": "Manager daily review - check for unauthorized supplier creation and review of daily transactions.", "correctionAdvice": "Remove one of the Cash In or Cash Out conflicts or get VP approval.", "tags": ["ACCOUNTING", "VIOLATION_SOD", "SOX"], "state": "ENFORCED", "scheduled": false, "creatorId": "95a9b731946541a5b95728d054f880ca", "modifierId": null, "violationOwnerAssignmentConfig": {"assignmentRule": "MANAGER", "ownerRef": null}, "type": "CONFLICTING_ACCESS_BASED", "conflictingAccessCriteria": {"leftCriteria": {"name": "Money_In", "criteriaList": [{"type": "ENTITLEMENT", "id": "058f458ade634bcd9bf90db13e0d55c8", "name": "AccountsPayable"}]}, "rightCriteria": {"name": "Money_Out", "criteriaList": [{"type": "ENTITLEMENT", "id": "acdcb0f71b4842f1807a19446ea0c3bf", "name": "AccountsReceivable"}]}}, "id": "af6e6b9d-de33-4369-9f4d-d07f9569a7a6", "name": "Money In & Money Out", "created": "2024-09-09T15:33:32Z", "modified": "2024-09-09T15:33:32Z"}
6
+ {"description": "Policy to enforce that an Invoice Creator does not also have the ability to pay invoices", "ownerRef": {"type": "IDENTITY", "id": "9ed56b09b0f548ff888d2f73783dbd3b", "name": "adam.creaney"}, "creatorRef": null, "externalPolicyReference": " http://acme.policies/sod", "policyQuery": "@access(id:72fdb961abbe443ba9834b82c2fa1ecc) AND @access(id:4d60180b14f841d6b22cb368dc1e5599)", "compensatingControls": "No employee will have sole authority of or responsibility for control to create and approve invoices.\n\n An employee cannot serve as both the creator and approver for the same invoice review under a contract or order.", "correctionAdvice": "Deny or remove access upon discovery of conflicting access unless VP approved or time limited access not to exceed 24 hours", "tags": ["SOD", "VIOLATION_SOD", "SOX"], "state": "ENFORCED", "scheduled": false, "creatorId": "95a9b731946541a5b95728d054f880ca", "modifierId": null, "violationOwnerAssignmentConfig": {"assignmentRule": null, "ownerRef": null}, "type": "CONFLICTING_ACCESS_BASED", "conflictingAccessCriteria": {"leftCriteria": {"name": "Invoice Create", "criteriaList": [{"type": "ENTITLEMENT", "id": "72fdb961abbe443ba9834b82c2fa1ecc", "name": "CreateInvoices"}]}, "rightCriteria": {"name": "Invoice Approve", "criteriaList": [{"type": "ENTITLEMENT", "id": "4d60180b14f841d6b22cb368dc1e5599", "name": "ProcessInvoices"}]}}, "id": "73c0fc38-4a22-45b7-a67a-1979c8f2fb6c", "name": "Invoice Creation and Approval Control", "created": "2024-09-09T15:33:32Z", "modified": "2024-09-09T15:33:32Z"}
7
+ {"description": "Developer Administrator Violation", "ownerRef": {"type": "IDENTITY", "id": "9ed56b09b0f548ff888d2f73783dbd3b", "name": "adam.creaney"}, "creatorRef": null, "externalPolicyReference": "See section 23 of Employee Handbook on Data Handling", "policyQuery": "@access(id:0a6e00f2dda84f258391df8e9d4b6b8e OR id:b7f0ea8ec1d3436a9d430d8f6f686905) AND @access(id:5e5529b158404b68995ec909d92653f1)", "compensatingControls": "Controls go here", "correctionAdvice": "Advice goes here", "tags": ["CUSTOM123"], "state": "ENFORCED", "scheduled": false, "creatorId": "95a9b731946541a5b95728d054f880ca", "modifierId": null, "violationOwnerAssignmentConfig": {"assignmentRule": "MANAGER", "ownerRef": null}, "type": "CONFLICTING_ACCESS_BASED", "conflictingAccessCriteria": {"leftCriteria": {"name": "Administrator", "criteriaList": [{"type": "ENTITLEMENT", "id": "0a6e00f2dda84f258391df8e9d4b6b8e", "name": "SysAdministration"}, {"type": "ENTITLEMENT", "id": "b7f0ea8ec1d3436a9d430d8f6f686905", "name": "Admins"}]}, "rightCriteria": {"name": "Developer", "criteriaList": [{"type": "ENTITLEMENT", "id": "5e5529b158404b68995ec909d92653f1", "name": "Development"}]}}, "id": "0188b34e-e273-4efd-87ee-a42ae49d3c1a", "name": "Developer Administrator Violation", "created": "2024-09-09T15:33:32Z", "modified": "2024-09-09T15:33:32Z"}
8
+ {"description": "No Manager Assigned", "ownerRef": {"type": "IDENTITY", "id": "9ed56b09b0f548ff888d2f73783dbd3b", "name": "adam.creaney"}, "creatorRef": null, "externalPolicyReference": null, "policyQuery": "NOT _exists_:manager.id", "compensatingControls": "Assign a manager", "correctionAdvice": "Assign a manager", "tags": [], "state": "ENFORCED", "scheduled": false, "creatorId": "95a9b731946541a5b95728d054f880ca", "modifierId": null, "violationOwnerAssignmentConfig": {"assignmentRule": null, "ownerRef": null}, "type": "GENERAL", "conflictingAccessCriteria": null, "id": "944735dd-abe8-4bd3-a840-e6bcc65ffddc", "name": "No Manager Assigned", "created": "2024-09-09T15:33:31Z", "modified": "2024-09-09T15:33:31Z"}
sessions/28e04043-15a7-4e6b-8dd9-eeddbee5428b/IdentityNow/transforms (2025-02-19_10-32-27)/data.jsonl ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {"id": "06353c92-7427-4db2-ae02-eff43a95cfe2", "name": "Determine Lifecycle State", "type": "dateCompare", "attributes": {"requiresPeriodicRefresh": "true", "firstDate": {"attributes": {"name": "startDate"}, "type": "identityAttribute"}, "negativeCondition": {"attributes": {"firstDate": {"attributes": {"name": "endDate"}, "type": "identityAttribute"}, "negativeCondition": "inactive", "operator": "gt", "positiveCondition": "active", "secondDate": "now"}, "type": "dateCompare"}, "operator": "gt", "positiveCondition": "prehire", "secondDate": "now"}, "internal": false}
2
+ {"id": "18516076-7ad5-4cc7-b610-765fa8e7f769", "name": "Determine License", "type": "lookup", "attributes": {"input": {"attributes": {"input": {"attributes": {"values": [{"attributes": {"name": "cloudLifecycleState"}, "type": "identityAttribute"}, "-", {"attributes": {"name": "accountType"}, "type": "identityAttribute"}]}, "type": "concat"}}, "type": "lower"}, "table": {"active-": "licensed", "prehire-": "licensed", "loa-": "licensed", "inactive-": "unlicensed", "active-bot": "light", "active-serviceAccount": "light", "default": "unlicensed"}}, "internal": false}
3
+ {"id": "1bfd74db-4ef8-4aab-b1ff-585ffd33c4ff", "name": "Remove Diacritical Marks", "type": "decomposeDiacriticalMarks", "attributes": null, "internal": true}
4
+ {"id": "2d3a1b7d-db1e-482e-bab2-3720b2c523b5", "name": "ISO8601EndDateFormat", "type": "firstValid", "attributes": {"values": [{"attributes": {"attributeName": "endDate", "inputFormat": "MM/dd/yyyy", "outputFormat": "ISO8601", "sourceName": "Employee"}, "type": "dateFormat"}, {"attributes": {"value": "2199-01-01T00:00:00.000Z"}, "type": "static"}]}, "internal": false}
5
+ {"id": "4a73a15b-0b1f-4701-8cb0-a11c204ed66e", "name": "RFC5646 Language Format", "type": "rfc5646", "attributes": null, "internal": true}
6
+ {"id": "8c177084-6194-4a6e-8fe7-9741507afa2e", "name": "Derive FirstInitial+LastName In Upper", "type": "upper", "attributes": {"input": {"attributes": {"values": [{"attributes": {"begin": 0, "end": 1, "input": {"attributes": {"name": "firstname"}, "type": "identityAttribute"}}, "type": "substring"}, {"attributes": {"name": "lastname"}, "type": "identityAttribute"}]}, "type": "concat"}}, "internal": false}
7
+ {"id": "9fb4b2f0-1172-492d-9444-7338e6c97c68", "name": "ISO8601DateFormat", "type": "dateFormat", "attributes": {"inputFormat": "MM/dd/yyyy", "outputFormat": "ISO8601"}, "internal": false}
8
+ {"id": "af81a719-4c7e-44f6-8310-266a7fdbb404", "name": "ToLower", "type": "lower", "attributes": null, "internal": true}
9
+ {"id": "b634a7bd-a45b-4286-a663-1e50aabec2f6", "name": "E.164 Phone Format", "type": "e164phone", "attributes": null, "internal": true}
10
+ {"id": "c0be8f35-2ed1-4dda-ae11-660a0dfd582b", "name": "Use Preferred Name", "type": "displayName", "attributes": null, "internal": true}
11
+ {"id": "c4790c8c-b6e1-4431-af8d-d86180259516", "name": "NoEmail", "type": "firstValid", "attributes": {"values": [{"attributes": {"attributeName": "EMAIL_ADDRESS_WORK", "sourceName": "Workday"}, "type": "accountAttribute"}, {"attributes": {"value": "(none)"}, "type": "static"}]}, "internal": false}
12
+ {"id": "ce88eafe-948c-4877-a18b-da3646c5ab25", "name": "ToUpper", "type": "upper", "attributes": null, "internal": true}
13
+ {"id": "df80db36-0191-4e64-a112-409faa223ca0", "name": "ISO3166 Country Format", "type": "iso3166", "attributes": null, "internal": true}
14
+ {"id": "ffc01973-c5f4-4af9-bae7-57d03317e0f7", "name": "Format GCP Email", "type": "concat", "attributes": {"values": [{"attributes": {"input": {"attributes": {"name": "uid"}, "type": "identityAttribute"}}, "type": "lower"}, "@se-gcp.sailpointtechnologies.com"]}, "internal": false}
sessions/28e04043-15a7-4e6b-8dd9-eeddbee5428b/IdentityNow/workflow-library (2025-02-19_10-32-32)/data.jsonl ADDED
The diff for this file is too large to render. See raw diff
 
sessions/28e04043-15a7-4e6b-8dd9-eeddbee5428b/IdentityNow/workflows (2025-02-19_10-32-29)/data.jsonl ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {"id": "032887bb-1955-47af-9b83-62e37508c166", "name": "Don Test Workflow2", "description": "test workflow", "created": "2024-09-26T13:58:58.679574344Z", "modified": "2024-10-11T19:08:12.389426195Z", "modifiedBy": {"type": "IDENTITY", "id": "a5c91ed8e54b4d8b8feab1ece875f710", "name": "dcoltrain"}, "definition": {"start": "Get Identity", "steps": {"End Step - Success": {"displayName": "", "type": "success"}, "Get Identity": {"actionId": "sp:get-identity", "attributes": {"id.$": "$.trigger.identity.id"}, "description": "", "displayName": "", "nextStep": "Send Email", "type": "action", "versionNumber": 2}, "HTTP Request": {"actionId": "sp:http", "attributes": {"authenticationType": "basic", "basicAuthPassword": "$.secrets.25476482-6c7c-40c1-bf9f-de980c498792", "basicAuthUserName": "dcoltrain", "method": "post", "requestContentType": "json", "requestHeaders": {}, "url": "https://008b0219-5038-4bb8-bb55-587fc7ff78db.mock.pstmn.io/philosophers"}, "displayName": "", "nextStep": "End Step - Success", "type": "action", "versionNumber": 2}, "Send Email": {"actionId": "sp:send-email", "attributes": {"body": "<p>email triggered by attribute change in RL SailPoint ISC</p>\n<p>&nbsp;</p>\n<p>Identity ID for affected record is $.trigger.identity.id</p>", "context": {}, "from": "", "recipientEmailList": ["dcoltrain@radiantlogic.com"], "replyTo": "", "subject": "triggered workflow in RL ISC"}, "displayName": "", "nextStep": "HTTP Request", "type": "action", "versionNumber": 2}}}, "enabled": true, "executionCount": 0, "failureCount": 0, "creator": {"type": "IDENTITY", "id": "a5c91ed8e54b4d8b8feab1ece875f710", "name": "dcoltrain"}, "owner": {"type": "IDENTITY", "id": "a5c91ed8e54b4d8b8feab1ece875f710", "name": "dcoltrain"}, "trigger": {"type": "EVENT", "attributes": {"attributeToFilter": "title", "filter.$": "$.changes[?(@.attribute == \"firstname\")]", "id": "idn:identity-attributes-changed"}}}
2
+ {"id": "42843aa9-296f-4771-b464-3b56d640e3d9", "name": "Don Test Workflow", "description": "", "created": "2024-09-16T17:07:04.423191338Z", "modified": "2024-09-18T13:24:33.505127467Z", "modifiedBy": {"type": "IDENTITY", "id": "a5c91ed8e54b4d8b8feab1ece875f710", "name": "dcoltrain"}, "definition": {"start": "Get Identity", "steps": {"End Step - Success": {"displayName": "", "type": "success"}, "Get Identity": {"actionId": "sp:get-identity", "attributes": {"id.$": "$.trigger.identity.id"}, "description": "", "displayName": "", "nextStep": "Send Email", "type": "action", "versionNumber": 2}, "Send Email": {"actionId": "sp:send-email", "attributes": {"body": "<p>email triggered by attribute change in RL SailPoint ISC</p>\n<p>&nbsp;</p>\n<p>Identity ID for affected record is $.trigger.identity.id</p>", "context": {}, "from": "", "recipientEmailList": ["dcoltrain@radiantlogic.com"], "replyTo": "", "subject": "triggered workflow in RL ISC"}, "displayName": "", "nextStep": "End Step - Success", "type": "action", "versionNumber": 2}}}, "enabled": false, "executionCount": 0, "failureCount": 0, "creator": {"type": "IDENTITY", "id": "a5c91ed8e54b4d8b8feab1ece875f710", "name": "dcoltrain"}, "owner": {"type": "IDENTITY", "id": "a5c91ed8e54b4d8b8feab1ece875f710", "name": "dcoltrain"}, "trigger": {"type": "EVENT", "attributes": {"attributeToFilter": "title", "filter.$": "$.changes[?(@.attribute == \"firstname\")]", "id": "idn:identity-attributes-changed"}}}
3
+ {"id": "6fdb1ed5-f179-477b-ab25-6d433971fd44", "name": "New Employee Update Emergency Contact info v5 multi-value form", "description": "This fires for each new employee and requests (using a form) emergency contact information. This information is saved into the Identity Profile using an API call. Note that this info could be saved into a database or some other system. Also, if the person is in the Engineering Dept, then a confirmation email will not be sent.", "created": "2024-09-09T15:32:59.080786731Z", "modified": "2024-09-09T15:32:59.080786731Z", "modifiedBy": {"type": "IDENTITY", "id": "95a9b731946541a5b95728d054f880ca", "name": "slpt.support"}, "definition": {"start": "Form", "steps": {"Compare Strings": {"choiceList": [{"comparator": "StringEquals", "nextStep": "HTTP Request", "variableA.$": "$.trigger.attributes.department", "variableB": "Engineering"}], "defaultStep": "Send Email", "description": "If the employee is in Engineering department, do not send a confirmation email. This seems to annoy the engineers...", "displayName": "Is Dept = Engineering?", "type": "choice"}, "End Step - Success": {"displayName": "", "type": "success"}, "Form": {"actionId": "sp:forms", "attributes": {"deadline": "1h", "formDefinitionId": "a1a929df-3336-43a2-85f5-c57a1bcf9ab1", "inputForForm_formEmployeeName.$": "$.trigger.attributes.displayName", "notificationBody": "Hello {{$.trigger.attributes.displayName}},\n<br><br>\nPlease add your emergency contact information ASAP. Select the link below to enter your information. \n<br><br>\nThank you.\n<br>", "notificationSubject": "Please Update Your Emergency Contact Info", "recipient.$": "$.trigger.identity.id", "reminder": "1h", "reminderBody": "This is a reminder - Please add your emergency contact information ASAP."}, "description": "Request that the new employee add his/her emergency contact information.", "displayName": "Form - Requests Emergency Info from new employee", "nextStep": "Compare Strings", "type": "action", "versionNumber": 1}, "HTTP Request": {"actionId": "sp:http", "attributes": {"authenticationType": null, "jsonRequestBody": {"Contact Name.$": "$.form.formData.contactName", "Contact Relationship.$": "$.form.formData.relationshipField", "Contact Telephone Number.$": "$.form.formData.telephoneNumber", "Employee Info.$": "$.form.formData.InputFormEmployeeInfo", "Full Info Dump.$": "$.trigger", "Insurance Policy Number.$": "$.form.formData.insurancePolicyNumber", "LifeInsuranc (yes/no).$": "$.form.formData.toggleLifeInsurance", "New Employee Email.$": "$.trigger.attributes.email", "New Employee Name.$": "$.trigger.attributes.displayName"}, "method": "get", "requestContentType": "json", "url": "<<PUT YOUR WebHook.site URL HERE>>", "urlParams": null}, "description": "Call an API and send the info to a system.", "displayName": "Update the System using HTTP Request", "nextStep": "End Step - Success", "type": "action", "versionNumber": 2}, "Send Email": {"actionId": "sp:send-email", "attributes": {"body": "This is a confirmation email with your updated contact information:\n<br><br>\nEmployee = ${newemployee}<br>\nEmail = ${email}<br><br>\n\n\nContact Name = ${contactName}<br>\nRelationship = ${contactRelationship}<br>\nContact Phone Number = ${contactTelephoneNumber}<br><br>\n\nContact Info = ${contactInfo}<br><br>\n\nThank you for providing this important information.", "context": {"contactInfo.$": "$.form.formData.InputFormEmployeeInfo", "contactName.$": "$.form.formData.contactName", "contactRelationship.$": "$.form.formData.relationshipField", "contactTelephoneNumber.$": "$.form.formData.telephoneNumber", "email.$": "$.trigger.attributes.email", "newemployee.$": "$.trigger.attributes.displayName"}, "from": "noreply@sailpoint.com", "recipientEmailList.$": "$.trigger.attributes.email", "replyTo": "noreply@sailpoint.com", "subject": "${newemployee}, Thank you for Updating your Emergency Contact Information "}, "description": "Confirmation Message", "displayName": "Send Email Confirmation to Recipient ", "nextStep": "HTTP Request", "type": "action", "versionNumber": 2}}}, "enabled": false, "executionCount": 0, "failureCount": 0, "creator": {"type": "IDENTITY", "id": "95a9b731946541a5b95728d054f880ca", "name": "slpt.support"}, "owner": {"type": "IDENTITY", "id": "9ed56b09b0f548ff888d2f73783dbd3b", "name": "adam.creaney"}, "trigger": {"type": "EVENT", "attributes": {"attributeToFilter": "uid", "description": "A new employee was just added to the system.", "id": "idn:identity-created", "operatorToFilter": "operatorUid", "operatorUid": "StringContains", "operatorValue": "StringContains"}}}
4
+ {"id": "0ad23132-335b-48e7-adbd-aafea56b426a", "name": "Workflow Mover Access History", "description": "Workflow when an identity moves within an organization to another team, changing managers, etc.\n\nSend access history to new manager.", "created": "2024-09-09T15:32:58.883479256Z", "modified": "2024-09-09T15:32:58.883479256Z", "modifiedBy": {"type": "IDENTITY", "id": "95a9b731946541a5b95728d054f880ca", "name": "slpt.support"}, "definition": {"start": "IsJobTitleChange", "steps": {"Get Identity History": {"actionId": "sp:get-identity-history", "attributes": {"eventTypes": null, "from.$": null, "id.$": "$.trigger.identity.id"}, "description": null, "nextStep": "Send Email", "type": "action"}, "IsJobTitleChange": {"choiceList": [{"comparator": "StringEquals", "nextStep": "Get Identity History", "variableA.$": "$.changes[?(@.attribute=='jobTitle')].attribute", "variableB": "jobTitle"}], "defaultStep": "success", "type": "choice"}, "Send Email": {"actionId": "sp:send-email", "attributes": {"body": null, "recipientId.$": null}, "type": "action"}, "success": {"type": "success"}}}, "enabled": false, "executionCount": 0, "failureCount": 0, "creator": {"type": "IDENTITY", "id": "95a9b731946541a5b95728d054f880ca", "name": "slpt.support"}, "owner": {"type": "IDENTITY", "id": "9ed56b09b0f548ff888d2f73783dbd3b", "name": "adam.creaney"}, "trigger": {"type": "EVENT", "attributes": {"id": "idn:identity-attributes-changed"}}}
5
+ {"id": "7cafa32a-7b54-41b1-8e60-c5bbe1733c78", "name": "Update and Certify Access When an Identity Changes Departments", "description": "Workflow to update a user's access during a mover scenario, such as when a user changes departments or teams.", "created": "2024-09-09T15:32:58.697159627Z", "modified": "2024-09-09T15:32:58.697159627Z", "modifiedBy": {"type": "IDENTITY", "id": "95a9b731946541a5b95728d054f880ca", "name": "slpt.support"}, "definition": {"start": "Wait", "steps": {"Activate Certification Campaign": {"actionId": "sp:activate-campaign", "attributes": {"id.$": "$.createCertificationCampaign.id"}, "description": "Activates the certification campaign created in the previous step.", "nextStep": "success", "type": "action", "versionNumber": 1}, "Compare Strings": {"choiceList": [{"comparator": "StringEquals", "nextStep": "Get Identity", "variableA.$": "$.trigger.changes[?(@.attribute=='department')].newValue", "variableB": "Sales"}], "defaultStep": "success", "description": "Determines whether the identity's department attribute was updated to Sales. If so, the workflow proceeds. Otherwise, the workflow ends.\n\nNOTE: This template makes an assumption in later actions/nodes about the identity's former department. You could use an additional operator with branching steps for different departments for more flexibility.", "type": "choice"}, "Create Certification Campaign": {"actionId": "sp:create-campaign", "attributes": {"description": "Department Change", "duration": "1w", "emailNotificationEnabled": true, "identityId.$": "$.trigger.identity.id", "name.$": "$.trigger.identity.name", "reviewerId.$": "$.getIdentity.manager.id"}, "description": "Creates a certification campaign so that the identity's new manager can review their access, to ensure they have the access they need and don't have access they don't need.", "nextStep": "Activate Certification Campaign", "type": "action", "versionNumber": 1}, "Get Access": {"actionId": "sp:access:get", "attributes": {"accessprofiles": false, "entitlements": false, "getAccessBy": "searchQuery", "query": "name: \"Product Role\"", "roles": true}, "description": "Gets all roles with \"Product Role\" in their name.\n\nIn this example, the identity needs to retain this access despite their department move. If the access gets removed because they no longer meet the role criteria, it must be put back by this workflow. This step retrieves the access for the Manage Access step to add.", "nextStep": "Manage Access", "type": "action", "versionNumber": 1}, "Get Identity": {"actionId": "sp:get-identity", "attributes": {"id.$": "$.trigger.identity.id"}, "description": "Retrieves available details about the identity.", "nextStep": "Get Access", "type": "action", "versionNumber": 1}, "Get Identity 1": {"actionId": "sp:get-identity", "attributes": {"id.$": "$.getIdentity.manager.id"}, "description": "This node is used to gather information about the user's manager to populate their email.", "nextStep": "Send Email", "type": "action", "versionNumber": 1}, "Manage Access": {"actionId": "sp:access:manage", "attributes": {"addIdentities.$": "$.trigger.identity.id", "comments": "Automatically requested as transitory access by workflows", "removeDuration": "2w", "requestType": "GRANT_ACCESS", "requestedItems.$": "$.getAccess.accessItems"}, "description": "Adds the access from the Get Access step to the identity.", "nextStep": "Get Identity 1", "type": "action", "versionNumber": 1}, "Send Email": {"actionId": "sp:send-email", "attributes": {"body": "Attention, \n\nUser ${name} has recently changed departments and is now a member of the ${newDepartment}.", "context": {"name.$": "$.trigger.identityId", "newDepartment.$": "$.trigger.changes[?(@.attribute=='department')].newValue"}, "recipientEmailList.$": "$.getIdentity1.attributes.email", "recipientId.$": "$.getIdentity.manager.id", "subject": "Department Change"}, "description": "Sends an email to the identity's manager. Configure details about this email below.", "nextStep": "Create Certification Campaign", "type": "action", "versionNumber": 2}, "Wait": {"actionId": "sp:sleep", "attributes": {"duration": "1h", "type": "waitFor"}, "description": "Pauses to wait for all other provisioning activities to complete, such as automated role or access profile removals.", "nextStep": "Compare Strings", "type": "action", "versionNumber": 1}, "success": {"description": "Ends the workflow and marks it as a success.", "type": "success"}}}, "enabled": false, "executionCount": 0, "failureCount": 0, "creator": {"type": "IDENTITY", "id": "95a9b731946541a5b95728d054f880ca", "name": "slpt.support"}, "owner": {"type": "IDENTITY", "id": "9ed56b09b0f548ff888d2f73783dbd3b", "name": "adam.creaney"}, "trigger": {"type": "EVENT", "attributes": {"filter.$": "$.changes[?(@.attribute == \"department\")]", "id": "idn:identity-attributes-changed"}}}
6
+ {"id": "1c4fc2f5-f2a0-489f-a8cf-a319483e8bd7", "name": "Service Now ticket-Initiate Onboarding Process ", "description": "Workflow to begin the process of onboarding a new identity.", "created": "2024-09-09T15:32:58.505074994Z", "modified": "2024-09-09T15:32:58.505074994Z", "modifiedBy": {"type": "IDENTITY", "id": "95a9b731946541a5b95728d054f880ca", "name": "slpt.support"}, "definition": {"start": "Manage ServiceNow Ticket", "steps": {"Comparar cadenas": {"choiceList": [{"comparator": "StringEquals", "nextStep": "Send Email 2"}], "defaultStep": "Send Email 4", "type": "choice"}, "Compare Strings": {"choiceList": [{"comparator": "StringEquals", "nextStep": "Send Email", "variableA.$": "$.httpRequest.statusCode", "variableB": "200"}], "defaultStep": "Send Email 1", "description": "Determines whether the HTTP Request step was successful.", "type": "choice"}, "Get Identity": {"actionId": "sp:get-identity", "attributes": {"id.$": "$.trigger.attributes.manager.id"}, "description": "This node is used to gather information about the user's manager to populate their email.", "nextStep": "Compare Strings", "type": "action", "versionNumber": 2}, "Manage ServiceNow Ticket": {"actionId": "sp:snow", "attributes": {"action": "create"}, "nextStep": "Get Identity", "type": "action", "versionNumber": 1}, "Manage ServiceNow Ticket 1": {"actionId": "sp:snow", "attributes": {"action": "get"}, "nextStep": "Comparar cadenas", "type": "action", "versionNumber": 1}, "Send Email": {"actionId": "sp:send-email", "attributes": {"body": "Dear ${manager},<br><p>An ITSM request has been submitted for a new employee named <b style=\"\">${employee}</b> Here are the details of the request:<br>${body}</p><br><p>Thank you,<br> Your Access Team</p>", "context": {"body.$": "$.httpRequest", "employee.$": "$.trigger.identity.name", "manager.$": "$.getIdentity.attributes.firstname"}, "recipientEmailList.$": "$.getIdentity.attributes.email", "replyTo": null, "subject": "New Employee"}, "description": "Sends an email to the new-hire's manager informing them of the service desk ticket creation.", "nextStep": "Wait", "type": "action", "versionNumber": 2}, "Send Email 1": {"actionId": "sp:send-email", "attributes": {"body": "The creation of the service desk ticket for physical assets failed. Please create this ticket manually and verify all other onboarding tasks for the following new identity:\n\nAttributes:\n- Email: ${email}\n- Name: ${displayName}", "context": {"email.$": "$.trigger.attributes.email", "name.$": "$.trigger.attributes.displayName"}, "recipientEmailList.$": "$.getIdentity.attributes.email", "replyTo.$": "", "subject": "Service Desk Ticket Creation Failed"}, "description": "Notifies the new identity's manager that the HTTP request failed.", "nextStep": "failure 1", "type": "action", "versionNumber": 2}, "Send Email 2": {"actionId": "sp:send-email", "attributes": {"context": {}}, "nextStep": "success", "type": "action", "versionNumber": 2}, "Send Email 4": {"actionId": "sp:send-email", "attributes": {"context": {}}, "nextStep": "failure 2", "type": "action", "versionNumber": 2}, "Wait": {"actionId": "sp:sleep", "attributes": {"duration": "2d", "type": "waitFor"}, "nextStep": "Manage ServiceNow Ticket 1", "type": "action", "versionNumber": 1}, "failure 1": {"description": "Ends the workflow in a failure state.", "failureDetails": "The automated joiner workflow failed to create a service desk ticket for physical assets.", "failureName": "Failed service desk ticket", "type": "failure"}, "failure 2": {"description": "Ends the workflow in a failure state.", "failureDetails": "The automated joiner workflow failed to create a service desk ticket for physical assets.", "failureName": "Failed service desk ticket", "type": "failure"}, "success": {"description": "Ends the workflow after all access has been requested and the certification has been activated.", "type": "success"}}}, "enabled": false, "executionCount": 0, "failureCount": 0, "creator": {"type": "IDENTITY", "id": "95a9b731946541a5b95728d054f880ca", "name": "slpt.support"}, "owner": {"type": "IDENTITY", "id": "9ed56b09b0f548ff888d2f73783dbd3b", "name": "adam.creaney"}, "trigger": {"type": "EVENT", "attributes": {"filter.$": "$[?($.attributes.department == \"sales\")]", "id": "idn:identity-created"}}}
7
+ {"id": "e3fcd4f8-a119-420b-9ed9-7e1f4d1abbe2", "name": "Service Now- ticket generation Example", "description": "Creates a ticket on every access request", "created": "2024-09-09T15:32:58.314958308Z", "modified": "2024-09-09T15:32:58.314958308Z", "modifiedBy": {"type": "IDENTITY", "id": "95a9b731946541a5b95728d054f880ca", "name": "slpt.support"}, "definition": {"start": "Manage ServiceNow Ticket", "steps": {"Manage ServiceNow Ticket": {"actionId": "sp:snow", "attributes": {"action": "create"}, "nextStep": "Wait", "type": "action", "versionNumber": 1}, "Manage ServiceNow Ticket 1": {"actionId": "sp:snow", "attributes": {"action": "update", "stateUpdate": "6"}, "nextStep": "failure", "type": "action", "versionNumber": 1}, "Wait": {"actionId": "sp:sleep", "attributes": {"duration": "20m", "type": "waitFor"}, "nextStep": "Manage ServiceNow Ticket 1", "type": "action", "versionNumber": 1}, "failure": {"type": "failure"}}}, "enabled": false, "executionCount": 0, "failureCount": 0, "creator": {"type": "IDENTITY", "id": "95a9b731946541a5b95728d054f880ca", "name": "slpt.support"}, "owner": {"type": "IDENTITY", "id": "9ed56b09b0f548ff888d2f73783dbd3b", "name": "adam.creaney"}, "trigger": {"type": "EVENT", "attributes": {"id": "idn:access-request-post-approval"}}}
8
+ {"id": "879e5652-ba09-4c4f-84a2-817e927d8c89", "name": "Service Now Sign Off Ticket", "description": "", "created": "2024-09-09T15:32:58.104037397Z", "modified": "2024-09-09T15:32:58.104037397Z", "modifiedBy": {"type": "IDENTITY", "id": "95a9b731946541a5b95728d054f880ca", "name": "slpt.support"}, "definition": {"start": "Manage ServiceNow Ticket", "steps": {"Manage ServiceNow Ticket": {"actionId": "sp:snow", "attributes": {"action": "create"}, "nextStep": "Wait", "type": "action", "versionNumber": 1}, "Manage ServiceNow Ticket 1": {"actionId": "sp:snow", "attributes": {"action": "update", "stateUpdate": "7"}, "type": "action", "versionNumber": 1}, "Wait": {"actionId": "sp:sleep", "attributes": {"duration": "10m", "type": "waitFor"}, "nextStep": "Manage ServiceNow Ticket 1", "type": "action", "versionNumber": 1}}}, "enabled": false, "executionCount": 0, "failureCount": 0, "creator": {"type": "IDENTITY", "id": "95a9b731946541a5b95728d054f880ca", "name": "slpt.support"}, "owner": {"type": "IDENTITY", "id": "9ed56b09b0f548ff888d2f73783dbd3b", "name": "adam.creaney"}, "trigger": {"type": "EVENT", "attributes": {"id": "idn:certification-signed-off"}}}
9
+ {"id": "0f653143-eb86-4f56-9c64-c57e9f4ec19e", "name": "Separation of Duties 2023 Workflow", "description": "This workflow creates a certification campaign each time identities are found in violation. This will ensure access is always reviewed and any inappropriate items are revoked.", "created": "2024-09-09T15:32:57.905829825Z", "modified": "2024-09-09T15:32:57.905829825Z", "modifiedBy": {"type": "IDENTITY", "id": "95a9b731946541a5b95728d054f880ca", "name": "slpt.support"}, "definition": {"start": "Get List of Identities", "steps": {"Activate Certification Campaign": {"actionId": "sp:activate-campaign", "attributes": {"id.$": "$.getListOfIdentities.identities"}, "nextStep": "Send Slack Message", "type": "action", "versionNumber": 1}, "Compare Strings": {"choiceList": [{"comparator": "StringMatches", "nextStep": "Activate Certification Campaign", "variableA.$": "$.getListOfIdentities.identities", "variableB.$": "$.getListOfIdentities.identities"}], "defaultStep": "success", "type": "choice"}, "Get List of Identities": {"actionId": "sp:get-identities", "attributes": {"inputQuery": null, "inputSavedSearch": "ade6188f-1743-4913-b43a-e5e8e82c7a04", "searchBy": "savedSearch"}, "description": null, "nextStep": "Compare Strings", "type": "action", "versionNumber": 2}, "Send Slack Message": {"actionId": "sp:send-slack-message", "attributes": {"id": "2c91808978ff6f2201790b33559f1d5a"}, "nextStep": "success", "type": "action", "versionNumber": 1}, "success": {"type": "success"}}}, "enabled": false, "executionCount": 0, "failureCount": 0, "creator": {"type": "IDENTITY", "id": "95a9b731946541a5b95728d054f880ca", "name": "slpt.support"}, "owner": {"type": "IDENTITY", "id": "9ed56b09b0f548ff888d2f73783dbd3b", "name": "adam.creaney"}, "trigger": {"type": "SCHEDULED", "attributes": {"cronString": "0 2 * * *", "dailyTimes": ["02:00"], "frequency": "daily", "timeZone": "America/New_York"}}}
10
+ {"id": "3a71f51d-b8d3-455b-b6d6-f7c08f73d270", "name": "Remove Access When an Identity Becomes Inactive", "description": "Workflow to disable the accounts of identities that move into an \"inactive\" lifecycle state.", "created": "2024-09-09T15:32:57.336980032Z", "modified": "2024-09-09T15:32:57.336980032Z", "modifiedBy": {"type": "IDENTITY", "id": "95a9b731946541a5b95728d054f880ca", "name": "slpt.support"}, "definition": {"start": "Compare Strings", "steps": {"Compare Strings": {"choiceList": [{"comparator": "StringEquals", "nextStep": "Get Identity", "variableA.$": "$.trigger.changes[?(@.attribute == \"cloudLifecycleState\")].newValue", "variableB": "Inactive"}], "defaultStep": "success", "description": "Verifies that the change in cloudLifecycleState is to \"Inactive\".", "type": "choice"}, "Get Accounts": {"actionId": "sp:get-accounts", "attributes": {"getAccountsBy": "specificIdentity", "identity.$": "$.trigger.identity.id"}, "description": "Retrieves the identity's current list of accounts.", "nextStep": "Manage Accounts", "type": "action", "versionNumber": 1}, "Get Identity": {"actionId": "sp:get-identity", "attributes": {"id.$": "$.trigger.identity.id"}, "description": "Retrieves available details about the identity.", "nextStep": "Get Identity 1", "type": "action", "versionNumber": 2}, "Get Identity 1": {"actionId": "sp:get-identity", "attributes": {"id.$": "$.getIdentity.managerRef.id"}, "description": "This node is used to gather information about the user's manager to populate their email in the recipient field.", "nextStep": "Send Email 1", "type": "action", "versionNumber": 2}, "HTTP Request": {"actionId": "sp:http", "attributes": {}, "description": "Initiates a service desk ticket to ensure that physical assets are returned to the organization. This step submits the ticket to your service desk tool and returns the status of that request.\n\nEdit the details of this step to match your ITSM tool and environment.", "nextStep": "Get Accounts", "type": "action", "versionNumber": 2}, "Manage Accounts": {"actionId": "sp:manage-account", "attributes": {"accountIds.$": "$.getAccounts.accounts", "operation": "disable"}, "description": "Disables all accounts returned by the Get Accounts step.", "nextStep": "success 1", "type": "action", "versionNumber": 1}, "Send Email 1": {"actionId": "sp:send-email", "attributes": {"body": "Dear ${manager}<br><p>This email is to notify you that employee <b style=\"\">${displayName}</b> is no longer active.</p><br>Thank you,<br>Your Access Team", "context": {"displayName.$": "$.getIdentity.attributes.displayName", "manager.$": "$.getIdentity1.attributes.firstname"}, "recipientEmailList.$": "$.getIdentity1.attributes.email", "subject": "Employee Leaving"}, "description": "Notifies the users manager that the user is now inactive. This step can also be configured to notify security admins using a distribution list.", "nextStep": "HTTP Request", "type": "action", "versionNumber": 2}, "success": {"description": "Terminates the workflow when the comparison operator indicates the user was changed to any lifecycle state other than \"Inactive.\"", "type": "success"}, "success 1": {"description": "Finishes the workflow in a Success state. Some organizations may want to add a step to create a certification campaign prior to ending the workflow.", "type": "success"}}}, "enabled": false, "executionCount": 0, "failureCount": 0, "creator": {"type": "IDENTITY", "id": "95a9b731946541a5b95728d054f880ca", "name": "slpt.support"}, "owner": {"type": "IDENTITY", "id": "9ed56b09b0f548ff888d2f73783dbd3b", "name": "adam.creaney"}, "trigger": {"type": "EVENT", "attributes": {"filter.$": "$.changes[?(@.attribute == \"cloudLifecycleState\")]", "id": "idn:identity-attributes-changed"}}}
11
+ {"id": "569a4c9f-d75c-41d7-bf3f-c400d2227415", "name": "OnBoarding New Contractor", "description": "", "created": "2024-09-09T15:32:56.747840294Z", "modified": "2024-09-09T15:32:56.747840294Z", "modifiedBy": {"type": "IDENTITY", "id": "95a9b731946541a5b95728d054f880ca", "name": "slpt.support"}, "definition": {"start": "Compare Strings", "steps": {"Compare Strings": {"choiceList": [{"nextStep": "Send Email", "variableA.$": "$.trigger.attributes.userType", "variableB": "Employee"}], "type": "choice"}, "HTTP Request": {"actionId": "sp:http", "attributes": {"url": null}, "type": "action", "versionNumber": 1}, "Send Email": {"actionId": "sp:send-email", "attributes": {"recipientId": null}, "nextStep": "Send Email 1", "type": "action", "versionNumber": 1}, "Send Email 1": {"actionId": "sp:send-email", "attributes": {"body": "Hey, you have a new employee in your team!", "recipientId.$": "$.trigger.attributes.manager.id"}, "nextStep": "HTTP Request", "type": "action", "versionNumber": 1}}}, "enabled": false, "executionCount": 0, "failureCount": 0, "creator": {"type": "IDENTITY", "id": "95a9b731946541a5b95728d054f880ca", "name": "slpt.support"}, "owner": {"type": "IDENTITY", "id": "9ed56b09b0f548ff888d2f73783dbd3b", "name": "adam.creaney"}, "trigger": {"type": "EVENT", "attributes": {"id": "idn:identity-created"}}}
12
+ {"id": "1296ebd2-41e2-4299-a465-f5192738d8e7", "name": "Initiate Onboarding Process When a New Identity is Created", "description": "Workflow to begin the process of onboarding a new identity.", "created": "2024-09-09T15:32:55.922293871Z", "modified": "2024-09-09T15:32:55.922293871Z", "modifiedBy": {"type": "IDENTITY", "id": "95a9b731946541a5b95728d054f880ca", "name": "slpt.support"}, "definition": {"start": "HTTP Request", "steps": {"Compare Strings": {"choiceList": [{"comparator": "StringEquals", "nextStep": "Send Email", "variableA.$": "$.httpRequest.statusCode", "variableB": "200"}], "defaultStep": "Send Email 1", "description": "Determines whether the HTTP Request step was successful.", "type": "choice"}, "Create Certification Campaign": {"actionId": "sp:create-campaign", "attributes": {"description": "New Hire certification - This campaign is to verify access for new hires.", "duration": "1w", "emailNotificationEnabled": true, "identityId.$": "$.trigger.identity.id", "name.$": "$.trigger.identity.name", "reviewerId.$": "$.trigger.attributes.manager.id"}, "description": "Creates a new-hire certification campaign so that the new hire's manager can review their access.", "nextStep": "success", "type": "action", "versionNumber": 1}, "Get Access": {"actionId": "sp:access:get", "attributes": {"accessprofiles": false, "entitlements": true, "getAccessBy": "searchQuery", "query": "name: \"Travel Booking\"", "roles": false}, "description": "Finds entitlements this user needs that are not currently provisioned as part of an access profile or role.\n\nThis example finds entitlements related to \"Travel Booking\". Although these entitlements are not part of an existing role or access profile, the new sales team member will need this access, so the workflow gets those entitlements for use in the Manage Access step.", "nextStep": "Manage Access", "type": "action", "versionNumber": 1}, "Get Identity": {"actionId": "sp:get-identity", "attributes": {"id.$": "$.trigger.attributes.manager.id"}, "description": "This node is used to gather information about the user's manager to populate their email.", "nextStep": "Compare Strings", "type": "action", "versionNumber": 2}, "HTTP Request": {"actionId": "sp:http", "attributes": {"authenticationType": "OAuth"}, "nextStep": "Get Identity", "type": "action", "versionNumber": 2}, "Manage Access": {"actionId": "sp:access:manage", "attributes": {"addIdentities.$": "$.trigger.identity.id", "comments": "Automatically granted by workflows", "removeDuration": "2w", "requestType": "GRANT_ACCESS", "requestedItems.$": "$.getAccess.accessItems"}, "description": "Takes the results from the Get Access step for Travel Booking entitlements and submit it as an access request for the new identity.", "nextStep": "Create Certification Campaign", "type": "action", "versionNumber": 1}, "Send Email": {"actionId": "sp:send-email", "attributes": {"body": "Dear ${manager},<br><p>An ITSM request has been submitted for a new employee named <b style=\"\">${employee}</b> Here are the details of the request:<br>${body}</p><br><p>Thank you,<br> Your Access Team</p>", "context": {"body.$": "$.httpRequest", "employee.$": "$.trigger.identity.name", "manager.$": "$.getIdentity.attributes.firstname"}, "recipientEmailList.$": "$.getIdentity.attributes.email", "replyTo": null, "subject": "New Employee"}, "description": "Sends an email to the new-hire's manager informing them of the service desk ticket creation.", "nextStep": "Get Access", "type": "action", "versionNumber": 2}, "Send Email 1": {"actionId": "sp:send-email", "attributes": {"body": "The creation of the service desk ticket for physical assets failed. Please create this ticket manually and verify all other onboarding tasks for the following new identity:\n\nAttributes:\n- Email: ${email}\n- Name: ${displayName}", "context": {"email.$": "$.trigger.attributes.email", "name.$": "$.trigger.attributes.displayName"}, "recipientEmailList.$": "$.getIdentity.attributes.email", "replyTo.$": "", "subject": "Service Desk Ticket Creation Failed"}, "description": "Notifies the new identity's manager that the HTTP request failed.", "nextStep": "failure 1", "type": "action", "versionNumber": 2}, "failure 1": {"description": "Ends the workflow in a failure state.", "failureDetails": "The automated joiner workflow failed to create a service desk ticket for physical assets.", "failureName": "Failed service desk ticket", "type": "failure"}, "success": {"description": "Ends the workflow after all access has been requested and the certification has been activated.", "type": "success"}}}, "enabled": false, "executionCount": 0, "failureCount": 0, "creator": {"type": "IDENTITY", "id": "95a9b731946541a5b95728d054f880ca", "name": "slpt.support"}, "owner": {"type": "IDENTITY", "id": "9ed56b09b0f548ff888d2f73783dbd3b", "name": "adam.creaney"}, "trigger": {"type": "EVENT", "attributes": {"filter.$": "$[?($.attributes.department == \"sales\")]", "id": "idn:identity-created"}}}
13
+ {"id": "2533cd7a-4c53-4d7c-97d0-314a0c9582d8", "name": "Workflow Mover Campaign", "description": "Workflow when an identity moves within an organization to another team, changing managers, etc.", "created": "2024-09-09T15:32:55.163458041Z", "modified": "2024-09-09T15:32:55.163458041Z", "modifiedBy": {"type": "IDENTITY", "id": "95a9b731946541a5b95728d054f880ca", "name": "slpt.support"}, "definition": {"start": "IsJobTitleChange", "steps": {"ActivateCampaign": {"actionId": "sp:activate-campaign", "attributes": {"id.$": "$.id"}, "description": "Activates a campaign.", "nextStep": "EndState", "type": "action", "versionNumber": 1}, "CreateCampaign": {"actionId": "sp:create-campaign", "attributes": {"description": "This is a test campaign generated by a workflow", "duration": "24h", "emailNotificationEnabled": true, "identityId.$": "$.identity.id", "name": "Workflow Generated Campaign", "recommendationsEnabled": true}, "description": "Creates a campaign.", "nextStep": "IsCampaignGeneratedComplete", "type": "action", "versionNumber": 1}, "EndState": {"description": "This is the final state.", "type": "success"}, "IsCampaignGeneratedComplete": {"choiceList": [{"comparator": "StringEquals", "nextStep": "EndState", "variableA.$": "$.status", "variableB": "COMPLETED"}], "defaultStep": "ActivateCampaign", "description": "Checks to see if the status of the campaign is COMPLETED or STAGED. STAGED will go to activate campaign before ending.", "type": "choice"}, "IsJobTitleChange": {"choiceList": [{"comparator": "StringEquals", "nextStep": "CreateCampaign", "variableA.$": "$.changes[?(@.attribute=='jobTitle')].attribute", "variableB": "jobTitle"}], "defaultStep": "Send Email", "type": "choice"}, "Send Email": {"actionId": "sp:send-email", "attributes": {"body": "The workflow Failed", "recipientId": "2c91808978ff6f2201790b33559f1d5a", "subject": "Failure"}, "description": "Send email", "nextStep": "EndState", "type": "action", "versionNumber": 1}}}, "enabled": false, "executionCount": 0, "failureCount": 0, "creator": {"type": "IDENTITY", "id": "95a9b731946541a5b95728d054f880ca", "name": "slpt.support"}, "owner": {"type": "IDENTITY", "id": "9ed56b09b0f548ff888d2f73783dbd3b", "name": "adam.creaney"}, "trigger": {"type": "EVENT", "attributes": {"id": "idn:identity-attributes-changed"}}}
14
+ {"id": "c629255e-93ee-4f52-801d-aa020c8890f1", "name": "Workflow Mover Campaign Test", "description": "Workflow when identities move between teams - create certification", "created": "2024-09-09T15:32:54.95461501Z", "modified": "2024-09-09T15:32:54.95461501Z", "modifiedBy": {"type": "IDENTITY", "id": "95a9b731946541a5b95728d054f880ca", "name": "slpt.support"}, "definition": {"start": "Compare Strings", "steps": {"Activate Certification Campaign": {"actionId": "sp:activate-campaign", "attributes": {"id.$": "$.createCertificationCampaign.id"}, "description": "Activate the Campaign", "nextStep": "success", "type": "action"}, "Compare Strings": {"choiceList": [{"comparator": "StringEquals", "nextStep": "success", "variableA.$": "$.trigger.changes[?(@.attribute == 'jobTitle')].oldValue", "variableB.$": "$.trigger.changes[?(@.attribute == 'jobTitle')].newValue"}], "defaultStep": "Create Certification Campaign", "type": "choice"}, "Create Certification Campaign": {"actionId": "sp:create-campaign", "attributes": {"description": "Workflows-Generated Campaign for job title changes", "duration": "24h", "identityId.$": "$.trigger.identity.id", "name": "Workflows-Generated Campaign"}, "description": "This will create the campaign", "nextStep": "Activate Certification Campaign", "type": "action"}, "success": {"type": "success"}}}, "enabled": false, "executionCount": 0, "failureCount": 0, "creator": {"type": "IDENTITY", "id": "95a9b731946541a5b95728d054f880ca", "name": "slpt.support"}, "owner": {"type": "IDENTITY", "id": "9ed56b09b0f548ff888d2f73783dbd3b", "name": "adam.creaney"}, "trigger": {"type": "EVENT", "attributes": {"id": "idn:identity-attributes-changed"}}}
15
+ {"id": "f9d24f0e-cc25-49af-a704-4d3287351c74", "name": "Movement", "description": "", "created": "2024-09-09T15:32:54.49261567Z", "modified": "2024-09-09T15:32:54.49261567Z", "modifiedBy": {"type": "IDENTITY", "id": "95a9b731946541a5b95728d054f880ca", "name": "slpt.support"}, "definition": {"start": "Get Accounts", "steps": {"Get Accounts": {"actionId": "sp:get-accounts", "attributes": {}, "nextStep": "Manage Access", "type": "action", "versionNumber": 1}, "Manage Access": {"actionId": "sp:access:manage", "attributes": {"requestType": "REVOKE_ACCESS"}, "nextStep": "Manage ServiceNow Ticket", "type": "action", "versionNumber": 1}, "Manage ServiceNow Ticket": {"actionId": "sp:snow", "attributes": {}, "type": "action", "versionNumber": 1}}}, "enabled": false, "executionCount": 0, "failureCount": 0, "creator": {"type": "IDENTITY", "id": "95a9b731946541a5b95728d054f880ca", "name": "slpt.support"}, "owner": {"type": "IDENTITY", "id": "9ed56b09b0f548ff888d2f73783dbd3b", "name": "adam.creaney"}, "trigger": {"type": "EXTERNAL", "attributes": {}}}
16
+ {"id": "9e3b2d6f-5577-488c-8aa2-5bda909a6e6c", "name": "Leaver Workflow", "description": "Workflow to disable the accounts of identities that move into an \"inactive\" lifecycle state.", "created": "2024-09-09T15:32:53.791670279Z", "modified": "2024-09-09T15:32:53.791670279Z", "modifiedBy": {"type": "IDENTITY", "id": "95a9b731946541a5b95728d054f880ca", "name": "slpt.support"}, "definition": {"start": "Compare Strings", "steps": {"Compare Strings": {"choiceList": [{"comparator": "StringEquals", "nextStep": "Get Identity", "variableA.$": "$.trigger.changes[?(@.attribute == \"cloudLifecycleState\")].newValue", "variableB": "Inactive"}], "defaultStep": "success", "description": "Verifies that the change in cloudLifecycleState is to \"Inactive\".", "type": "choice"}, "Get Accounts": {"actionId": "sp:get-accounts", "attributes": {"getAccountsBy": "specificIdentity", "identity.$": "$.trigger.identity.id"}, "description": "Retrieves the identity's current list of accounts.", "nextStep": "Manage Accounts", "type": "action", "versionNumber": 1}, "Get Identity": {"actionId": "sp:get-identity", "attributes": {"id.$": "$.trigger.identity.id"}, "description": "Retrieves available details about the identity.", "nextStep": "Get Identity 1", "type": "action", "versionNumber": 2}, "Get Identity 1": {"actionId": "sp:get-identity", "attributes": {"id.$": "$.getIdentity.managerRef.id"}, "description": "This node is used to gather information about the user's manager to populate their email in the recipient field.", "nextStep": "Send Email 1", "type": "action", "versionNumber": 2}, "HTTP Request": {"actionId": "sp:http", "attributes": {}, "description": "Initiates a service desk ticket to ensure that physical assets are returned to the organization. This step submits the ticket to your service desk tool and returns the status of that request.\n\nEdit the details of this step to match your ITSM tool and environment.", "nextStep": "Get Accounts", "type": "action", "versionNumber": 2}, "Manage Accounts": {"actionId": "sp:manage-account", "attributes": {"accountIds.$": "$.getAccounts.accounts", "operation": "disable"}, "description": "Disables all accounts returned by the Get Accounts step.", "nextStep": "success 1", "type": "action", "versionNumber": 1}, "Send Email 1": {"actionId": "sp:send-email", "attributes": {"body": "Dear ${manager}<br><p>This email is to notify you that employee <b style=\"\">${displayName}</b> is no longer active.</p><br>Thank you,<br>Your Access Team", "context": {"displayName.$": "$.getIdentity.attributes.displayName", "manager.$": "$.getIdentity1.attributes.firstname"}, "recipientEmailList.$": "$.getIdentity1.attributes.email", "subject": "Employee Leaving"}, "description": "Notifies the users manager that the user is now inactive. This step can also be configured to notify security admins using a distribution list.", "nextStep": "HTTP Request", "type": "action", "versionNumber": 2}, "success": {"description": "Terminates the workflow when the comparison operator indicates the user was changed to any lifecycle state other than \"Inactive.\"", "type": "success"}, "success 1": {"description": "Finishes the workflow in a Success state. Some organizations may want to add a step to create a certification campaign prior to ending the workflow.", "type": "success"}}}, "enabled": false, "executionCount": 0, "failureCount": 0, "creator": {"type": "IDENTITY", "id": "95a9b731946541a5b95728d054f880ca", "name": "slpt.support"}, "owner": {"type": "IDENTITY", "id": "9ed56b09b0f548ff888d2f73783dbd3b", "name": "adam.creaney"}, "trigger": {"type": "EVENT", "attributes": {"filter.$": "$.changes[?(@.attribute == \"cloudLifecycleState\")]", "id": "idn:identity-attributes-changed"}}}
17
+ {"id": "2124d6dd-7fc1-42ec-8451-918f456814e1", "name": "Incomplete Campaign Escalation", "description": "", "created": "2024-09-09T15:32:53.490741235Z", "modified": "2024-09-09T15:32:53.490741235Z", "modifiedBy": {"type": "IDENTITY", "id": "95a9b731946541a5b95728d054f880ca", "name": "slpt.support"}, "definition": {"start": "Compare Strings", "steps": {"Compare Strings": {"choiceList": [{"comparator": "StringEquals", "nextStep": "Create Certification Campaign", "variableA.$": "$.trigger.campaign.status", "variableB": "INCOMPLETE"}], "defaultStep": "failure", "type": "choice"}, "Create Certification Campaign": {"actionId": "sp:create-campaign", "attributes": {"activateUponCreation": true, "description": "Automatically triggered campaign due to uncertified items", "duration": "1w", "emailNotificationEnabled": true, "name": "Escalation for Incomplete Campaign", "recommendationsEnabled": true, "reviewerCertificationType": "IDENTITY", "reviewerId.$": "$.trigger.campaign.campaignOwner.id.manager", "reviewerIdentitiesToCertify.$": "$.trigger.campaign.id", "type": "REVIEWER_IDENTITY", "undecidedAccess": true}, "description": null, "nextStep": "success", "type": "action", "versionNumber": 2}, "failure": {"failureName": "Failure", "type": "failure"}, "success": {"type": "success"}}}, "enabled": false, "executionCount": 0, "failureCount": 0, "creator": {"type": "IDENTITY", "id": "95a9b731946541a5b95728d054f880ca", "name": "slpt.support"}, "owner": {"type": "IDENTITY", "id": "9ed56b09b0f548ff888d2f73783dbd3b", "name": "adam.creaney"}, "trigger": {"type": "EVENT", "attributes": {"id": "idn:campaign-ended"}}}
18
+ {"id": "9fcb441b-52f6-40f8-9b1f-342acf94068e", "name": "Disable Outliers", "description": "A nine-step workflow triggered when an outlier is detected with a score above 0.9. When the score is this high, all accounts for the identity are disabled and an email notification is sent to their manager to review access and investigate the exceptions.", "created": "2024-09-09T15:32:52.53344359Z", "modified": "2024-09-09T15:32:52.53344359Z", "modifiedBy": {"type": "IDENTITY", "id": "95a9b731946541a5b95728d054f880ca", "name": "slpt.support"}, "definition": {"start": "Compare Numbers", "steps": {"Compare Numbers": {"choiceList": [{"comparator": "NumericGreaterThanEquals", "nextStep": "Get Identity", "variableA.$": "$.trigger.score", "variableB": 0.9}], "defaultStep": "success", "description": "The workflow triggers at or above 0.9. This comparator checks the value to ensure it is at or above 0.9.", "type": "choice"}, "Get Access": {"actionId": "sp:access:get", "attributes": {"accessprofiles": true, "entitlements": true, "getAccessBy": "specificIdentity", "identityToReturn.$": "$.trigger.identity.id", "roles": true}, "description": "This action returns all access associated with the outlier identity.", "nextStep": "Get Identity 1", "type": "action", "versionNumber": 1}, "Get Accounts": {"actionId": "sp:get-accounts", "attributes": {"getAccountsBy": "specificIdentity", "identity.$": "$.trigger.identity.id"}, "description": "This action is used to gather all the accounts associated with the identity. In this workflow, the accounts will be disabled until a review can be conducted.", "nextStep": "Send Email", "type": "action", "versionNumber": 1}, "Get Identity": {"actionId": "sp:get-identity", "attributes": {"id.$": "$.trigger.identity.id"}, "description": "This action returns attributes associated with the identity that triggered the outlier score.", "nextStep": "Get Access", "type": "action", "versionNumber": 2}, "Get Identity 1": {"actionId": "sp:get-identity", "attributes": {"id.$": "$.getIdentity.managerRef.id"}, "description": "This action returns the outlier identity's manager's name and attributes.", "nextStep": "Get Accounts", "type": "action", "versionNumber": 2}, "Manage Accounts": {"actionId": "sp:manage-account", "attributes": {"accountIds.$": "$.getAccounts.accounts", "operation": "disable"}, "description": "This action disables all account found in the prior action \"Get Accounts\".", "nextStep": "success 1", "type": "action", "versionNumber": 1}, "Send Email": {"actionId": "sp:send-email", "attributes": {"body": "Dear ${manager},<br><p> The SailPoint AI Engine has detected a user that has outlier access resulting in excessive risk. This identity, <b style=\"\"><u style=\"\">${user}</b> </u>, is your direct report and has an outlier score of <b style=\"\"><font color=\"#993300\">${score}</b></font>.<br><br>Please note that <font color=\"#993300\"><b style=\"\"><i style=\"\">all accounts</font></b></i> for <b style=\"\">${user}</b> have been disabled until a full forensic audit can be completed. A complete listing of all access can be found below.<br></p><br>Thank you,<br>Your Security & Compliance Team.<br><p><b style=\"\"><i style=\"\">Access:<br>${access}</b></i></p>", "context": {"access.$": "$.getAccess.accessItems", "manager.$": "$.getIdentity1.attributes.displayName", "score.$": "$.trigger.score", "user.$": "$.getIdentity.attributes.displayName"}, "recipientEmailList.$": "$.getIdentity1.attributes.email", "subject": "Outlier - Excessive Risk notification"}, "description": "This action sends an email notification to the outlier identity's manager. The email informs the manager that the identity had an outlier score that was deemed \"Excessive Risk\" and action has been taken to disable all accounts until a full security review can be completed.", "nextStep": "Manage Accounts", "type": "action", "versionNumber": 2}, "success": {"description": "The outlier score was not at or above 0.9. No further action is required.", "type": "success"}, "success 1": {"description": "This workflow has completed successfully.", "type": "success"}}}, "enabled": false, "executionCount": 0, "failureCount": 0, "creator": {"type": "IDENTITY", "id": "95a9b731946541a5b95728d054f880ca", "name": "slpt.support"}, "owner": {"type": "IDENTITY", "id": "9ed56b09b0f548ff888d2f73783dbd3b", "name": "adam.creaney"}, "trigger": {"type": "EVENT", "attributes": {"filter.$": "$[?(@.score >= 0.9)]", "id": "iai:outlier-detected"}}}
19
+ {"id": "0a3fe427-61ef-4aca-b962-cab42d1876ab", "name": "Disable Accounts for Detected Outlier Identities (0.9+)a", "description": "A nine-step workflow to trigger when a detected outlier score is at or above 0.9. When the score is this high, all accounts for the identity are disabled and an email notification is sent to their manager to review access and investigate the exceptions.", "created": "2024-09-09T15:32:51.816659986Z", "modified": "2024-09-09T15:32:51.816659986Z", "modifiedBy": {"type": "IDENTITY", "id": "95a9b731946541a5b95728d054f880ca", "name": "slpt.support"}, "definition": {"start": "Compare Numbers", "steps": {"Compare Numbers": {"choiceList": [{"comparator": "NumericGreaterThanEquals", "nextStep": "Get Identity", "variableA.$": "$.trigger.score", "variableB": 0.9}], "defaultStep": "success", "description": "The workflow triggers at or above 0.9. This comparator checks the value to ensure it is at or above 0.9.", "type": "choice"}, "Get Access": {"actionId": "sp:access:get", "attributes": {"accessprofiles": true, "entitlements": true, "getAccessBy": "specificIdentity", "identityToReturn.$": "$.trigger.identity.id", "roles": true}, "description": "This action returns all access associated with the outlier identity.", "nextStep": "Get Identity 1", "type": "action", "versionNumber": 1}, "Get Accounts": {"actionId": "sp:get-accounts", "attributes": {"getAccountsBy": "specificIdentity", "identity.$": "$.trigger.identity.id"}, "description": "This action is used to gather all the accounts associated with the identity. In this workflow, the accounts will be disabled until a review can be conducted.", "nextStep": "Send Email", "type": "action", "versionNumber": 1}, "Get Identity": {"actionId": "sp:get-identity", "attributes": {"id.$": "$.trigger.identity.id"}, "description": "This action returns attributes associated with the identity that triggered the outlier score.", "nextStep": "Get Access", "type": "action", "versionNumber": 2}, "Get Identity 1": {"actionId": "sp:get-identity", "attributes": {"id.$": "$.getIdentity.managerRef.id"}, "description": "This action returns the outlier identity's manager's name and attributes.", "nextStep": "Get Accounts", "type": "action", "versionNumber": 2}, "Manage Accounts": {"actionId": "sp:manage-account", "attributes": {"accountIds.$": "$.getAccounts.accounts", "operation": "disable"}, "description": "This action disables all account found in the prior action \"Get Accounts\".", "nextStep": "success 1", "type": "action", "versionNumber": 1}, "Send Email": {"actionId": "sp:send-email", "attributes": {"body": "Dear ${manager},<br><p> The SailPoint AI Engine has detected a user that has outlier access resulting in excessive risk. This identity, <b style=\"\"><u style=\"\">${user}</b> </u>, is your direct report and has an outlier score of <b style=\"\"><font color=\"#993300\">${score}</b></font>.<br><br>Please note that <font color=\"#993300\"><b style=\"\"><i style=\"\">all accounts</font></b></i> for <b style=\"\">${user}</b> have been disabled until a full forensic audit can be completed. A complete listing of all access can be found below.<br></p><br>Thank you,<br>Your Security & Compliance Team.<br><p><b style=\"\"><i style=\"\">Access:<br>${access}</b></i></p>", "context": {"access.$": "$.getAccess.accessItems", "manager.$": "$.getIdentity1.attributes.displayName", "score.$": "$.trigger.score", "user.$": "$.getIdentity.attributes.displayName"}, "recipientEmailList.$": "$.getIdentity1.attributes.email", "subject": "Outlier - Excessive Risk notification"}, "description": "This action sends an email notification to the outlier identity's manager. The email informs the manager that the identity had an outlier score that was deemed \"Excessive Risk\" and action has been taken to disable all accounts until a full security review can be completed.", "nextStep": "Manage Accounts", "type": "action", "versionNumber": 2}, "success": {"description": "The outlier score was not at or above 0.9. No further action is required.", "type": "success"}, "success 1": {"description": "This workflow has completed successfully.", "type": "success"}}}, "enabled": false, "executionCount": 0, "failureCount": 0, "creator": {"type": "IDENTITY", "id": "95a9b731946541a5b95728d054f880ca", "name": "slpt.support"}, "owner": {"type": "IDENTITY", "id": "9ed56b09b0f548ff888d2f73783dbd3b", "name": "adam.creaney"}, "trigger": {"type": "EVENT", "attributes": {"filter.$": "$[?(@.score >= 0.9)]", "id": "iai:outlier-detected"}}}
20
+ {"id": "320fb2fa-6343-4a5d-8e18-9ef97331d9ba", "name": "Template: Create and Activate a Certification Campaign", "description": "Create and activate a certification campaign for every identity that has changed departments.", "created": "2024-09-09T15:32:51.162553615Z", "modified": "2024-09-09T15:32:51.162553615Z", "modifiedBy": {"type": "IDENTITY", "id": "95a9b731946541a5b95728d054f880ca", "name": "slpt.support"}, "definition": {"start": "Compare Strings", "steps": {"Compare Strings": {"choiceList": [{"comparator": "StringMatches", "nextStep": "Create Certification Campaign", "variableA.$": "$.trigger.changes[?(@.attribute == \"department\")].attribute", "variableB": "department"}], "defaultStep": "success", "description": "Only create the campaign for identities who change departments. This doesn't look for a specific department.", "type": "choice"}, "Create Certification Campaign": {"actionId": "sp:create-campaign", "attributes": {"duration": "2w", "identityId.$": "$.trigger.identity.id", "name.$": "$.trigger.identity.name"}, "description": "Create an identity certification campaign any identity that changed departments. Name it after the identity so we know who is being certified.", "nextStep": "Get Identity", "type": "action", "versionNumber": 1}, "Get Identity": {"actionId": "sp:get-identity", "attributes": {"id.$": "$.trigger.identity.id"}, "description": "Get the identity's attributes so we can look up their manager.", "nextStep": "Get Identity 2", "type": "action", "versionNumber": 2}, "Get Identity 2": {"actionId": "sp:get-identity", "attributes": {"id.$": "$.getIdentity.managerRef.id"}, "description": "This node is used to gather information about the user's manager to populate their email.", "nextStep": "Send Email 1", "type": "action", "versionNumber": 2}, "Send Email 1": {"actionId": "sp:send-email", "attributes": {"body": "Dear ${recipName}, <br><p>This notification is to inform you that <b style=\"\">${displayName}</b> has recently changed departments. </p> <br><p>A Certification campaign named <b style=\"\">${campaignName} </b>has been generated for your review. </p> <br><p> Thank you <br>Security & Compliance</p>", "context": {"campaignName.$": "$.createCertificationCampaign.attributes.name", "displayName.$": "$.getIdentity.attributes.displayName", "recipName.$": "$.getIdentity2.attributes.displayName"}, "recipientEmailList.$": "$.getIdentity2.attributes.email", "replyTo": null, "subject": "Direct Report department change"}, "description": "Send an email to the identity's manager so they know there is a certification campaign pending for their employee that they need to complete.", "nextStep": "success 1", "type": "action", "versionNumber": 2}, "success": {"description": "Don't do anything else, since the identity didn't change departments.", "type": "success"}, "success 1": {"type": "success"}}}, "enabled": false, "executionCount": 0, "failureCount": 0, "creator": {"type": "IDENTITY", "id": "95a9b731946541a5b95728d054f880ca", "name": "slpt.support"}, "owner": {"type": "IDENTITY", "id": "9ed56b09b0f548ff888d2f73783dbd3b", "name": "adam.creaney"}, "trigger": {"type": "EVENT", "attributes": {"filter.$": "$.changes[?(@.attribute == \"department\")]", "id": "idn:identity-attributes-changed"}}}
21
+ {"id": "8c2900a9-19d6-4c2d-8a47-2c78b8b6373a", "name": "Template: Create and Activate a Certification Campaign time bound", "description": "Create and activate a certification campaign for every identity that has changed departments.", "created": "2024-09-09T15:32:50.538299618Z", "modified": "2024-09-09T15:32:50.538299618Z", "modifiedBy": {"type": "IDENTITY", "id": "95a9b731946541a5b95728d054f880ca", "name": "slpt.support"}, "definition": {"start": "Compare Strings", "steps": {"Activate Certification Campaign": {"actionId": "sp:activate-campaign", "attributes": {"id.$": "$.createCertificationCampaign.id"}, "description": "Activate the campaign we created.", "nextStep": "Get Identity", "type": "action", "versionNumber": 1}, "Compare Strings": {"choiceList": [{"comparator": "StringMatches", "nextStep": "Create Certification Campaign", "variableA.$": "$.trigger.changes[?(@.attribute == \"department\")].attribute", "variableB": "department"}], "defaultStep": "success", "description": "Only create the campaign for identities who change departments. This doesn't look for a specific department.", "type": "choice"}, "Create Certification Campaign": {"actionId": "sp:create-campaign", "attributes": {"duration": "2w", "identityId.$": "$.trigger.identity.id", "name.$": "$.trigger.identity.name"}, "description": "Create an identity certification campaign any identity that changed departments. Name it after the identity so we know who is being certified.", "nextStep": "Activate Certification Campaign", "type": "action", "versionNumber": 1}, "Get Identity": {"actionId": "sp:get-identity", "attributes": {"id.$": "$.trigger.identity.id"}, "description": "Get the identity's attributes so we can look up their manager.", "nextStep": "Get Identity 1", "type": "action", "versionNumber": 1}, "Get Identity 1": {"actionId": "sp:get-identity", "attributes": {"id.$": "$.getIdentity.manager.id"}, "description": "This node is used to gather information about the user's manager to populate their email.", "nextStep": "Send Email 1", "type": "action", "versionNumber": 1}, "Send Email 1": {"actionId": "sp:send-email", "attributes": {"body.$": "$.trigger.identity.name", "context": {}, "recipientEmailList.$": "$.getIdentity1.attributes.email", "subject": "New certification campaign generated for your employee"}, "description": "Send an email to the identity's manager so they know there is a certification campaign pending for their employee that they need to complete.", "nextStep": "success 1", "type": "action", "versionNumber": 2}, "success": {"description": "Don't do anything else, since the identity didn't change departments.", "type": "success"}, "success 1": {"type": "success"}}}, "enabled": false, "executionCount": 0, "failureCount": 0, "creator": {"type": "IDENTITY", "id": "95a9b731946541a5b95728d054f880ca", "name": "slpt.support"}, "owner": {"type": "IDENTITY", "id": "9ed56b09b0f548ff888d2f73783dbd3b", "name": "adam.creaney"}, "trigger": {"type": "EVENT", "attributes": {"id": "idn:identity-attributes-changed"}}}
22
+ {"id": "73fbba11-0d85-4e69-8b77-a675a357edde", "name": "Campaign reminder v2", "description": "This campaign reminder will send a notification to outstanding reviewers every day at 6am. This covers up to the first 10 reviewers. This workflow can be duplicated to support as many reviewers as needed.", "created": "2024-09-09T15:32:50.133462163Z", "modified": "2024-09-09T15:32:50.133462163Z", "modifiedBy": {"type": "IDENTITY", "id": "95a9b731946541a5b95728d054f880ca", "name": "slpt.support"}, "definition": {"start": "HTTP Request", "steps": {"HTTP Request": {"actionId": "sp:http", "attributes": {"authenticationType": "OAuth", "method": "get", "oAuthClientId": "f4a69d2d1f874bce8aabf72c84644614", "oAuthClientSecret": "", "oAuthTokenUrl": "https://company1072-poc.api.identitynow-demo.com/oauth/token", "requestContentType": "json", "url": "https://company1072-poc.api.identitynow-demo.com/v3/certifications", "urlParams": "filters:phase eq \"ACTIVE\" and completed eq false"}, "description": "Get current certification reviewers for any certifications that are still active.", "nextStep": "Send Email", "type": "action", "versionNumber": 2}, "Send Email": {"actionId": "sp:send-email", "attributes": {"body": "Please log into SailPoint IdentityNow and review your pending certification decisions.", "context": {}, "from": "", "recipientEmailList.$": "$.hTTPRequest.body[1,2,3,4,5,6,7,8,9].reviewer.email", "replyTo": "no-reply@sailpoint.com", "subject": "Campaign decision reminder"}, "description": "Send Notification email to handle certification decisions that are outstanding. Supports sending to 10 people.", "nextStep": "success", "type": "action", "versionNumber": 2}, "success": {"type": "success"}}}, "enabled": false, "executionCount": 0, "failureCount": 0, "creator": {"type": "IDENTITY", "id": "95a9b731946541a5b95728d054f880ca", "name": "slpt.support"}, "owner": {"type": "IDENTITY", "id": "9ed56b09b0f548ff888d2f73783dbd3b", "name": "adam.creaney"}, "trigger": {"type": "SCHEDULED", "attributes": {"cronString": "0 * * * *", "frequency": "cronSchedule", "timeZone": "US/Eastern"}}}
23
+ {"id": "2c0b484f-a33e-4faf-8694-2c24cd5f490c", "name": "Bills Update and certify mover scenario", "description": "Workflow to update a user's access during a mover scenario, such as when a user changes departments or teams.", "created": "2024-09-09T15:32:48.918613102Z", "modified": "2024-09-09T15:32:48.918613102Z", "modifiedBy": {"type": "IDENTITY", "id": "95a9b731946541a5b95728d054f880ca", "name": "slpt.support"}, "definition": {"start": "Wait", "steps": {"Compare Strings": {"choiceList": [{"comparator": "StringEquals", "nextStep": "Get Identity", "variableA.$": "$.trigger.changes[?(@.attribute=='department')].newValue", "variableB": "Sales"}], "defaultStep": "success", "description": "Determines whether the identity's department attribute was updated to Sales. If so, the workflow proceeds. Otherwise, the workflow ends.\n\nNOTE: This template makes an assumption in later actions/nodes about the identity's former department. You could use an additional operator with branching steps for different departments for more flexibility.", "type": "choice"}, "Create Certification Campaign": {"actionId": "sp:create-campaign", "attributes": {"description": "Department Change", "duration": "1w", "emailNotificationEnabled": true, "identityId.$": "$.trigger.identity.id", "name.$": "$.trigger.identity.name", "reviewerId.$": "$.getIdentity.managerRef.id"}, "description": "Creates a certification campaign so that the identity's new manager can review their access, to ensure they have the access they need and don't have access they don't need.", "nextStep": "success", "type": "action", "versionNumber": 1}, "Get Access": {"actionId": "sp:access:get", "attributes": {"accessprofiles": false, "entitlements": false, "getAccessBy": "searchQuery", "query": "name: \"Product Role\"", "roles": true}, "description": "Gets all roles with \"Product Role\" in their name.\n\nIn this example, the identity needs to retain this access despite their department move. If the access gets removed because they no longer meet the role criteria, it must be put back by this workflow. This step retrieves the access for the Manage Access step to add.", "nextStep": "Manage Access", "type": "action", "versionNumber": 1}, "Get Identity": {"actionId": "sp:get-identity", "attributes": {"id.$": "$.trigger.identity.id"}, "description": "Retrieves available details about the identity.", "nextStep": "Get Access", "type": "action", "versionNumber": 2}, "Get Identity 1": {"actionId": "sp:get-identity", "attributes": {"id.$": "$.getIdentity.managerRef.id"}, "description": "This node is used to gather information about the user's manager to populate their email in \"Recipient\"", "nextStep": "Send Email", "type": "action", "versionNumber": 2}, "Manage Access": {"actionId": "sp:access:manage", "attributes": {"addIdentities.$": "$.trigger.identity.id", "comments": "Automatically requested as transitory access by workflows", "removeDuration": "2w", "requestType": "GRANT_ACCESS", "requestedItems.$": "$.getAccess.accessItems"}, "description": "Adds the access from the Get Access step to the identity.", "nextStep": "Get Identity 1", "type": "action", "versionNumber": 1}, "Send Email": {"actionId": "sp:send-email", "attributes": {"body": "Attention, <br><p>User <b style=\"\">${name} </b>has recently changed departments </p><br>Thank you,<br>Your Access Team", "context": {"name.$": "$.getIdentity.attributes.displayName"}, "recipientEmailList.$": "$.getIdentity1.attributes.email", "recipientId.$": "$.getIdentity.managerRef.id", "subject": "Department Change"}, "description": "Sends an email to the identity's manager. Configure details about this email below.", "nextStep": "Create Certification Campaign", "type": "action", "versionNumber": 2}, "Wait": {"actionId": "sp:sleep", "attributes": {"duration": "1h", "type": "waitFor"}, "description": "Pauses to wait for all other provisioning activities to complete, such as automated role or access profile removals.", "nextStep": "Compare Strings", "type": "action", "versionNumber": 1}, "success": {"description": "Ends the workflow and marks it as a success.", "type": "success"}}}, "enabled": false, "executionCount": 0, "failureCount": 0, "creator": {"type": "IDENTITY", "id": "95a9b731946541a5b95728d054f880ca", "name": "slpt.support"}, "owner": {"type": "IDENTITY", "id": "9ed56b09b0f548ff888d2f73783dbd3b", "name": "adam.creaney"}, "trigger": {"type": "EVENT", "attributes": {"filter.$": "$.changes[?(@.attribute == \"department\")]", "id": "idn:identity-attributes-changed"}}}
utils.py ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import io
3
+ import zipfile
4
+ import json
5
+ import datetime
6
+
7
+ def zip_session_folder(folder_path):
8
+ """Create a ZIP file from a session folder"""
9
+ zip_buffer = io.BytesIO()
10
+ with zipfile.ZipFile(zip_buffer, "w", zipfile.ZIP_DEFLATED) as zf:
11
+ for root, dirs, files in os.walk(folder_path):
12
+ for file in files:
13
+ full_path = os.path.join(root, file)
14
+ arcname = os.path.relpath(full_path, folder_path)
15
+ zf.write(full_path, arcname)
16
+ zip_buffer.seek(0)
17
+ return zip_buffer
18
+
19
+ def handle_path_parameters(endpoint, api_base_url, param_values):
20
+ """Process path parameters for any endpoint"""
21
+ params = extract_path_params(endpoint)
22
+ if not params:
23
+ return api_base_url.rstrip("/") + endpoint, None
24
+
25
+ # Validate parameters
26
+ missing_params = [p for p in params if p not in param_values or not param_values[p]]
27
+ if missing_params:
28
+ return None, f"Error: Missing required parameters: {', '.join(missing_params)}"
29
+
30
+ # Replace parameters in URL
31
+ full_url = api_base_url.rstrip("/") + endpoint
32
+ for param in params:
33
+ full_url = full_url.replace(f"{{{param}}}", param_values[param])
34
+
35
+ return full_url, None
36
+
37
+ def extract_path_params(endpoint):
38
+ """Extract path parameters from an endpoint URL"""
39
+ params = []
40
+ parts = endpoint.split('/')
41
+ for part in parts:
42
+ if part.startswith('{') and part.endswith('}'):
43
+ param_name = part[1:-1]
44
+ params.append(param_name)
45
+ return params
46
+
47
+ def extract_query_params(endpoint_spec):
48
+ """Extract query parameters from endpoint specification"""
49
+ query_params = []
50
+ parameters = endpoint_spec.get('parameters', [])
51
+ for param in parameters:
52
+ if param.get('in') == 'query':
53
+ name = param.get('name')
54
+ required = param.get('required', False)
55
+ description = param.get('description', 'No description')
56
+ query_params.append(('query', name, required, description))
57
+ return query_params
58
+
59
+ def save_response_data(data, endpoint, base_save_folder):
60
+ """Save API response data to file"""
61
+ safe_endpoint_name = endpoint.strip("/").replace("/", "_") or "root"
62
+ timestamp = datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
63
+ save_folder = os.path.join(base_save_folder, f"{safe_endpoint_name} ({timestamp})")
64
+ os.makedirs(save_folder, exist_ok=True)
65
+
66
+ filename = os.path.join(save_folder, "data.jsonl")
67
+ with open(filename, "w", encoding="utf-8") as f:
68
+ if isinstance(data, list):
69
+ for item in data:
70
+ f.write(json.dumps(item) + "\n")
71
+ elif isinstance(data, dict):
72
+ f.write(json.dumps(data) + "\n")
73
+ else:
74
+ f.write(str(data))
uv.lock ADDED
The diff for this file is too large to render. See raw diff