Edits history of script submission #22296 for ' Obtain and cache a ServiceNow OAuth2 access token (servicenow)'

  • python3
    
    import time
    import requests
    from typing import TypedDict
    
    try:
        import wmill
        _WMILL_AVAILABLE = True
    except ImportError:
        _WMILL_AVAILABLE = False
    
    TOKEN_CACHE_PATH = "f/ServiceNow/snow_token_cache"
    TOKEN_EXPIRY_BUFFER_SECS = 60
    
    
    class servicenow(TypedDict):
        instance_url: str
        client_id: str
        client_secret: str
        username: str
        password: str
    
    
    def get_token(snow: servicenow) -> str:
        """
        Returns a valid ServiceNow access token.
        Caches the token in f/ServiceNow/snow_token_cache and reuses it until
        it is within 60s of expiry (default lifetime is 30 minutes).
        Uses OAuth2 Resource Owner Password Credentials grant.
        """
        if _WMILL_AVAILABLE:
            try:
                cached = wmill.get_resource(TOKEN_CACHE_PATH)
                if (
                    isinstance(cached, dict)
                    and cached.get("expires_at", 0) > time.time() + TOKEN_EXPIRY_BUFFER_SECS
                ):
                    return cached["token"]
            except Exception:
                pass
    
        resp = requests.post(
            f"{snow['instance_url'].rstrip('/')}/oauth_token.do",
            data={
                "grant_type": "password",
                "client_id": snow["client_id"],
                "client_secret": snow["client_secret"],
                "username": snow["username"],
                "password": snow["password"],
            },
            headers={"Content-Type": "application/x-www-form-urlencoded"},
            timeout=15,
        )
        resp.raise_for_status()
        data = resp.json()
    
        if "error" in data:
            raise RuntimeError(f"ServiceNow OAuth error: {data['error']} — {data.get('error_description', '')}")
    
        token = data["access_token"]
        expires_in = data.get("expires_in", 1800)
    
        if _WMILL_AVAILABLE:
            try:
                wmill.set_resource(
                    path=TOKEN_CACHE_PATH,
                    value={"token": token, "expires_at": time.time() + expires_in},
                    resource_type="state",
                )
            except Exception:
                pass
    
        return token
    
    
    def main(snow: servicenow) -> str:
        """Verify credentials and warm the token cache. Safe to run standalone."""
        token = get_token(snow)
        print(f"Token obtained successfully (prefix: {token[:10]}...)")
        print(f"Cached at: {TOKEN_CACHE_PATH}")
        return "OK"
    

    Submitted by elib3n379 51 days ago