Spaces:
Sleeping
Sleeping
Commit ·
12bee28
1
Parent(s): 07951b7
Add all project files
Browse files- .env +3 -0
- .gitignore +10 -0
- .gradio/certificate.pem +31 -0
- .python-version +1 -0
- README.md +0 -12
- app.py +469 -0
- identityNow.py +141 -0
- iiq.py +110 -0
- okta.py +109 -0
- pyproject.toml +13 -0
- session_28e04043-15a7-4e6b-8dd9-eeddbee5428b.zip +3 -0
- sessions/28e04043-15a7-4e6b-8dd9-eeddbee5428b/IdentityNow/sod-policies (2025-02-19_10-32-26)/data.jsonl +8 -0
- sessions/28e04043-15a7-4e6b-8dd9-eeddbee5428b/IdentityNow/transforms (2025-02-19_10-32-27)/data.jsonl +14 -0
- sessions/28e04043-15a7-4e6b-8dd9-eeddbee5428b/IdentityNow/workflow-library (2025-02-19_10-32-32)/data.jsonl +0 -0
- sessions/28e04043-15a7-4e6b-8dd9-eeddbee5428b/IdentityNow/workflows (2025-02-19_10-32-29)/data.jsonl +23 -0
- utils.py +74 -0
- uv.lock +0 -0
.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> </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> </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
|
|
|