identity access management

Performing Access Recertification with Saviynt

Configure and execute access recertification campaigns in Saviynt Enterprise Identity Cloud to validate user entitlements, revoke excessive access, and maintain compliance with SOX, SOC2, and HIPAA.

access-recertificationcertification-campaigncomplianceidentity-governanceigasaviynt
Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-Skills
Framework mappings

Overview

Access recertification (also called access certification or access review) is a periodic process where designated reviewers validate that users have appropriate access to systems and data. Saviynt Enterprise Identity Cloud (EIC) automates this process through certification campaigns that present reviewers with current access assignments and collect approve/revoke/conditionally-certify decisions. Campaigns can be triggered on schedule (quarterly, semi-annually), event-driven (department transfer, role change), or on-demand. Saviynt provides intelligence features including risk scoring, usage analytics, and peer-group analysis to help reviewers make informed decisions.

When to Use

  • When conducting security assessments that involve performing access recertification with saviynt
  • When following incident response procedures for related security events
  • When performing scheduled security testing or auditing activities
  • When validating security controls through hands-on testing

Prerequisites

  • Saviynt Enterprise Identity Cloud (EIC) tenant with admin access
  • Identity data synchronized from authoritative sources (HR, AD, cloud)
  • Entitlement data imported from target applications
  • Certifier roles assigned (managers, application owners, data owners)
  • Campaign templates defined for each certification type

Core Concepts

Campaign Types

Type Scope Trigger Certifier
User Manager All access for users under a manager Scheduled (quarterly) Direct manager
Entitlement Owner All users with a specific entitlement Scheduled (semi-annually) Entitlement/app owner
Application All access to a specific application Scheduled Application owner
Role-Based All users assigned to a specific role Scheduled Role owner
Event-Based Users whose attributes changed Attribute change trigger New manager
Micro-Certification Single user, single entitlement On-demand Manager or owner

Certification Decisions

Decision Effect Use Case
Certify (Approve) Access maintained Access is still required
Revoke Access removal ticket created Access no longer needed
Conditionally Certify Access maintained with conditions Access needed temporarily, review again
Delegate Reassign to another certifier Certifier lacks knowledge to decide
Abstain No decision recorded Conflict of interest

Campaign Lifecycle

CONFIGURATION → PREVIEW → ACTIVE → IN PROGRESS → COMPLETED → REMEDIATION
       │            │         │          │             │            │
       │            │         │          │             │            └── Revoke tickets
       │            │         │          │             │                executed
       │            │         │          │             │
       │            │         │          │             └── All decisions
       │            │         │          │                 collected
       │            │         │          │
       │            │         │          └── Certifiers reviewing
       │            │         │              and making decisions
       │            │         │
       │            │         └── Campaign launched,
       │            │             notifications sent
       │            │
       │            └── Read-only preview for validation

       └── Campaign parameters defined

Workflow

Step 1: Configure Campaign Template

In Saviynt Admin Console:

  1. Navigate to Certifications > Campaign > Create New Campaign
  2. Define campaign parameters:
