Sample Python script to programmatically retrieve OTP for Client Connector users

Use Case: ServiceDesk users need to retrieve OTP for Client Connector users for troubleshooting purposes and customer wants to avoid giving ServiceDesk access to the Zscaler Admin UI. ServiceDesk can programmatically retrieve OTP for users from within applications like ServiceNow using the new Mobile Admin API

The script uses the new Mobile Admin API.

Script requirements:

  1. API enabled in your tenant
  2. Python3 with Requests module
  3. API access setup in Mobile Admin UI (Step 1 below)
  4. Script updated with API Endpoint URL, Client ID and Client Secret (Step 2 below)
% python get_device_otp.py username@authdomain.com

HOSTNAME            |TYPE                     |OS                                                |EXITOTP
____________________________________________________________________________________________________________
sales1               VMware, Inc. VMware7,1    Microsoft Windows 10 Enterprise;64 bit;amd64       ays0bmiu9e
salesuser1’s iPad    Apple iPad13,4            Version 15.4.1 (Build 19E258)                      0rnbg2omrb

Step 1: Configure API access in Mobile Admin UI by creating your API Key and Secret

Step 2: Update the Python script with your credentials

#
# written: Niladri Datta ndatta@zscaler.com
# date: April 16 2022
# updated: Feb 2023
# This script accepts username as a parameter and retrieves exitOTP for all
# devices registered in Zscaler Mobile Admin for user
#
# Usage:
# > python3 get_device_otp.py user@domain.com
# HOSTNAME            |TYPE                     |OS                                                |EXITOTP
# ________________________________________________________________________________________________________________________
# sales1               VMware, Inc. VMware7,1    Microsoft Windows 10 Enterprise;64 bit;amd64       ays0bmiu9e
# salesuser1’s iPad    Apple iPad13,4            Version 15.4.1 (Build 19E258)                      0rnbg2omrb
#
# Requirements:
# Environment: Python3 with Requests module installed
# base_url: API endpoint URL for your tenant. Change it below
# key: Change this to the Client ID value from the Mobile Admin UI
# secret: Change this to the Client Secret value from the Mobile Admin UI

import sys
import requests
import json
import urllib3


# Change this to your cloud used by tenant
base_url = "https://api-mobile.zscalerbeta.net/papi"

# Insert generated API key and secret from Mobile Admin UI
key = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
secret = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"

# Disable unverified HTTPS warnings
urllib3.disable_warnings()


def getdeviceotp(session_token: str, url: str, dev_udid: str) -> str:
    # This function retrieves the exitOTP for a device

    # Include the session token in your header
    auth_header = {
        'Content-Type': 'application/json',
        'auth-token': session_token
    }

    # Send udid as parameter in URL query string
    request_params = {
        'udid': dev_udid
    }

    rurl = url + "/public/v1/getOtp"

    # Get the OTP
    response_otp = requests.get(rurl, headers=auth_header, params=request_params, verify=False)
    device_exitotp = response_otp.json()["exitOtp"]

    return device_exitotp


####
# Step 1: Get username parameter
if len(sys.argv) == 2:
    user = sys.argv[1]
else:
    sys.exit("Exiting. Script executed incorrectly.\nExample syntax: python thisscript.py someuser@somedomain.com")

###
# Step 2: Authenticate and get session token
stdheader = {
    'Content-Type': 'application/json',
    'Accept': 'application/json',
}

fullurl = base_url + "/auth/v1/login"

# Send API key and secret in body
auth_param = {
    "apiKey": key,
    "secretKey": secret
}

response = requests.post(fullurl, headers=stdheader, data=json.dumps(auth_param), verify=False)

if response.status_code == 200:
    mytoken = response.json()["jwtToken"]
else:
    sys.exit("Check URL and API key and secret. Authentication failed")

###
# Step 3: Get all devices enrolled by user
header = {
    'Content-Type': 'application/json',
    'auth-token': mytoken
}

fullurl = base_url + "/public/v1/getDevices"

# Send username parameter in URL query string
payload = {
    'username': user
}

response = requests.get(fullurl, headers=header, params=payload, verify=False)

if response.status_code == 200:
    registered_device_count = 0
    all_user_devices = response.json()

    # Print the header
    print("{0:<20}|{1:<25}|{2:<50}|{3:<15}".format("HOSTNAME", "TYPE", "OS", "EXITOTP"))
    print("_"*120)
    ##
    # Step 4: Get OTP for all registered user devices
    for index in all_user_devices:
        if index["registrationState"] == "Registered":
            udid = index["udid"]
            dev_otp = getdeviceotp(mytoken, base_url, udid)
            print("{0:<20} {1:<25} {2:<50} {3:<15}".format(index['machineHostname'], index['detail'],
                                                                 index['osVersion'], dev_otp))
            registered_device_count += 1

    # Existing user with no Registered devices
    if registered_device_count == 0:
        print(f"User {user} has no active devices enrolled")

# User not found returns a 400
elif response.status_code == 400:
    print(f"User {user} not found")
else:
    sys.exit("Error getting device list for user")

Customers can also retrieve all devices as well as App Profile passwords with the new API.

3 Likes

This is awesome. I’m testing it out using my tenant and am getting the following error:

Traceback (most recent call last):
File “/Users/ryan/Downloads/get_device_otp.py”, line 117, in
dev_otp = getdeviceotp(mytoken, base_url, udid)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File “/Users/ryan/Downloads/get_device_otp.py”, line 55, in getdeviceotp
device_otp = response_otp.json()[“otp”]
~~~~~~~~~~~~~~~~~~~^^^^^^^

Any ideas how to resolve?

The new release of Mobile Admin & Zscaler Client Connector now supports OTPs for specific actions which breaks this code as a result. When I orignally wrote the script there was only one OTP which could be used to perform all actions.

Instead of device_otp = response_otp.json()["otp"] you should now use device_otp = response_otp.json()["exitOtp"] which will retrieve the OTP to exit Zscaler Client Connector.

You can check out all the new OTPs you can now retrieve here.

Thanks for the note. I have updated the post.

3 Likes

Zscaler-api-talker python SDK has already this and other methods that be be used

1 Like