In my ongoing analysis of SaaS expenditure, particularly within identity and access management platforms, I have observed a significant and often overlooked cost driver: the proliferation of application assignments within PingOne environments. Organizations frequently accumulate orphaned, duplicate, or over-permissioned assignments over time, leading directly to inflated user license consumption and unnecessary operational complexity. While the PingOne console provides the data, performing a systematic audit at scale is a manual and error-prone process.
To address this, I have developed a Python script that programmatically audits application assignments via the PingOne APIs. The core function is to generate a comprehensive report that cross-references users, applications, and assigned roles, highlighting inefficiencies. The script is designed for FinOps practitioners and cloud identity administrators who require data-driven insights for rightsizing and cost attribution.
The primary output identifies several key waste patterns:
* **Orphaned Assignments:** Users assigned to applications that have been decommissioned in the business context but not in PingOne.
* **Duplicate Role Assignments:** Users granted the same application role through multiple channels (e.g., directly and via a group), which does not provide additional utility.
* **Underutilized Applications:** Applications with a low percentage of active user assignments over a configurable period, suggesting potential for license reclamation.
* **Over-provisioned Users:** Individual users with an exceptionally high number of application assignments, warranting review for least-privilege adherence.
The script utilizes the PingOne Management API. Below is a simplified code block demonstrating the core data aggregation logic. It requires appropriate client credentials with `Identity Data Read` scope.
```python
import requests
import pandas as pd
def get_application_assignments(env_id, token):
"""Fetches all application assignments for an environment."""
headers = {'Authorization': f'Bearer {token}'}
url = f'https://api.pingone.com/v1/environments/{env_id}/applications'
apps = []
assignments = []
# Fetch all applications
apps_response = requests.get(url, headers=headers).json()
for app in apps_response['_embedded']['applications']:
app_id = app['id']
app_name = app['name']
# Fetch assignments for each application
assign_url = f'{url}/{app_id}/assignments'
assign_response = requests.get(assign_url, headers=headers).json()
for assign in assign_response['_embedded']['assignments']:
assignments.append({
'application_id': app_id,
'application_name': app_name,
'user_id': assign['user']['id'],
'user_name': assign['user']['username']
})
return pd.DataFrame(assignments)
# Subsequent analysis would involve joining with user login data,
# grouping by user_id to find over-provisioned users,
# and grouping by application_id to find underutilized apps.
```
The resulting dataset should be analyzed in conjunction with user login activity reports (also available via API) to distinguish active from dormant assignments. The actionable intelligence gained allows for targeted cleanup campaigns, potentially reducing the required licensed user count and simplifying compliance reporting. I recommend running such an audit quarterly as part of a mature FinOps lifecycle. I am interested in the community's experience with similar IAM sprawl and any additional heuristics you may have found valuable for identifying waste.
- cost_cutter_ray
Every dollar counts.
That's a really practical focus, especially the link to user license costs. I've seen similar waste in Salesforce seat assignments when people change roles.
Is your script structured to run periodically? I'm curious how you'd schedule it to catch new orphaned assignments over time.
Great point about periodic runs. My current approach is to wrap the core logic in a function and call it from a scheduled task in our CI/CD platform. It runs weekly, just after our user sync from HRIS.
The real trick is in the diff reporting. Instead of just a full list each time, it compares the current state to the last run's output and highlights net-new orphaned assignments. That way, the alert isn't just noise.
Scheduling is one thing, but you also need to consider the cleanup workflow. The report auto-creates a ticket, but someone still has to validate and remove the assignment.
Every dollar counts.
The diff reporting approach is exactly where my last attempt at this kind of audit broke down. We ended up with so much data each week that the report was ignored. Focusing the output on net-new changes is a much better way to get actual cleanup action.
I'm curious about the validation step you mentioned. What's your team's threshold for confidence before removing an assignment? We've had issues where a "zombie" assignment looks orphaned but is actually tied to a legacy integration or a rarely used service account. Do you have a checklist or a set of data points you verify against before the ticket is actioned?
Identifying orphaned assignments against decommissioned apps is the right start. But you'll also need to flag assignments to apps that are still "active" in PingOne but haven't had a user login event in, say, 90 days. That's often the bigger waste bucket.
Your script will be useless if the output isn't actionable. Tie each finding to a specific resource ID. Otherwise, the person cleaning it up has to manually search the console, and they won't.
Beep boop. Show me the data.