Parameter Value
Campaign Name Q1 2025 Manager Access Review
Campaign Type User Manager
Description Quarterly review of all user access
Certifier Type Manager (dynamic - user's direct manager)
Secondary Certifier Application Owner (fallback if manager unavailable)
Due Date 14 days from launch
Reminder Schedule Day 7, Day 10, Day 13
Escalation Auto-revoke on Day 15 if no decision
  1. Configure scope filters:

    • Include: All active users
    • Exclude: Service accounts, break-glass accounts
    • Application filter: All connected applications
  2. Configure intelligence features:

    • Enable risk scoring (high-risk entitlements highlighted)
    • Enable usage data (last access date shown)
    • Enable peer analysis (compare access to peer group)
    • Enable SoD violation flagging

Step 2: Configure Certifier Experience

Customize what certifiers see during the review:

Columns Displayed:

  • User name and title
  • Application name
  • Entitlement/role name
  • Risk score (1-10)
  • Last access date
  • Peer group comparison (% of peers with same access)
  • SoD violation flag

Decision Options:

  • Certify with justification (free text)
  • Revoke with reason (dropdown: no longer needed, SoD conflict, role change)
  • Conditionally certify with expiry date

Bulk Actions:

  • Certify all low-risk items
  • Revoke all items not accessed in 90+ days
  • Filter by application, risk level, or SoD status

Step 3: Launch Campaign via API

import requests
 
SAVIYNT_URL = "https://tenant.saviyntcloud.com"
SAVIYNT_TOKEN = "your-api-token"
 
def create_certification_campaign(campaign_config):
    """Create and launch a Saviynt certification campaign."""
    headers = {
        "Authorization": f"Bearer {SAVIYNT_TOKEN}",
        "Content-Type": "application/json"
    }
 
    # Create campaign
    response = requests.post(
        f"{SAVIYNT_URL}/ECM/api/v5/createCampaign",
        headers=headers,
        json={
            "campaignname": campaign_config["name"],
            "campaigntype": campaign_config["type"],
            "description": campaign_config["description"],
            "certifier": campaign_config["certifier_type"],
            "duedate": campaign_config["due_date"],
            "reminderdays": campaign_config["reminder_days"],
            "autorevoke": campaign_config.get("auto_revoke", True),
            "autorevokedays": campaign_config.get("auto_revoke_days", 15),
            "scope": campaign_config.get("scope", {}),
        }
    )
    response.raise_for_status()
    campaign_id = response.json().get("campaignId")
 
    # Launch campaign
    launch_response = requests.post(
        f"{SAVIYNT_URL}/ECM/api/v5/launchCampaign",
        headers=headers,
        json={"campaignId": campaign_id}
    )
    launch_response.raise_for_status()
 
    return {
        "campaign_id": campaign_id,
        "status": "launched",
        "certifications_created": launch_response.json().get("certificationCount", 0)
    }
 
def get_campaign_status(campaign_id):
    """Get current status and progress of a campaign."""
    headers = {"Authorization": f"Bearer {SAVIYNT_TOKEN}"}
    response = requests.get(
        f"{SAVIYNT_URL}/ECM/api/v5/getCampaignDetails",
        headers=headers,
        params={"campaignId": campaign_id}
    )
    response.raise_for_status()
    data = response.json()
 
    return {
        "campaign_id": campaign_id,
        "status": data.get("status"),
        "total_items": data.get("totalLineItems", 0),
        "certified": data.get("certifiedCount", 0),
        "revoked": data.get("revokedCount", 0),
        "pending": data.get("pendingCount", 0),
        "completion_rate": data.get("completionPercentage", 0),
    }

Step 4: Monitor Campaign Progress

Track certification progress and send escalations:

  • Dashboard: Saviynt provides real-time campaign dashboard with completion rates
  • Reminders: Automatic email reminders at configured intervals
  • Escalation: If certifier does not respond by due date, escalate to manager's manager or auto-revoke
  • Delegation: Allow certifiers to delegate specific items to application owners

Step 5: Execute Remediation

After campaign closes:

  1. Auto-Remediation: Saviynt automatically creates provisioning tasks to revoke denied access
  2. Ticket Integration: Revocation tasks create tickets in ServiceNow/Jira for tracking
  3. Grace Period: Configure a grace period (e.g., 5 business days) before access is actually removed
  4. Verification: After revocation, verify access is removed from target systems
  5. Audit Trail: All decisions, revocations, and remediations logged for compliance evidence

Validation Checklist

  • Campaign templates configured for each certification type
  • Certifier roles assigned (managers, app owners, data owners)
  • Risk scoring and usage analytics enabled
  • SoD violation detection configured
  • Reminder and escalation schedules defined
  • Auto-revoke policy for non-responsive certifiers configured
  • Campaign launched and certifiers notified
  • Campaign completion rate > 95% before close
  • Revocation tasks created for all denied entitlements
  • Remediation completed within SLA
  • Campaign report generated for compliance audit
  • Evidence archived for regulatory retention period

References

Source materials

References and resources

Everything below is rendered for inspection. Script files are read-only and never run.

References 3

api-reference.md1.7 KB

API Reference: Saviynt Access Recertification

Saviynt EIC REST API v5

Authentication

POST /ECM/api/login
Body: {"username": "admin", "password": "pass"}
Returns: {"access_token": "...", "token_type": "Bearer"}

Certification Endpoints

Method Endpoint Description
POST /ECM/api/v5/listCertification List campaigns
POST /ECM/api/v5/getCertificationDetails Campaign statistics
POST /ECM/api/v5/getCertificationItems Get review items
POST /ECM/api/v5/certifyItems Certify/revoke items

listCertification Payload

Field Description
certificationstatus active, completed, expired
max Maximum results per page
offset Pagination offset

Certification Item Fields

Field Description
username Identity under review
entitlement_value Access being reviewed
risk_score Computed risk (0-10)
last_used_date Last access usage date
peer_group_match Whether peers have same access

certifyItems Actions

Action Description
certify Approve continued access
revoke Remove access
consult Request additional reviewer input

Campaign Types

Type Trigger
User Manager Manager reviews direct reports
Application Owner App owner reviews all users
Entitlement Owner Entitlement owner reviews holders
Event-Based Triggered by role/department change

References

standards.md1.9 KB

Access Recertification with Saviynt - Standards Reference

Compliance Requirements for Access Reviews

SOX Section 404 (Sarbanes-Oxley)

  • Quarterly access reviews for financially significant applications
  • Evidence of review decisions with justification
  • Remediation of revoked access within defined SLA
  • Separation of duties validation during certification

SOC 2 Type II

  • CC6.1: Logical access controls
  • CC6.2: User registration and authorization
  • CC6.3: Access modification and removal
  • Semi-annual certification campaigns required for trust service criteria

PCI DSS v4.0

  • 7.2.4: User accounts and access reviewed at least every 6 months
  • 7.2.5: Application and system accounts reviewed every 6 months
  • Evidence of review decisions required

HIPAA Security Rule

  • 164.312(a)(1): Access control standard
  • 164.308(a)(3)(ii)(A): Workforce clearance procedure
  • 164.308(a)(4): Information access management
  • Annual access reviews for PHI-accessing systems

GDPR Article 5(1)(f)

  • Appropriate security of personal data
  • Regular access reviews ensure only authorized personnel access PII
  • Documentation of access review decisions

Saviynt Campaign Configuration Standards

Campaign Frequency by Compliance

Framework Minimum Frequency Scope
SOX Quarterly Financial applications
SOC 2 Semi-annually All in-scope systems
PCI DSS Semi-annually Cardholder data systems
HIPAA Annually PHI-accessing systems
ISO 27001 Annually All systems
NIST CSF Per risk assessment Risk-based

Risk-Based Certification

Risk Level Review Frequency Certifier Auto-Revoke
Critical Monthly CISO + App Owner 7 days
High Quarterly Manager + App Owner 14 days
Medium Semi-annually Manager 21 days
Low Annually Manager 30 days
workflows.md3.9 KB

Access Recertification with Saviynt - Workflows

Campaign Execution Workflow

WEEK 1: PREPARATION
    ├── Review and update certifier assignments
    ├── Verify identity data freshness (HR sync)
    ├── Validate entitlement data accuracy
    ├── Configure campaign template
    └── Schedule campaign launch
 
WEEK 2: LAUNCH AND REVIEW
    ├── Launch campaign (auto-notifications sent)
    ├── Certifiers receive email with review link
    ├── Certifiers review each line item:
    │   ├── Check user's current role
    │   ├── Review risk score
    │   ├── Check last access date
    │   ├── Compare with peer group
    │   └── Make certify/revoke decision
    └── Day 7: First reminder sent
 
WEEK 3: FOLLOW-UP
    ├── Day 10: Second reminder sent
    ├── Day 13: Final reminder (escalation warning)
    ├── Security team contacts non-responsive certifiers
    └── Campaign manager reviews progress dashboard
 
WEEK 4: CLOSE AND REMEDIATE
    ├── Day 14: Campaign due date
    ├── Day 15: Auto-revoke for non-certified items (if configured)
    ├── Revocation tasks created automatically
    ├── Remediation tickets sent to provisioning team
    ├── Access removed from target systems
    └── Campaign report generated for compliance

Certifier Decision Workflow

Certifier opens Saviynt certification inbox

    ├── For each user-entitlement pair:

    │   ├── Review Context:
    │   │   ├── User's name, title, department
    │   │   ├── Entitlement name and application
    │   │   ├── Risk score (1-10)
    │   │   ├── Last access: 3 days ago / 180 days ago / Never
    │   │   ├── Peer analysis: 85% of peers have this access
    │   │   └── SoD violation: None / Conflict detected
    │   │
    │   ├── Decision Logic:
    │   │   ├── Active user + Used recently + Peers have it → CERTIFY
    │   │   ├── Active user + Not used in 90+ days → INVESTIGATE
    │   │   ├── User changed department → LIKELY REVOKE
    │   │   ├── SoD violation detected → REVOKE or ESCALATE
    │   │   └── Cannot determine → DELEGATE to app owner
    │   │
    │   └── Record decision with justification

    └── Submit all decisions

Event-Based Certification Workflow

User attribute changes in HR system (e.g., department transfer)

    ├── Saviynt detects change via HR connector sync

    ├── User update rule triggers micro-certification:
    │   ├── Scope: All entitlements for this user
    │   ├── Certifier: New manager
    │   └── Due date: 7 days

    ├── New manager reviews all access:
    │   ├── Certify access relevant to new role
    │   ├── Revoke access specific to old role
    │   └── Request new access if needed

    └── Remediation executes for revoked items

Remediation Tracking Workflow

Campaign completes with revoked items

    ├── Saviynt creates provisioning tasks for each revocation

    ├── For each revoked entitlement:
    │   ├── Create deprovisioning request
    │   ├── Route to target system connector
    │   ├── Execute removal (API/connector)
    │   ├── Verify removal succeeded
    │   └── Update audit log

    ├── If automated removal fails:
    │   ├── Create manual remediation ticket (ServiceNow)
    │   ├── Assign to application admin
    │   ├── Track SLA compliance
    │   └── Escalate if overdue

    └── Post-remediation verification:
        ├── Re-scan target systems
        ├── Confirm revoked access no longer present
        └── Archive compliance evidence

Scripts 2

agent.py6.7 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""Agent for managing Saviynt access recertification campaigns via REST API."""

import os
import requests
import json
import argparse
from datetime import datetime, timezone
import urllib3

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)


def authenticate(base_url, username, password):
    """Authenticate to Saviynt EIC and get OAuth token."""
    url = f"{base_url}/ECM/api/login"
    payload = {"username": username, "password": password}
    resp = requests.post(url, json=payload, verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true", timeout=30)  # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments
    resp.raise_for_status()
    token = resp.json().get("access_token")
    print(f"[*] Authenticated to Saviynt EIC")
    return {"Authorization": f"Bearer {token}"}


def list_campaigns(base_url, headers, status="active"):
    """List certification campaigns."""
    url = f"{base_url}/ECM/api/v5/listCertification"
    payload = {"certificationstatus": status, "max": 50, "offset": 0}
    resp = requests.post(url, headers=headers, json=payload, verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true", timeout=30)  # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments
    resp.raise_for_status()
    campaigns = resp.json().get("certifications", [])
    print(f"\n[*] Campaigns ({status}): {len(campaigns)}")
    for c in campaigns:
        print(f"  {c.get('certificationname')} - certifier: {c.get('certifier', 'N/A')} "
              f"| due: {c.get('duedate', 'N/A')}")
    return campaigns


def get_campaign_details(base_url, headers, cert_key):
    """Get detailed campaign status including item counts."""
    url = f"{base_url}/ECM/api/v5/getCertificationDetails"
    payload = {"certkey": cert_key}
    resp = requests.post(url, headers=headers, json=payload, verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true", timeout=30)  # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments
    resp.raise_for_status()
    details = resp.json()
    total = details.get("totalitems", 0)
    certified = details.get("certifieditems", 0)
    revoked = details.get("revokeditems", 0)
    pending = total - certified - revoked
    print(f"\n[*] Campaign {cert_key}: total={total}, certified={certified}, "
          f"revoked={revoked}, pending={pending}")
    return details


def get_pending_items(base_url, headers, cert_key, max_items=100):
    """Get items pending review in a certification campaign."""
    url = f"{base_url}/ECM/api/v5/getCertificationItems"
    payload = {"certkey": cert_key, "status": "pending", "max": max_items, "offset": 0}
    resp = requests.post(url, headers=headers, json=payload, verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true", timeout=30)  # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments
    resp.raise_for_status()
    items = resp.json().get("certificationitems", [])
    print(f"\n[*] Pending items: {len(items)}")
    high_risk = [i for i in items if i.get("risk_score", 0) > 7]
    print(f"  High-risk items (score > 7): {len(high_risk)}")
    for i in high_risk[:10]:
        print(f"  [!] {i.get('username')} - {i.get('entitlement_value')} "
              f"(risk: {i.get('risk_score')})")
    return items


def certify_items(base_url, headers, cert_key, item_ids, action="certify"):
    """Certify or revoke items in a campaign."""
    url = f"{base_url}/ECM/api/v5/certifyItems"
    payload = {"certkey": cert_key, "itemids": item_ids, "action": action,
               "comments": f"Auto-{action} by recertification agent"}
    resp = requests.post(url, headers=headers, json=payload, verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true", timeout=30)  # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments
    resp.raise_for_status()
    print(f"[*] {action.capitalize()}d {len(item_ids)} items in campaign {cert_key}")
    return resp.json()


def check_overdue_campaigns(base_url, headers):
    """Find campaigns past their due date."""
    url = f"{base_url}/ECM/api/v5/listCertification"
    payload = {"certificationstatus": "active", "max": 200, "offset": 0}
    resp = requests.post(url, headers=headers, json=payload, verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true", timeout=30)  # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments
    resp.raise_for_status()
    campaigns = resp.json().get("certifications", [])
    now = datetime.now(timezone.utc)
    overdue = []
    for c in campaigns:
        due = c.get("duedate", "")
        if due:
            try:
                due_dt = datetime.fromisoformat(due.replace("Z", "+00:00"))
                if due_dt < now:
                    overdue.append({"name": c.get("certificationname"),
                                    "due": due, "certifier": c.get("certifier")})
            except ValueError:
                pass
    print(f"\n[*] Overdue campaigns: {len(overdue)}")
    for o in overdue:
        print(f"  [!] {o['name']} (due: {o['due']}, certifier: {o['certifier']})")
    return overdue


def generate_report(campaigns, overdue, output_path):
    """Generate recertification status report."""
    report = {"report_date": datetime.now(timezone.utc).isoformat(),
              "active_campaigns": len(campaigns), "overdue_campaigns": len(overdue),
              "campaigns": campaigns[:50], "overdue": overdue}
    with open(output_path, "w") as f:
        json.dump(report, f, indent=2, default=str)
    print(f"\n[*] Report saved to {output_path}")


def main():
    parser = argparse.ArgumentParser(description="Saviynt Access Recertification Agent")
    parser.add_argument("action", choices=["list", "details", "pending", "overdue", "full-audit"])
    parser.add_argument("--url", required=True, help="Saviynt EIC base URL")
    parser.add_argument("--username", required=True)
    parser.add_argument("--password", required=True)
    parser.add_argument("--cert-key", help="Certification campaign key")
    parser.add_argument("-o", "--output", default="recert_report.json")
    args = parser.parse_args()

    headers = authenticate(args.url, args.username, args.password)
    if args.action == "list":
        list_campaigns(args.url, headers)
    elif args.action == "details" and args.cert_key:
        get_campaign_details(args.url, headers, args.cert_key)
    elif args.action == "pending" and args.cert_key:
        get_pending_items(args.url, headers, args.cert_key)
    elif args.action == "overdue":
        check_overdue_campaigns(args.url, headers)
    elif args.action == "full-audit":
        campaigns = list_campaigns(args.url, headers)
        overdue = check_overdue_campaigns(args.url, headers)
        generate_report(campaigns, overdue, args.output)


if __name__ == "__main__":
    main()
process.py8.5 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""
Saviynt Access Recertification Campaign Manager

Manages access recertification campaigns via Saviynt REST API,
tracks campaign progress, and generates compliance reports.

Requirements:
    pip install requests pandas
"""

import json
import logging
import sys
from datetime import datetime, timedelta, timezone

try:
    import requests
except ImportError:
    print("[ERROR] requests required: pip install requests")
    sys.exit(1)

logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger("saviynt_recert")


class SaviyntCampaignManager:
    """Manage Saviynt access recertification campaigns."""

    def __init__(self, base_url, username, password):
        self.base_url = base_url.rstrip("/")
        self.token = self._authenticate(username, password)

    def _authenticate(self, username, password):
        """Authenticate and retrieve API token."""
        resp = requests.post(
            f"{self.base_url}/ECM/api/login",
            json={"username": username, "password": password},
        )
        resp.raise_for_status()
        token = resp.json().get("access_token")
        if not token:
            raise Exception("Authentication failed: no token returned")
        logger.info("Authenticated to Saviynt EIC")
        return token

    def _api_call(self, method, endpoint, json_data=None):
        headers = {
            "Authorization": f"Bearer {self.token}",
            "Content-Type": "application/json",
        }
        url = f"{self.base_url}{endpoint}"
        resp = requests.request(method, url, headers=headers, json=json_data)
        resp.raise_for_status()
        return resp.json()

    def create_campaign(self, name, campaign_type, certifier_type,
                         due_days=14, scope=None):
        """Create a new certification campaign."""
        due_date = (datetime.now(timezone.utc) + timedelta(days=due_days)).strftime(
            "%Y-%m-%d"
        )
        payload = {
            "campaignname": name,
            "campaigntype": campaign_type,  # UserManager, EntitlementOwner, Application
            "certifier": certifier_type,
            "duedate": due_date,
            "reminderdays": "7,10,13",
            "autorevoke": True,
            "autorevokedays": due_days + 1,
            "status": "New",
        }
        if scope:
            payload["scope"] = scope

        result = self._api_call("POST", "/ECM/api/v5/createCampaign", payload)
        campaign_id = result.get("campaignId")
        logger.info(f"Campaign created: {name} (ID: {campaign_id})")
        return result

    def launch_campaign(self, campaign_id):
        """Launch an existing campaign, sending notifications to certifiers."""
        result = self._api_call(
            "POST", "/ECM/api/v5/launchCampaign",
            {"campaignId": campaign_id}
        )
        logger.info(f"Campaign {campaign_id} launched")
        return result

    def get_campaign_status(self, campaign_id):
        """Get campaign progress and statistics."""
        result = self._api_call(
            "GET", f"/ECM/api/v5/getCampaignDetails?campaignId={campaign_id}"
        )
        return {
            "campaign_id": campaign_id,
            "name": result.get("campaignname"),
            "status": result.get("status"),
            "total_items": result.get("totalLineItems", 0),
            "certified": result.get("certifiedCount", 0),
            "revoked": result.get("revokedCount", 0),
            "pending": result.get("pendingCount", 0),
            "completion_pct": result.get("completionPercentage", 0),
            "due_date": result.get("duedate"),
        }

    def get_all_campaigns(self, status=None):
        """List all certification campaigns."""
        params = {}
        if status:
            params["status"] = status
        result = self._api_call("GET", "/ECM/api/v5/getCampaigns")
        campaigns = result.get("campaigns", [])
        if status:
            campaigns = [c for c in campaigns if c.get("status") == status]
        return campaigns

    def get_certification_items(self, campaign_id, status_filter=None):
        """Get individual certification line items for a campaign."""
        result = self._api_call(
            "POST", "/ECM/api/v5/getCertificationDetails",
            {"campaignId": campaign_id, "max": 1000}
        )
        items = result.get("certifications", [])
        if status_filter:
            items = [i for i in items if i.get("status") == status_filter]
        return items

    def generate_compliance_report(self, campaign_id):
        """Generate a compliance-ready report for a completed campaign."""
        status = self.get_campaign_status(campaign_id)
        items = self.get_certification_items(campaign_id)

        certified_items = [i for i in items if i.get("decision") == "Certify"]
        revoked_items = [i for i in items if i.get("decision") == "Revoke"]
        pending_items = [i for i in items if not i.get("decision")]

        report = {
            "report_title": "Access Recertification Compliance Report",
            "campaign_name": status.get("name"),
            "campaign_id": campaign_id,
            "generated_at": datetime.now(timezone.utc).isoformat(),
            "metrics": {
                "total_items_reviewed": status["total_items"],
                "certified": status["certified"],
                "revoked": status["revoked"],
                "pending": status["pending"],
                "completion_rate": f"{status['completion_pct']}%",
                "certification_rate": (
                    f"{(status['certified'] / status['total_items'] * 100):.1f}%"
                    if status["total_items"] else "N/A"
                ),
                "revocation_rate": (
                    f"{(status['revoked'] / status['total_items'] * 100):.1f}%"
                    if status["total_items"] else "N/A"
                ),
            },
            "findings": [],
        }

        if pending_items:
            report["findings"].append({
                "severity": "High",
                "finding": f"{len(pending_items)} items not reviewed by due date",
                "action": "Auto-revoke or manual follow-up required",
            })

        revoke_rate = status["revoked"] / status["total_items"] if status["total_items"] else 0
        if revoke_rate > 0.20:
            report["findings"].append({
                "severity": "Medium",
                "finding": f"High revocation rate ({revoke_rate:.0%}) suggests over-provisioning",
                "action": "Review provisioning policies and role definitions",
            })

        return report


class RecertificationScheduler:
    """Schedule recurring certification campaigns based on compliance needs."""

    def __init__(self):
        self.schedules = []

    def add_schedule(self, name, frequency_days, campaign_type,
                      certifier_type, scope=None):
        """Add a recurring certification schedule."""
        self.schedules.append({
            "name": name,
            "frequency_days": frequency_days,
            "campaign_type": campaign_type,
            "certifier_type": certifier_type,
            "scope": scope,
            "last_run": None,
        })

    def get_due_campaigns(self):
        """Check which campaigns are due to run."""
        now = datetime.now(timezone.utc)
        due = []
        for schedule in self.schedules:
            if schedule["last_run"] is None:
                due.append(schedule)
            else:
                next_run = schedule["last_run"] + timedelta(days=schedule["frequency_days"])
                if now >= next_run:
                    due.append(schedule)
        return due

    def export_schedule(self, output_path):
        """Export certification schedule for documentation."""
        with open(output_path, "w") as f:
            json.dump({
                "schedules": self.schedules,
                "exported_at": datetime.now(timezone.utc).isoformat(),
            }, f, indent=2, default=str)
        logger.info(f"Schedule exported to {output_path}")


if __name__ == "__main__":
    print("=" * 60)
    print("Saviynt Access Recertification Campaign Manager")
    print("=" * 60)
    print()
    print("Usage:")
    print("  mgr = SaviyntCampaignManager('https://tenant.saviyntcloud.com',")
    print("      'admin', 'password')")
    print("  mgr.create_campaign('Q1 Review', 'UserManager', 'Manager')")
    print("  mgr.launch_campaign(campaign_id)")
    print("  report = mgr.generate_compliance_report(campaign_id)")

Assets 1

template.mdtext/markdown · 1.3 KB
Keep exploring