Skip to content

Commit

Permalink
Merge branch 'feature/zcc' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
mitchos committed May 14, 2022
2 parents 57ab193 + d3afd2d commit 224316f
Show file tree
Hide file tree
Showing 15 changed files with 429 additions and 0 deletions.
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ by Steve McGrath.
## Products
- Zscaler Private Access (ZPA)
- Zscaler Internet Access (ZIA)
- Zscaler Mobile Admin Portal for Zscaler Client Connector (ZCC)
- Cloud Security Posture Management (CSPM) - (work in progress)


Expand Down Expand Up @@ -72,6 +73,18 @@ for app_segment in zpa.app_segments.list_segments():
pprint(app_segment)
```

### Quick ZCC Example

```python
from pyzscaler import ZCC
from pprint import pprint

zcc = ZCC(client_id='CLIENT_ID', client_secret='CLIENT_SECRET', company_id='COMPANY_ID')
for device in zcc.devices.list_devices():
pprint(device)
```


## Documentation
### API Documentation
pyZscaler's API is fully 100% documented and is hosted at [ReadTheDocs](https://pyzscaler.readthedocs.io).
Expand Down
16 changes: 16 additions & 0 deletions docsrc/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

zs/zia/index
zs/zpa/index
zs/zcc/index

pyZscaler SDK - Library Reference
=====================================================================
Expand Down Expand Up @@ -39,6 +40,7 @@ Products
---------
- :doc:`Zscaler Private Access (ZPA) <zs/zpa/index>`
- :doc:`Zscaler Internet Access (ZIA) <zs/zia/index>`
- :doc:`Zscaler Mobile Admin Portal <zs/zcc/index>`
- Cloud Security Posture Management (CSPM) - (work in progress)

Installation
Expand Down Expand Up @@ -83,6 +85,20 @@ Quick ZPA Example
for app_segment in zpa.app_segments.list_segments():
pprint(app_segment)
Quick ZCC Example
^^^^^^^^^^^^^^^^^^^

.. code-block:: python
from pyzscaler import ZCC
from pprint import pprint
zcc = ZCC(client_id='CLIENT_ID', client_secret='CLIENT_SECRET', company_id='COMPANY_ID)
for device in zcc.devices.list_devices():
pprint(device)
.. automodule:: pyzscaler
:members:
Expand Down
12 changes: 12 additions & 0 deletions docsrc/zs/zcc/devices.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
devices
--------------

The following methods allow for interaction with the ZCC
Devices API endpoints.

Methods are accessible via ``zcc.devices``

.. _zcc-devices:

.. automodule:: pyzscaler.zcc.devices
:members:
26 changes: 26 additions & 0 deletions docsrc/zs/zcc/index.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
ZCC
==========
This package covers the ZCC interface.

Retrieving the ZCC Company ID.
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

The ZCC Company ID can be obtained by following these instructions:
1. Navigate to the Zscaler Mobile Admin Portal in a web browser.
2. Open the Browser console (typically ``F12``) and click on **Network**.
3. From the top navigation, click on **Enrolled Devices**.
4. Look for the API call ``mobileadmin.zscaler.net/webservice/api/web/usersByCompany`` in the 'Networks' tab
of the Browser Console. Click on this entry.
5. Click on either **Preview** or **Response** to see the data that was returned by the Mobile Admin Portal.
6. The Company ID is represented as an ``int`` and can be found under the ``companyId`` key in the object returned
for each user.

.. toctree::
:maxdepth: 1
:glob:
:hidden:

*

.. automodule:: pyzscaler.zcc
:members:
11 changes: 11 additions & 0 deletions docsrc/zs/zcc/secrets.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
secrets
--------------

The following methods allow for interaction with the ZCC API endpoints for managing secrets.

Methods are accessible via ``zcc.secrets``

.. _zcc-secrets:

.. automodule:: pyzscaler.zcc.secrets
:members:
12 changes: 12 additions & 0 deletions docsrc/zs/zcc/session.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
session
--------------

The following methods allow for interaction with the ZCC
Session API endpoints.

Methods are accessible via ``zcc.session``

.. _zcc-session:

.. automodule:: pyzscaler.zcc.session
:members:
1 change: 1 addition & 0 deletions pyzscaler/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@
]
__version__ = "1.1.1"

from pyzscaler.zcc import ZCC # noqa
from pyzscaler.zia import ZIA # noqa
from pyzscaler.zpa import ZPA # noqa
64 changes: 64 additions & 0 deletions pyzscaler/zcc/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import os

from box import Box
from restfly.session import APISession

from pyzscaler import __version__

from .devices import DevicesAPI
from .secrets import SecretsAPI
from .session import AuthenticatedSessionAPI


class ZCC(APISession):
"""
A Controller to access Endpoints in the Zscaler Mobile Admin Portal API.
The ZCC object stores the session token and simplifies access to CRUD options within the ZCC Portal.
Attributes:
client_id (str): The ZCC Client ID generated from the ZCC Portal.
client_secret (str): The ZCC Client Secret generated from the ZCC Portal.
company_id (str):
The ZCC Company ID. There seems to be no easy way to obtain this at present. See the note
at the top of this page for information on how to retrieve the Company ID.
"""

_vendor = "Zscaler"
_product = "Zscaler Mobile Admin Portal"
_backoff = 3
_build = __version__
_box = True
_box_attrs = {"camel_killer_box": True}
_env_base = "ZCC"
_env_cloud = "zscaler"
_url = "https://api-mobile.zscaler.net/papi"

def __init__(self, **kw):
self._client_id = kw.get("client_id", os.getenv(f"{self._env_base}_CLIENT_ID"))
self._client_secret = kw.get("client_secret", os.getenv(f"{self._env_base}_CLIENT_SECRET"))
self.company_id = kw.get("company_id", os.getenv(f"{self._env_base}_COMPANY_ID"))
self.conv_box = True
super(ZCC, self).__init__(**kw)

def _build_session(self, **kwargs) -> Box:
"""Creates a ZCC API session."""
super(ZCC, self)._build_session(**kwargs)
self._auth_token = self.session.create_token(client_id=self._client_id, client_secret=self._client_secret)
return self._session.headers.update({"auth-token": f"{self._auth_token}"})

@property
def devices(self):
"""The interface object for the :ref:`ZCC Devices interface <zcc-devices>`."""
return DevicesAPI(self)

@property
def secrets(self):
"""The interface object for the :ref:`ZCC Secrets interface <zcc-secrets>`."""
return SecretsAPI(self)

@property
def session(self):
"""The interface object for the :ref:`ZCC Authenticated Session interface <zcc-session>`."""
return AuthenticatedSessionAPI(self)
27 changes: 27 additions & 0 deletions pyzscaler/zcc/devices.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from box import BoxList
from restfly import APISession
from restfly.endpoint import APIEndpoint


class DevicesAPI(APIEndpoint):
def __init__(self, api: APISession):
super().__init__(api)
self.company_id = api.company_id

def list_devices(self) -> BoxList:
"""
Returns the list of devices enrolled in the Mobile Admin Portal.
Returns:
:obj:`BoxList`: A list containing devices using ZCC enrolled in the Mobile Admin Portal.
Examples:
Prints all devices in the Mobile Admin Portal to the console:
>>> for device in zcc.devices.list_devices():
... print(device)
"""
payload = {"companyId": self.company_id}

return self._get("public/v1/getDevices", json=payload)
79 changes: 79 additions & 0 deletions pyzscaler/zcc/secrets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
from restfly import APISession
from restfly.endpoint import APIEndpoint


class SecretsAPI(APIEndpoint):
os_map = {
"ios": 1,
"android": 2,
"windows": 3,
"macos": 4,
"linux": 5,
}

def __init__(self, api: APISession):
super().__init__(api)
self.company_id = api.company_id

def get_otp(self, device_id: str):
"""
Returns the OTP code for the specified device id.
Args:
device_id (str): The unique id for the enrolled device that the OTP will be obtained for.
Returns:
:obj:`Box`: A dictionary containing the requested OTP code for the specified device id.
Examples:
Obtain the OTP code for a device and print it to console:
>>> otp_code = zcc.secrets.get_otp('System-Serial-Number:1234ABCDEF')
... print(otp_code.otp)
"""

payload = {"udid": device_id}

return self._get("public/v1/getOtp", params=payload)

def get_passwords(self, username: str, os_type: str = "windows"):
"""
Return passwords for the specified username and device OS type.
Args:
username (str): The username that the device belongs to.
os_type (str): The OS Type for the device, defaults to `windows`. Valid options are:
- ios
- android
- windows
- macos
- linux
Returns:
:obj:`Box`: Dictionary containing passwords for the specified username's device.
Examples:
Print macos device passwords for username test@example.com:
>>> print(zcc.secrets.get_passwords(username='test@example.com',
... os_type='macos'))
"""

payload = {
"companyId": self.company_id,
}

# Simplify the os_type argument, raise an error if the user supplies the wrong one.
os_type = self.os_map.get(os_type, None)
if not os_type:
raise ValueError("Invalid os_type specified. Check the pyZscaler documentation for valid os_type options.")

params = {
"username": username,
"osType": os_type,
}

return self._get("public/v1/getPasswords", data=payload, params=params)
30 changes: 30 additions & 0 deletions pyzscaler/zcc/session.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from box import Box
from restfly.endpoint import APIEndpoint


class AuthenticatedSessionAPI(APIEndpoint):
def create_token(self, client_id: str, client_secret: str) -> Box:
"""
Creates a ZCC authentication token.
Args:
client_id (str): The ZCC Portal Client ID.
client_secret (str): The ZCC Portal Client Secret.
Returns:
:obj:`Box`: The authenticated session information.
Examples:
>>> zia.session.create(api_key='999999999',
... username='admin@example.com',
... password='MyInsecurePassword')
"""

payload = {
"apiKey": client_id,
"secretKey": client_secret,
}

return self._post("auth/v1/login", json=payload).jwt_token
28 changes: 28 additions & 0 deletions tests/zcc/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import pytest
import responses

from pyzscaler.zcc import ZCC


@pytest.fixture(name="session")
def fixture_session():
return {
"jwtToken": "ADMIN_LOGIN",
}


@pytest.fixture(name="zcc")
@responses.activate
def zcc(session):
responses.add(
responses.POST,
url="https://api-mobile.zscaler.net/papi/auth/v1/login",
content_type="application/json",
json=session,
status=200,
)
return ZCC(
client_id="abc123",
client_secret="999999",
company_id="88888",
)
24 changes: 24 additions & 0 deletions tests/zcc/test_zcc_devices.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import pytest
import responses
from box import BoxList
from responses import matchers


@pytest.fixture(name="devices")
def fixture_devices():
return [{"id": 1}, {"id": 2}]


@responses.activate
def test_list_devices(devices, zcc):
responses.add(
method="GET",
url="https://api-mobile.zscaler.net/papi/public/v1/getDevices",
json=devices,
match=[matchers.json_params_matcher({"companyId": "88888"})],
status=200,
)
resp = zcc.devices.list_devices()

assert isinstance(resp, BoxList)
assert resp[0].id == 1
Loading

0 comments on commit 224316f

Please sign in to comment.