diff --git a/.gitignore b/.gitignore index 7fdea58..d876e25 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,6 @@ *.pyc *.egg-info +.idea +venv +enviroments.json +environments.json diff --git a/README.md b/README.md index 4481452..4c7a2b5 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,13 @@ Supported Functionality * Devices * Get Device Details by Alt ID (Macaddress, Udid, Serialnumber, ImeiNumber, EasId) * Get Device ID by Alt ID (Macaddress, Udid, Serialnumber, ImeiNumber, EasId) + * Clear Device Passcode + * Send Commands To devices via Device ID or by Alt ID + * Get Device FileVualt Recover Key + * Get Security Info Sample by Device ID or Alt ID + * GET Bulk Security Info Sample + * Switch device From Staging User to End User + * Get Network info Sample by Device ID * Users * Search for users by Username, Firstname, Lastname, Email, OrganizationGroupID, or Role @@ -38,8 +45,12 @@ Supported Functionality * Get OG ID from Group ID * Create Customer type OG (On-Prem only) * Create Child OG + * Get UUID from OG ID * Smart Groups * Get SG ID by Name and OG ID + * Get SG Details + * Get Devices that are included in SG + * Add Device to SG Device Additions * Admins * Search for admins by Username, Firstname, Lastname, Email, OrganizationGroupID, or Role @@ -50,6 +61,15 @@ Supported Functionality * Get Tag ID from Tag Name * Profiles * Search for profiles by Type, Name, OG ID, Platform, Status, or Ownership + * Request Profile Install for a device +* LDAP + * Create LDAP configurations +* Feature Flags + * Set Feature Flag Status + * Get Feature Flag Status + * List all Feature Flags for a particular OG by UUID +* Info + * Get Environment Information Requirements --- diff --git a/pyairwatch/client.py b/pyairwatch/client.py index a562b6b..1bafdc4 100644 --- a/pyairwatch/client.py +++ b/pyairwatch/client.py @@ -1,20 +1,27 @@ -from __future__ import print_function +from __future__ import print_function, absolute_import import base64 -import json import logging import requests -from .mdm.devices import Devices -from .mdm.profiles import Profiles -from .mdm.smartgroups import SmartGroups -from .mdm.tags import Tags -from .system.admins import Admins -from .system.groups import Groups -from .system.usergroups import UserGroups -from .system.users import Users +from pyairwatch.error import AirWatchAPIError +from pyairwatch.mdm.devices import Devices +from pyairwatch.mdm.profiles import Profiles +from pyairwatch.mdm.smartgroups import SmartGroups +from pyairwatch.mdm.tags import Tags +from pyairwatch.mdm.ldap import LDAP +from pyairwatch.mdm.network import Network +from pyairwatch.system.admins import Admins +from pyairwatch.system.groups import Groups +from pyairwatch.system.usergroups import UserGroups +from pyairwatch.system.users import Users +from pyairwatch.system.featureflag import FeatureFlag +from pyairwatch.system.info import Info +from pyairwatch.mam.application import Application +from pyairwatch.mam.vpp import VPP # Enabling debugging at http.client level (requests->urllib3->http.client) -# you will see the REQUEST, including HEADERS and DATA, and RESPONSE with HEADERS but without DATA. +# you will see the REQUEST, including HEADERS and DATA, and RESPONSE with +# HEADERS but without DATA. # the only thing missing will be the response.body which is not logged. try: from http.client import HTTPConnection @@ -22,6 +29,9 @@ from httplib import HTTPConnection HTTPConnection.debuglevel = 0 +#todo: programing using library should be able to set logging level +#todo: Implement logging to using config https://docs.python.org/3/howto/logging.html#configuring-logging +#todo: set logging correclty for a library https://docs.python.org/3/howto/logging.html#configuring-logging-for-a-library logging.basicConfig() logging.getLogger().setLevel(logging.DEBUG) requests_log = logging.getLogger("requests.packages.urllib3") @@ -29,87 +39,113 @@ requests_log.propagate = True - -class AirWatchAPIError(Exception): - def __init__(self, json_response=None): - if json_response is None: - pass - else: - self.response = json_response - self.error_code = json_response.get('errorCode') - self.error_msg = str(json_response.get('message')) - if self.error_code is None: - self.error_code = 0 - self.error_msg = 'Unknown API error occurred' - - def __str__(self): - return 'Error #{}: {}'.format(self.error_code, self.error_msg) - - class AirWatchAPI(object): def __init__(self, env, apikey, username, password): - self.env = env - self.apikey = apikey - self.username = username - self.password = password - self.groups = Groups(self) - self.smartgroups = SmartGroups(self) - self.devices = Devices(self) - self.profiles = Profiles(self) - self.tags = Tags(self) - self.admins = Admins(self) - self.users = Users(self) - self.usergroups = UserGroups(self) - - def get(self, module, path, version=None, params=None, header=None, timeout=30): - """Sends a GET request to the API. Returns the response object.""" + self.env = env + self.apikey = apikey + self.username = username + self.password = password + self.groups = Groups(self) + self.smartgroups = SmartGroups(self) + self.devices = Devices(self) + self.profiles = Profiles(self) + self.tags = Tags(self) + self.admins = Admins(self) + self.users = Users(self) + self.usergroups = UserGroups(self) + self.featureflag = FeatureFlag(self) + self.ldap = LDAP(self) + self.info = Info(self) + self.network = Network(self) + self.application = Application(self) + self.vpp = VPP(self) + + def get(self, module, path, version=None, params=None, header=None, + timeout=30): + """ + Sends a GET request to the API. Returns the response object. + """ if header is None: header = {} - header.update(self._build_header(self.username, self.password, self.apikey)) + header.update(self._build_header(self.username, self.password, + self.apikey)) header.update({'Content-Type': 'application/json'}) endpoint = self._build_endpoint(self.env, module, path, version) try: - r = requests.get(endpoint, params=params, headers=header, timeout=timeout) + r = requests.get(endpoint, params=params, headers=header, + timeout=timeout) + r = self._check_for_error(r) + return r + except AirWatchAPIError as e: + raise e + + def post(self, module, path, version=None, params=None, data=None, + json=None, header=None, timeout=30): + """ + Sends a POST request to the API. Returns the response object. + """ + if header is None: + header = {} + header.update(self._build_header(self.username, self.password, + self.apikey)) + endpoint = self._build_endpoint(self.env, module, path, version) + try: + r = requests.post(endpoint, params=params, data=data, json=json, + headers=header, timeout=timeout) r = self._check_for_error(r) return r except AirWatchAPIError as e: raise e - def post(self, module, path, version=None, params=None, data=None, json=None, header=None, timeout=30): - """Sends a POST request to the API. Returns the response object.""" + def put(self, module, path, version=None, params=None, data=None, + json=None, header=None, timeout=30): + """ + Sends a PUT request to the API. Returns the response object. + """ if header is None: header = {} - header.update(self._build_header(self.username, self.password, self.apikey)) + header.update(self._build_header(self.username, self.password, + self.apikey)) endpoint = self._build_endpoint(self.env, module, path, version) try: - r = requests.post(endpoint, params=params, data=data, json=json, headers=header, timeout=timeout) + r = requests.put(endpoint, params=params, data=data, json=json, + headers=header, timeout=timeout) r = self._check_for_error(r) return r except AirWatchAPIError as e: raise e - def put(self, module, path, version=None, params=None, data=None, json=None, header=None, timeout=30): - """Sends a PUT request to the API. Returns the response object.""" + def patch(self, module, path, version=None, params=None, data=None, + json=None, header=None, timeout=30): + """ + Sends a Patch request to the API. Returns the response object. + """ if header is None: header = {} - header.update(self._build_header(self.username, self.password, self.apikey)) + header.update(self._build_header(self.username, self.password, + self.apikey)) endpoint = self._build_endpoint(self.env, module, path, version) try: - r = requests.put(endpoint, params=params, data=data, json=json, headers=header, timeout=timeout) + r = requests.patch(endpoint, params=params, data=data, json=json, + headers=header, timeout=timeout) r = self._check_for_error(r) return r except AirWatchAPIError as e: raise e - #NOQA - def delete(self, module, path, version=None, params=None, header=None, timeout=30): - """Sends a DELETE request to the API. Returns the response object.""" + def delete(self, module, path, version=None, params=None, header=None, + timeout=30): + """ + Sends a DELETE request to the API. Returns the response object. + """ if header is None: header = {} - header.update(self._build_header(self.username, self.password, self.apikey)) + header.update(self._build_header(self.username, self.password, + self.apikey)) endpoint = self._build_endpoint(self.env, module, path, version) try: - r = requests.delete(endpoint, params=params, headers=header, timeout=timeout) + r = requests.delete(endpoint, params=params, headers=header, + timeout=timeout) r = self._check_for_error(r) return r except AirWatchAPIError as e: @@ -117,8 +153,12 @@ def delete(self, module, path, version=None, params=None, header=None, timeout=3 @staticmethod def _check_for_error(response): - """Checks the response for json data, then for an error, then for a status code""" - if response.headers.get('Content-Type') in ('application/json', 'application/json; charset=utf-8'): + """ + Checks the response for json data, then for an error, then for + a status code + """ + if response.headers.get('Content-Type') in ('application/json', + 'application/json; charset=utf-8'): json = response.json() if json.get('errorCode'): raise AirWatchAPIError(json_response=json) @@ -129,7 +169,9 @@ def _check_for_error(response): @staticmethod def _build_endpoint(base_url, module, path=None, version=None): - """Builds the full url endpoint for the API request""" + """ + Builds the full url endpoint for the API request + """ if not base_url.startswith('https://'): base_url = 'https://' + base_url if base_url.endswith('/'): @@ -145,14 +187,16 @@ def _build_endpoint(base_url, module, path=None, version=None): return url + '/{}'.format(path) return url - @staticmethod def _build_header(username, password, token, accept='application/json'): - """Build the header with base64 login, AW API token, and accept a json response""" + """ + Build the header with base64 login, AW API token, + and accept a json response + """ hashed_auth = base64.b64encode((username + ':' + password).encode('utf8')).decode("utf-8") header = { - 'Authorization': 'Basic {}'.format(hashed_auth), - 'aw-tenant-code': token, - 'Accept': accept - } + 'Authorization': 'Basic {}'.format(hashed_auth), + 'aw-tenant-code': token, + 'Accept': accept + } return header diff --git a/pyairwatch/error.py b/pyairwatch/error.py new file mode 100644 index 0000000..84c1f36 --- /dev/null +++ b/pyairwatch/error.py @@ -0,0 +1,14 @@ +class AirWatchAPIError(Exception): + def __init__(self, json_response=None): + if json_response is None: + pass + else: + self.response = json_response + self.error_code = json_response.get('errorCode') + self.error_msg = str(json_response.get('message')) + if self.error_code is None: + self.error_code = 0 + self.error_msg = 'Unknown API error occurred' + + def __str__(self): + return 'Error #{}: {}'.format(self.error_code, self.error_msg) diff --git a/pyairwatch/mam/__init__.py b/pyairwatch/mam/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pyairwatch/mam/application.py b/pyairwatch/mam/application.py new file mode 100644 index 0000000..e294ffe --- /dev/null +++ b/pyairwatch/mam/application.py @@ -0,0 +1,22 @@ +from .mam import MAM + + +class Application(MAM): + """ + A class to manage Internal Applications + """ + + def __init__(self, client): + MAM.__init__(self, client) + + def get_app_assignment(self, application_uuid): + path = '/apps/{}/assignment-rule'.format(application_uuid) + return MAM._get(self, path=path) + + def create_app_assignment(self, application_uuid, assignment_data, action): + path = '/apps/{}/assignment-rule'.format(application_uuid) + return MAM._post(self, path=path, json=assignment_data) + + def update_app_assignment(self, application_uuid, assignment_data): + path = '/apps/{}/assignment-rule'.format(application_uuid) + return MAM._put(self, path=path, json=assignment_data) \ No newline at end of file diff --git a/pyairwatch/mam/blobs.py b/pyairwatch/mam/blobs.py new file mode 100644 index 0000000..ea153cc --- /dev/null +++ b/pyairwatch/mam/blobs.py @@ -0,0 +1,10 @@ +from .mam import MAM + + +class Blobs(MAM): + """ + A class to manage Blobs + """ + + def __init__(self, client): + MAM.__init__(self, client) diff --git a/pyairwatch/mam/internalapps.py b/pyairwatch/mam/internalapps.py new file mode 100644 index 0000000..16b2f52 --- /dev/null +++ b/pyairwatch/mam/internalapps.py @@ -0,0 +1,10 @@ +from .mam import MAM + + +class InternalApps(MAM): + """ + A class to manage Internal Applications + """ + + def __init__(self, client): + MAM.__init__(self, client) diff --git a/pyairwatch/mam/mam.py b/pyairwatch/mam/mam.py new file mode 100644 index 0000000..f17c6d3 --- /dev/null +++ b/pyairwatch/mam/mam.py @@ -0,0 +1,37 @@ +class MAM(object): + """ + Application management + """ + + def __init__(self, client): + self.client = client + + def _get(self, path=None, version=None, params=None, + header=None): + """GET requests for base mam endpoints""" + return self.client.get(module='mam', path=path, + version=version, params=params, header=header) + + def _post(self, path=None, version=None, params=None, + data=None, json=None, header=None): + """POST requests for base mam endpoints""" + return self.client.post(module='mam', path=path, version=version, + params=params, data=data, + json=json, header=header) + + def _put(self, path=None, version=None, params=None, + data=None, json=None, header=None): + """PUT requests for base mam endpoints""" + return self.client.put(module='mam', path=path, version=version, + params=params, data=data, + json=json, header=header) + + def _patch(self, path=None, version=None, params=None, + data=None, json=None, header=None): + """Patch requests for base mam endpoints""" + return self.client.patch(module='mam', path=path, version=version, + params=params, data=data, + json=json, header=header) + + def _delete(self, path=None, version=None, params=None, header=None): + return self.client.delete(module='mam', path=path, version=version, params=params, header=header) \ No newline at end of file diff --git a/pyairwatch/mam/vpp.py b/pyairwatch/mam/vpp.py new file mode 100644 index 0000000..4a0569a --- /dev/null +++ b/pyairwatch/mam/vpp.py @@ -0,0 +1,26 @@ +from .mam import MAM + + +class VPP(MAM): + """ + A class to manage Internal Applications + """ + + def __init__(self, client): + MAM.__init__(self, client) + + def get_vpp_details(self, application_id): + path = '/apps/purchased/{}'.format(application_id) + header = {'Content-Type': 'application/json;version=2'} + return MAM._get(self, path=path, header=header , version=2) + + def search(self, **kwargs): + """ + Search for VPP application details, its assignments, and deployment parameters. + :param kwargs: + :return: + """ + return MAM._get(self, path='/apps/purchased/search', params=kwargs) + + # def search_by_atl_id(self, search_by, value): + # return self.search(search_by, str(value)) diff --git a/pyairwatch/mdm/devices.py b/pyairwatch/mdm/devices.py index 0085715..3873ca9 100644 --- a/pyairwatch/mdm/devices.py +++ b/pyairwatch/mdm/devices.py @@ -1,24 +1,26 @@ -class Devices(object): +from .mdm import MDM + + +class Devices(MDM): """ A class to manage functionalities of Mobile Device Management (MDM). """ def __init__(self, client): - self.client = client + MDM.__init__(self, client) def search(self, **kwargs): """Returns the Device information matching the search parameters.""" - response = self._get(path='/devices', params=kwargs) - return response + return MDM._get(self, path='/devices', params=kwargs) def search_all(self, **kwargs): """Returns the Devices matching the search parameters.""" - response = self._get(path='/devices/search', params=kwargs) + response = MDM._get(self, path='/devices/search', params=kwargs) return response - def get_details_by_alt_id(self, serialnumber=None, macaddress=None, udid=None, imeinumber=None, easid=None): + def get_details_by_alt_id(self, serialnumber=None, macaddress=None, + udid=None, imeinumber=None, easid=None): """Returns the Device information matching the search parameters.""" - params = {} if serialnumber: response = self.search(searchby='Serialnumber', id=str(serialnumber)) elif macaddress: @@ -33,27 +35,96 @@ def get_details_by_alt_id(self, serialnumber=None, macaddress=None, udid=None, i return None return response - def get_id_by_alt_id(self, serialnumber=None, macaddress=None, udid=None, imeinumber=None, easid=None): - if serialnumber: - response = self.search(searchby='Serialnumber', id=str(serialnumber)) - elif macaddress: - response = self.search(searchby='Macaddress', id=str(macaddress)) - elif udid: - response = self.search(searchby='Udid', id=str(udid)) - elif imeinumber: - response = self.search(searchby='ImeiNumber', id=str(imeinumber)) - elif easid: - response = self.search(searchby='EasId', id=str(easid)) - else: - return None + def get_id_by_alt_id(self, serialnumber=None, macaddress=None, udid=None, + imeinumber=None, easid=None): + response = self.get_details_by_alt_id(serialnumber, macaddress, udid, imeinumber, easid) return response['Id']['Value'] - def _get(self, module='mdm', path=None, version=None, params=None, header=None): - """GET requests for the /MDM/Devices module.""" - response = self.client.get(module=module, path=path, version=version, params=params, header=header) - return response + def clear_device_passcode(self, device_id): + """ + Clear the passcode on a device + """ + return MDM._post(self, + path='/devices/{}/clearpasscode'.format(device_id)) - def _post(self, module='mdm', path=None, version=None, params=None, data=None, json=None, header=None): - """POST requests for the /MDM/Devices module.""" - response = self.client.post(module=module, path=path, version=version, params=params, data=data, json=json, header=header) - return response + def send_commands_for_device_id(self, command, device_id): + """ + Commands for devices selecting device based on id + """ + path = '/devices/{}/commands'.format(device_id) + command = 'command={}'.format(command) + return MDM._post(self, path=path, params=command) + + def send_commands_by_id(self, command, searchby, id): + """ + Commands for devices selecting device based on id + """ + _path = '/devices/commands' + _query = 'command={}&searchBy={}&id={}'.format(str(command), + str(searchby), + str(id)) + return MDM._post(self, path=_path, params=_query) + + def get_details_by_device_id(self, device_id): + """ + device details by device id + """ + return MDM._get(self, path='/devices/{}'.format(device_id)) + + def get_device_filevault_recovery_key(self, device_uuid): + """ + Gets a macOS device's FileVault Recovery Key + """ + _path = '/devices/{}/security/recovery-key'.format(device_uuid) + return MDM._get(self, path=_path) + + def get_security_info_by_id(self, device_id): + """ + Processes the device ID to retrieve the security + information sample related info + """ + _path = '/devices/{}/security'.format(device_id) + return MDM._get(self, path=_path) + + def get_security_info_by_alternate_id(self, searchby, id): + """ + Processes the device ID to retrieve the security + information sample related info by Alternate ID + """ + _path = '/devices/security' + _params = 'searchby={}&id={}'.format(searchby, id) + return MDM._get(self, path=_path, params=_params) + + def get_bulk_security_info(self, organization_group_id, user_name, + params=None): + """ + Processes the information like organization group ID, user name, model, + platform, last seen, ownership, compliant status, seen since parameters + and fetches the security information for the same. + """ + _path = '/devices/securityinfosearch' + _query = 'organizationgroupid={}&user={}'.format(organization_group_id, + user_name) + return MDM._get(self, path=_path, params=_query) + + def switch_device_from_staging_to_user(self, device_id, user_id): + """ + API for Single Staging switch to directory or basic user + """ + _path = "/devices/{}/enrollmentuser/{}".format(device_id, user_id) + return MDM._patch(self, path=_path) + + def get_managed_admin_account_by_uuid(self, device_id): + """ + Get information of the administrator account configured on a macOS + device via device enrollment program (DEP). + """ + _path = "/devices/{}/security/managed-admin-information".format(device_id) + return MDM._get(self, path=_path) + + def delete_device_by_id(self, device_id): + """ + :param device_id: + :return: API response + """ + return MDM._delete(self, path='/devices/{}'.format(device_id)) diff --git a/pyairwatch/mdm/ldap.py b/pyairwatch/mdm/ldap.py new file mode 100644 index 0000000..3b37060 --- /dev/null +++ b/pyairwatch/mdm/ldap.py @@ -0,0 +1,14 @@ +from .mdm import MDM + + +class LDAP(MDM): + """ + A class to manage functionalities of LDAP Definition. + """ + def __init__(self, client): + MDM.__init__(self, client) + self.jheader = {'Content-Type': 'application/json'} + + def create_ldap(self, ldap_data): + path = '/enterpriseintegration/ldap' + return MDM._post(self, path=path, json=ldap_data, header=self.jheader) diff --git a/pyairwatch/mdm/mdm.py b/pyairwatch/mdm/mdm.py new file mode 100644 index 0000000..3d7bd3f --- /dev/null +++ b/pyairwatch/mdm/mdm.py @@ -0,0 +1,36 @@ +class MDM(object): + """ + Base MDM class + """ + def __init__(self, client): + self.client = client + + def _get(self, module='mdm', path=None, version=None, params=None, + header=None): + """GET requests for base mdm endpoints""" + return self.client.get(module=module, path=path, + version=version, params=params, header=header) + + def _post(self, module='mdm', path=None, version=None, params=None, + data=None, json=None, header=None): + """POST requests for base mdm endpoints""" + return self.client.post(module=module, path=path, version=version, + params=params, data=data, + json=json, header=header) + + def _put(self, module='mdm', path=None, version=None, params=None, + data=None, json=None, header=None): + """PUT requests for base mdm endpoints""" + return self.client.put(module=module, path=path, version=version, + params=params, data=data, + json=json, header=header) + + def _patch(self, module='mdm', path=None, version=None, params=None, + data=None, json=None, header=None): + """Patch requests for base mdm endpoints""" + return self.client.patch(module=module, path=path, version=version, + params=params, data=data, + json=json, header=header) + + def _delete(self, module='MDM', path=None, version=None, params=None, header=None): + return self.client.delete(module=module, path=path, version=version, params=params, header=header) diff --git a/pyairwatch/mdm/network.py b/pyairwatch/mdm/network.py new file mode 100644 index 0000000..ad80eab --- /dev/null +++ b/pyairwatch/mdm/network.py @@ -0,0 +1,14 @@ +from .mdm import MDM + + +class Network(MDM): + """ + Get network sample information + version 1 + """ + def __init__(self, client): + MDM.__init__(self, client) + + def get_network_by_device_id(self, device_id): + """get network sample information based with Device ID""" + return MDM._get(self, path='/devices/{}/network'.format(device_id)) diff --git a/pyairwatch/mdm/profiles.py b/pyairwatch/mdm/profiles.py index e2e2d08..38a0a9f 100644 --- a/pyairwatch/mdm/profiles.py +++ b/pyairwatch/mdm/profiles.py @@ -1,10 +1,13 @@ -class Profiles(object): +from .mdm import MDM + + +class Profiles(MDM): """ A class to manage V2 API's for AirWatch Profiles Management """ def __init__(self, client): - self.client = client + MDM.__init__(self, client) def search(self, **kwargs): """ @@ -20,15 +23,20 @@ def search(self, **kwargs): status={status} ownership={ownership} """ - response = self._get(path='/profiles/search', params=kwargs) - return response - - def _get(self, module='mdm', path=None, version=None, params=None, header=None): - """GET requests for the /MDM/Profiles module.""" - response = self.client.get(module=module, path=path, version=version, params=params, header=header) - return response - - def _post(self, module='mdm', path=None, version=None, params=None, data=None, json=None, header=None): - """POST requests for the /MDM/Profiles module.""" - response = self.client.post(module=module, path=path, version=version, params=params, data=data, json=json, header=header) - return response + return MDM._get(self, path='/profiles/search', params=kwargs) + + def install_profile(self, device_id, profile_id, payloads=None): + """ + Queues up installation commands for interactive + profiles for a device by overriding payload settings. + """ + path = '/devices/{}/commands/installprofile'.format(device_id) + query = 'profileid=' + str(profile_id) + return MDM._post(self, path=path, params=query) + + def get_profile_by_id(self, profile_id): + return MDM._get(self, path='/profiles/{}'.format(profile_id), version=2) + + def get_payload_keys(self, platform, payload): + path = '/profiles/platforms/{}/payloads/{}/getpayloadkeys'.format(platform, payload) + return MDM._get(self, path=path, version=2) diff --git a/pyairwatch/mdm/scheduleosupdate.py b/pyairwatch/mdm/scheduleosupdate.py new file mode 100644 index 0000000..4c064db --- /dev/null +++ b/pyairwatch/mdm/scheduleosupdate.py @@ -0,0 +1,28 @@ +from .mdm import MDM + + +class ScheduleOSUpdate(MDM): + """ + For Schedule OS Update + """ + + def __init__(self, client): + MDM.__init__(self, client) + self.jheader = {'Content-Type': 'application/json'} + + def update_device(self, os_update_product_keys, install_action, + serialnumber=None, macaddress=None, udid=None, + imeinumber=None, easid=None): + """ + Needs work + """ + path = '/devices/commands/scheduleosupdate' + params = {} + if serialnumber: + params['searchby'] = 'Serialnumber' + params['id'] = str(serialnumber) + else: + return None + params['installaction'] = 'default' + body = os_update_product_keys + return MDM._get(path=path, params=params,) diff --git a/pyairwatch/mdm/smartgroups.py b/pyairwatch/mdm/smartgroups.py index 0ec3b07..bc92080 100644 --- a/pyairwatch/mdm/smartgroups.py +++ b/pyairwatch/mdm/smartgroups.py @@ -1,10 +1,13 @@ -class SmartGroups(object): +from .mdm import MDM + + +class SmartGroups(MDM): """ A class to manage AirWatch Smart Groups. """ def __init__(self, client): - self.client = client + MDM.__init__(self, client) def search(self, **kwargs): """ @@ -17,22 +20,23 @@ def search(self, **kwargs): organizationgroupid={organizationgroupid} managedbyorganizationgroupid={managedbyorganizationgroupid} """ - response = self._get(path='/smartgroups/search', params=kwargs) + response = MDM._get(self, path='/smartgroups/search', params=kwargs) return response def get_details(self, id): """Retrieves the Smart Group details created in an Organization Group""" - response = self._get(path='/smartgroups/{}'.format(id)) + response = MDM._get(self, path='/smartgroups/{}'.format(id)) return response def get_devices(self, id): """Retrieves all devices from Smart Group""" - devices = self._get(path='/smartgroups/{}/devices'.format(id)) + devices = MDM._get(self, path='/smartgroups/{}/devices'.format(id)) return devices def get_id_from_og_id(self, og_id, sg_name): """Returns the Smart Group ID for a given SG Name & OG ID""" - response = self.search(managedbyorganizationgroupid=str(og_id), orderby='smartgroupid') + response = self.search(managedbyorganizationgroupid=str(og_id), + orderby='smartgroupid') for keys in response['SmartGroups']: if keys['Name'] == sg_name: sg_id = keys.get('SmartGroupID') @@ -40,34 +44,18 @@ def get_id_from_og_id(self, og_id, sg_name): def move_device_to_sg(self, sg_id, device_id, device_name): """Move Device to a Smart Group by Device ID""" - # sg_details = self.get_details(sg_id) - # print type(sg_details) sg_details = {} - sg_details[u'DeviceAdditions'] = [{u'Id': str(device_id).decode(), u'Name': str(device_name).decode()}] + sg_details[u'DeviceAdditions'] = [{u'Id': str(device_id).decode(), + u'Name': str(device_name).decode()}] print(sg_details) - # device = {'DeviceAdditions':[{ 'Id':'{}'.format(device_id)}]} - response = self._post(path='/smartgroups/{}/update'.format(str(sg_id)), data=sg_details) + path = '/smartgroups/{}/update'.format(str(sg_id)) + response = MDM._post(self, path=path, data=sg_details) d = self.get_details(sg_id) print(d) return response - def _get(self, module='mdm', path=None, version=None, params=None, header=None): - """GET requests for the /MDM/SmartGroups module.""" - response = self.client.get(module=module, path=path, version=version, params=params, header=header) - return response - - def _post(self, module='mdm', path=None, version=None, params=None, data=None, json=None, header=None): - """POST requests for the /MDM/SmartGroups module.""" - response = self.client.post(module=module, path=path, version=version, params=params, data=data, json=json, header=header) - return response - - def _put(self, module='mdm', path=None, version=None, params=None, data=None, json=None, header=None): - """PUT requests for the /MDM/SmartGroups module.""" - response = self.client.put(module=module, path=path, version=version, params=params, data=data, json=json, header=header) - return response - # Inconsistent behaviors during testing, commenting out these methods for now: # # def remove_device_from_sg(self, sg_id, device_id): diff --git a/pyairwatch/mdm/tags.py b/pyairwatch/mdm/tags.py index 6c60d1e..44b7ee3 100644 --- a/pyairwatch/mdm/tags.py +++ b/pyairwatch/mdm/tags.py @@ -1,20 +1,14 @@ -class Tags(object): +from .mdm import MDM + + +class Tags(MDM): """A class to manage various AirWatch device tag functionalities""" def __init__(self, client): - self.client = client + MDM.__init__(self, client) def get_id_by_name(self, name, og_id): - # mdm/tags/search?name={name} - response = self._get(path='/tags/search', params={'name':str(name), 'organizationgroupid':str(og_id)}) - return response - - def _get(self, module='mdm', path=None, version=None, params=None, header=None): - """GET requests for the /MDM/Tags module.""" - response = self.client.get(module=module, path=path, version=version, params=params, header=header) - return response - - def _post(self, module='mdm', path=None, version=None, params=None, data=None, json=None, header=None): - """POST requests for the /MDM/Tags module.""" - response = self.client.post(module=module, path=path, version=version, params=params, data=data, json=json, header=header) + """mdm/tags/search?name={name}""" + _params = {'name': str(name), 'organizationgroupid': str(og_id)} + response = MDM._get(self, path='/tags/search', params=_params) return response diff --git a/pyairwatch/system/admins.py b/pyairwatch/system/admins.py index 66d9c72..8b93c05 100644 --- a/pyairwatch/system/admins.py +++ b/pyairwatch/system/admins.py @@ -1,7 +1,10 @@ -class Admins(object): +from .system import System + + +class Admins(System): def __init__(self, client): - self.client = client + System.__init__(self, client) def search(self, **kwargs): """ @@ -17,15 +20,13 @@ def search(self, **kwargs): organizationgroupid={locationgroupid} role={role} """ - response = self._get(path='/admins/search', params=kwargs) - return response - - def _get(self, module='system', path=None, version=None, params=None, header=None): - """GET requests for the /System/Admins module.""" - response = self.client.get(module=module, path=path, version=version, params=params, header=header) + response = System._get(self, path='/admins/search', params=kwargs) return response - def _post(self, module='system', path=None, version=None, params=None, data=None, json=None, header=None): - """POST requests for the /System/Admins module.""" - response = self.client.post(module=module, path=path, version=version, params=params, data=data, json=json, header=header) + def create_admin_v1(self, user_data): + """ + Performs necessary checks and Create a new basic Admin user. + """ + path = '/admins/addadminuser' + response = System._post(self, path=path, data=user_data) return response diff --git a/pyairwatch/system/featureflag.py b/pyairwatch/system/featureflag.py new file mode 100644 index 0000000..6a1ab96 --- /dev/null +++ b/pyairwatch/system/featureflag.py @@ -0,0 +1,24 @@ +from .system import System + + +class FeatureFlag(System): + """ + For Feature Flags + """ + + def __init__(self, client): + System.__init__(self, client) + + def set_feature_flag(self, feature_flag, og_uuid, override): + """Set the Feature Flag""" + _path = '/featureFlag/{}/{}/{}'.format(feature_flag, og_uuid, override) + return System._post(self, path=_path) + + def get_feature_flag_status(self, feature_flag, og_uuid): + """GET a specific Feature Flag status""" + _path = '/featureFlag/{}/{}'.format(feature_flag, og_uuid) + return System._get(self, path=_path) + + def list_feature_flags_by_og(self, og_uuid): + """GET all Feature Flags for a particular OG need UUID""" + return System._get(self, path='/featureFlag/{}'.format(og_uuid)) diff --git a/pyairwatch/system/groups.py b/pyairwatch/system/groups.py index 868d775..ec01566 100644 --- a/pyairwatch/system/groups.py +++ b/pyairwatch/system/groups.py @@ -1,15 +1,19 @@ -class Groups(object): +from .system import System +import json + + +class Groups(System): """ A class to manage all core functionalities for AirWatch Organization Groups. """ jheader = {'Content-Type': 'application/json'} def __init__(self, client): - self.client = client + System.__init__(self, client) def search(self, **kwargs): """Returns the Groups matching the search parameters.""" - response = self._get(path='/groups/search', params=kwargs) + response = System._get(self, path='/groups/search', params=kwargs) return response def get_id_from_groupid(self, groupid): @@ -17,14 +21,27 @@ def get_id_from_groupid(self, groupid): response = self.search(groupid=str(groupid)) return response['LocationGroups'][0]['Id']['Value'] + def get_groupid_from_id(self, id): + """Returns the Group ID for a given ID""" + response = System._get(self, path='/groups/{}'.format(id)) + return response['GroupId'] + + def get_uuid_from_groupid(self, id): + """Returns the OG UUID for a given Group ID""" + response = System._get(self, path='/groups/{}'.format(id)) + return response['Uuid'] + def create(self, parent_id, ogdata): """Creates a Group and returns the new ID.""" - response = self._post(path='/groups/{}'.format(parent_id), data=ogdata, header=self.jheader) + response = System._post(self, path='/groups/{}'.format(parent_id), + data=ogdata, header=self.jheader) return response def create_customer_og(self, groupid, name=None): - """Creates a Customer type OG, with a given Group ID and Name, and returns the new ID""" - import json + """ + Creates a Customer type OG, with a given Group ID and Name, and returns + the new ID + """ new_og = {'GroupId': str(groupid), 'Name': str(name), 'LocationGroupType': 'Customer'} @@ -34,8 +51,10 @@ def create_customer_og(self, groupid, name=None): return response.get('Value') def create_child_og(self, parent_groupid, groupid, og_type=None, name=None): - """Creates a Child OG for a given Parent Group ID, with a given Type, Group ID, and Name, and returns the new ID""" - import json + """ + Creates a Child OG for a given Parent Group ID, with a given Type, + Group ID, and Name, and returns the new ID + """ pid = self.get_id_from_groupid(parent_groupid) new_og = {'GroupId': str(groupid), 'Name': str(name), @@ -46,13 +65,3 @@ def create_child_og(self, parent_groupid, groupid, og_type=None, name=None): new_og['LocationGroupType'] = 'Container' response = self.create(parent_id=pid, ogdata=json.dumps(new_og)) return response.get('Value') - - def _get(self, module='system', path=None, version=None, params=None, header=None): - """GET requests for the /System/Groups module.""" - response = self.client.get(module=module, path=path, version=version, params=params, header=header) - return response - - def _post(self, module='system', path=None, version=None, params=None, data=None, json=None, header=None): - """POST requests for the /System/Groups module.""" - response = self.client.post(module=module, path=path, version=version, params=params, data=data, json=json, header=header) - return response diff --git a/pyairwatch/system/info.py b/pyairwatch/system/info.py new file mode 100644 index 0000000..eec6ab3 --- /dev/null +++ b/pyairwatch/system/info.py @@ -0,0 +1,13 @@ +from .system import System + + +class Info(System): + """ + Get environment information + """ + def __init__(self, client): + System.__init__(self, client) + + def get_environment_info(self): + """get environment information""" + return System._get(self, path='/info') diff --git a/pyairwatch/system/system.py b/pyairwatch/system/system.py new file mode 100644 index 0000000..7ccc160 --- /dev/null +++ b/pyairwatch/system/system.py @@ -0,0 +1,37 @@ +class System(object): + """ + Base System class + """ + def __init__(self, client): + self.client = client + + def _get(self, module='system', path=None, + version=None, params=None, header=None): + """ + GET requests for base system endpoints + """ + return self.client.get(module=module, path=path, + version=version, params=params, header=header) + + def _post(self, module='system', path=None, + version=None, params=None, data=None, json=None, header=None): + """POST requests""" + return self.client.post(module=module, path=path, version=version, + params=params, data=data, + json=json, header=header) + + def _post_no_error_check(self, module='system', path=None, + version=None, params=None, data=None, + json=None, header=None): + """POST requests with no error check when none json is returned""" + return self.client.post_no_error_check(module=module, path=path, + version=version, params=params, + data=data, + json=json, header=header) + + def _put(self, module='system', path=None, + version=None, params=None, data=None, json=None, header=None): + """PUT requests""" + return self.client.put(module=module, path=path, version=version, + params=params, data=data, + json=json, header=header) diff --git a/pyairwatch/system/usergroups.py b/pyairwatch/system/usergroups.py index 60acb4e..6c45993 100644 --- a/pyairwatch/system/usergroups.py +++ b/pyairwatch/system/usergroups.py @@ -1,28 +1,21 @@ -class UserGroups(object): +from .system import System + + +class UserGroups(System): """ A class to manage all core functionalities for AirWatch Organization Groups. """ - jheader = {'Content-Type': 'application/json'} def __init__(self, client): - self.client = client + System.__init__(self, client) def search(self, **kwargs): """Returns the Users Groups matching the search parameters.""" - response = self._get(path='/usergroups/search', params=kwargs) + response = System._get(self, path='/usergroups/search', params=kwargs) return response def search_users(self, id, **kwargs): """Retrieves list of users from the provided user group id.""" - response = self._get(path='/usergroups/{}/users'.format(id), params=kwargs) - return response - - def _get(self, module='system', path=None, version=None, params=None, header=None): - """GET requests for the /System/UsersGroups module.""" - response = self.client.get(module=module, path=path, version=version, params=params, header=header) - return response - - def _post(self, module='system', path=None, version=None, params=None, data=None, json=None, header=None): - """POST requests for the /System/UsersGroups module.""" - response = self.client.post(module=module, path=path, version=version, params=params, data=data, json=json, header=header) + _path = '/usergroups/{}/users'.format(id) + response = System._get(self, path=_path, params=kwargs) return response diff --git a/pyairwatch/system/users.py b/pyairwatch/system/users.py index f72f191..a84dd53 100644 --- a/pyairwatch/system/users.py +++ b/pyairwatch/system/users.py @@ -1,9 +1,12 @@ -class Users(object): +from .system import System + + +class Users(System): def __init__(self, client): - self.client = client + System.__init__(self, client) - #UNTESTED + # UNTESTED def search(self, **kwargs): """ Returns the Enrollment User's details matching the search parameters @@ -18,15 +21,12 @@ def search(self, **kwargs): organizationgroupid={locationgroupid} role={role} """ - response = self._get(path='/users/search', params=kwargs) - return response + return System._get(self, path='/users/search', params=kwargs) - def _get(self, module='system', path=None, version=None, params=None, header=None): - """GET requests for the /System/Users module.""" - response = self.client.get(module=module, path=path, version=version, params=params, header=header) - return response - - def _post(self, module='system', path=None, version=None, params=None, data=None, json=None, header=None): - """POST requests for the /System/Users module.""" - response = self.client.post(module=module, path=path, version=version, params=params, data=data, json=json, header=header) + def create_device_registration_to_user(self, user_id, register_device_details): + """ + Creates a registration record for a user with the provided device details + """ + path = '/users{}/registerdevice'.format(user_id) + response = System._post(path=path, data=register_device_details) return response diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..26d8f57 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +setuptools +requests \ No newline at end of file diff --git a/setup.py b/setup.py index 849ab8b..bd48f67 100644 --- a/setup.py +++ b/setup.py @@ -1,11 +1,14 @@ from setuptools import setup -setup(name='PyVMwareAirWatch', - version='1.0', - description='PyVMwareAirWatch is a Python API library for [VMware AirWatch](https://www.air-watch.com/) 9.1+', - url='https://github.com/jprichards/PyVMwareAirWatch', - author='jprichards', - author_email='jprichards@example.com', - license='MIT', - packages=['pyairwatch'], - zip_safe=False) +setup( + name='PyVMwareAirWatch', + version='1.0.3', + description=('PyVMwareAirWatch is a Python API library for ' + '[VMware AirWatch] (https://www.air-watch.com/) 9.1+'), + url='https://github.com/jprichards/PyVMwareAirWatch', + author='jprichards', + author_email='jprichards@example.com', + license='MIT', + packages=['pyairwatch'], + zip_safe=False +) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/environments_template.json b/tests/environments_template.json new file mode 100644 index 0000000..ed9a947 --- /dev/null +++ b/tests/environments_template.json @@ -0,0 +1,8 @@ +{ + "enviroment_name" : { + "server" : "apitest.awmdmd.com", + "apikey" : "apikey", + "username" : "admin_user", + "password" : "password" + } +} diff --git a/tests/testing_is_important.py b/tests/testing_is_important.py new file mode 100644 index 0000000..64ec4ee --- /dev/null +++ b/tests/testing_is_important.py @@ -0,0 +1,28 @@ +from pyairwatch.client import AirWatchAPI +import unittest +import json + +# convert to pytest + +class TestBasicTests(unittest.TestCase): + def setUp(self): + input = "cnaapp" + with open("environments.json") as json_file: + server_details = json.load(json_file) + self.environment = AirWatchAPI(env=server_details[input]["server"], + apikey=server_details[input]["apikey"], + username=server_details[input]["username"], + password=server_details[input]["password"]) + + def teardown(self): + print("TEAR DOWN!") + + def test_is_this_AirWatch(self): + response = self.environment.info.get_enviroment_info() + self.assertEqual(response["ProductName"], "AirWatch Platform Service", + "Info API ran") +# todo: more tests + + +if __name__ == '__main__': + unittest.main()