├── .DS_Store ├── .gitignore ├── README.md ├── __init__.py ├── examples ├── .DS_Store ├── GetLastKnownIP │ ├── getLastKnownIP.py │ └── getLastKnownIP.sh ├── README.md └── RedeployMDM │ ├── redeployMDM.py │ └── redeployMDM.sh ├── jamfAuth.py ├── requirements.txt ├── support ├── .jamfauth-dev.json ├── .jamfauth.json ├── __init__.py ├── configCheck.py └── getToken.py └── version_notes.md /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/therealmacjeezy/JamfAuth/157f1cdaacbe22334eddebc9a2e902407c6075c4/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .ipynb_checkpoints 2 | support/__init__.pyc 3 | .DS_Store -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JamfAuth 2 | 3 | A python script to authenticate with the Jamf Pro API. 4 | 5 | [jamfAuth Examples](examples/README.md) 6 | 7 | **Current Version:** v0.3.4 8 | 9 | > Starting with version 0.3.4, you now have the option to use `jamfAuth` with a development server. By default, `jamfAuth` will function as normal. If you want to use `jamfAuth` with your development server, you will need to add **"dev"** inside the `startAuth()` function (ie: `startAuth("dev")`). See the usage and example section for more details. 10 | 11 | ### Supported Operating Systems 12 | **Operating System** | **Version** | **Status** | **Notes** 13 | -------------------- | ---------- | --------- | ---------- 14 | **macOS Monterey** | macOS 12.x | ✅ *(Supported)* | None 15 | **Windows Server** | 2022 | ✅ *(Supported in 0.3.3+)* | None 16 | **RedHat Enterprise Linux (RHEL)** | 9 (Beta) | ✅ *(Supported in 0.3.3+)* | *Requires keychains.alt package* 17 | **RedHat Enterprise Linux (RHEL)** | 8 | ✅ *(Supported in 0.3.3+)* | *Requires keychains.alt package* 18 | **CentOS Stream** | 8 | ⚠️ *(Needs Testing)* | None 19 | **Oracle Linux** | 8 | ✅ *(Supported in 0.3.3+)* | *Requires keychains.alt package* 20 | **Ubuntu Server** | 20.04.4 LTS | ✅ *(Supported in 0.3.3+)* | *Requires keychains.alt package* 21 | 22 | --- 23 | ## Overview 24 | This python script handles the API Authentication to your Jamf Pro Server. Once you have a valid API Token, you can store it as a variable and use it when performing API calls later in the script. 25 | 26 | Here is how `jamfAuth.py` works: 27 | - Checks to see if the JSON Config file exists. 28 | - **✅ JSON Config Found:** Attempts to load the `apiUserName` and `jamfHostName` variables 29 | - **⚠️ JSON Config Not Found:** Creates an empty JSON Config file and prompts you for the following things: `Jamf Pro Host Name`, `API Username` 30 | - Once the above information is entered/loaded, it will check the local keychain for an API Token. 31 | - **✅ API Token Found:** Checks to see if the API Token stored is valid 32 | - **✅ Valid Token:** Returns the API Token for use 33 | - **⚠️ Invalid Token:** Attempts to renew the API Token (using `keep-alive`). If the API Token is unable to be renewed, it will check the local keychain for the **API Password** 34 | - **✅ API Password Keychain Found:** Uses the `API Password` to get a new API Token then saves it to the local keychain then returns it for use 35 | - **⚠️ API Password Keychain Not Found:** Prompts for the API Password, stores it in the local keychain and gets a new API Token then returns it for use 36 | - **⚠️ API Token Not Found:** Checks the local keychain for the `API Password` 37 | - **✅API Password Keychain Found:** Uses the `API Password` to get a new API Token then saves it to the local keychain then returns it for use 38 | - **⚠️ API Password Keychain Not Found:** Prompts for the API Password, stores it in the local keychain and gets a new API Token then returns it for use 39 | 40 | 41 | 42 | The `API Password` and `API Token` will be stored in the local keychain (using the [keyring](https://pypi.org/project/keyring/) python library) with the following naming convention: 43 | 44 | **Variable** | **Keychain Naming Convention** 45 | ---------------- | -------------- 46 | **API Password** | service = **JamfProHostName**, username = **API Username**, password = **API Password** 47 | **API Token** | service = **JamfProHostName**, username = **API Username**+API, password = **API Token** 48 | 49 | 50 | The `jamfAuth` JSON Configuration file is located in the `support` directory: 51 | 52 | **PRODUCTION** 53 | **Install Method** | **Configuration File Location** 54 | ------------------ | ------------------------------ 55 | **Github** | `/path/to/jamfAuth/support/.jamfauth.json` 56 | **pip** | `/path/to/pip/site-packages/jamfAuth/support/.jamfauth.json` 57 | 58 | **DEV** 59 | **Install Method** | **Command** 60 | ------------------ | ------------------------------ 61 | **Github** | `/path/to/jamfAuth/support/.jamfauth-dev.json` 62 | **pip** | `/path/to/pip/site-packages/jamfAuth/support/.jamfauth-dev.json` 63 | 64 | --- 65 | ### jamfAuth Options 66 | The `jamfAuth` script also has two options available for use to help make setup easier, these are `reset` and `setup`. Depending on how you installed `jamfAuth` will depend on how these two options can be called. 67 | 68 | #### Reset Option 69 | 70 | **PRODUCTION** 71 | **Install Method** | **Command** 72 | ------------------ | ------------------------------ 73 | **Github** | `python3 /path/to/jamfAuth.py reset` 74 | **pip** | `python3 -c 'from jamfAuth import *; reset_config()'` 75 | 76 | 77 | **DEV** 78 | **Install Method** | **Command** 79 | ------------------ | ------------------------------ 80 | **Github** | `python3 /path/to/jamfAuth.py reset-dev` 81 | **pip** | `python3 -c 'from jamfAuth import *; reset_config("dev")'` 82 | 83 | The `reset` option allows you to reset the JSON Configuration file that `jamfAuth` uses. The following items in the JSON Config file will be reset: 84 | - apiUserName 85 | - jamfHostName 86 | - jamfAPIURL 87 | 88 | After the `reset` option is ran, you will be prompted to enter the `Jamf Pro Host Name` and `API Username` on the next run. 89 | 90 | #### Setup Option 91 | 92 | **PRODUCTION** 93 | **Install Method** | **Command** 94 | ------------------ | ------------------------------ 95 | **Github** | `python3 /path/to/jamfAuth.py setup` 96 | **pip** | `python3 -c 'from jamfAuth import *; startAuth()'` 97 | 98 | **DEV** 99 | **Install Method** | **Command** 100 | ------------------ | ------------------------------ 101 | **Github** | `python3 /path/to/jamfAuth.py setup-dev` 102 | **pip** | `python3 -c 'from jamfAuth import *; startAuth("dev")'` 103 | 104 | 105 | The `setup` option allows you to setup the JSON Configuration file that `jamfAuth.py` uses. You can use this option if you would like to avoid being prompted to enter information. 106 | 107 | --- 108 | #### Delete Keychain 109 | 110 | Currently, deleting the keychain is a manual process. I plan on building this into `jamfAuth` to make it easier to do, but until then, use the command below to delete the keychain for your API account: 111 | 112 | `python3 -c 'import keyring; keyring.delete_password("https:///api/v1/", "")'` 113 | 114 | ---- 115 | ## To-Do List 116 | - [x] Save API Token in the keychain and remove it from the JSON config file 117 | - [x] Add usage examples 118 | - [x] Add additional error handling (if a 401 occurs.. etc..) 119 | - [x] Create pip install 120 | - [ ] Add option to delete the keychain entry (currently manual delete) 121 | - [x] Add additional OS support (linux, windows) 122 | - [x] Add support for a development server 123 | 124 | --- 125 | ## Installation 126 | There are two ways to install `jamfAuth`: **Github** or **pip**. 127 | 128 | ### pip Method *[Recommended]* 129 | This method will install `jamfAuth` **and** all of the required packages. Using this method will allow you to import and use `jamfAuth` without having to copy the `jamfAuth` directory into the project your going to use it with. 130 | 131 | - `pip3 install jamfAuth` 132 | - [PyPi URL](https://pypi.org/project/jamfAuth/) 133 | 134 | ### Github Method 135 | This method will clone the `jamfAuth` code to your system. When using this method, you will need to install the required Python3 packages manually. 136 | 137 | - `git clone https://github.com/therealmacjeezy/JamfAuth.git` 138 | 139 | --- 140 | ## Requirements 141 | ### Jamf Pro 142 | 143 | - A Jamf Pro account that has API Access 144 | 145 | ### Python 146 | **Python Version** | **Supported** 147 | ------------------ | ------------- 148 | 3.8.9 | ✅ *(Supported)* 149 | 3.9.x | ⚠️ *(Not Tested)* 150 | 3.10.x | ✅ *(Supported)* 151 | 152 | **Required Python Packages:** 153 | 154 | To install all of the required Python packages at once, use the `requirements.txt` file to install them using the command below: 155 | 156 | `pip3 install -r requirements.txt` 157 | 158 | **Individual Packages** 159 | - **requests** 160 | - `pip3 install requests` 161 | - **keyring** 162 | - `pip3 install keyring` 163 | - **keyrings.alt** *(Linux ONLY)* 164 | - `pip3 install keyrings.alt` 165 | 166 | --- 167 | ### Usage 168 | Once installed, you'll need to configure `jamfAuth` by using the **setup** option (see **Setup Option** section above). This will create the jamfAuth configuration file and the keychain entries. Once it's setup, you're ready to start playing with API Calls! 169 | 170 | To use `jamfAuth` with your script, import `jamfAuth` and set the `startAuth()` function to a variable to store the API Token. See the example below 171 | 172 | **Note:** 173 | > If you used the **Github** method to install `jamfAuth`, you will need to copy the `jamfAuth` directory into the root directory of the script you are going to be using it with. If you used the `pip` method, you can just import `jamfAuth` as normal. 174 | 175 | ``` 176 | from jamfAuth import * 177 | 178 | #### PRODUCTION SERVER EXAMPLE 179 | apiPassword = startAuth() 180 | 181 | #### DEVELOPMENT SERVER EXAMPLE 182 | apiPassword = startAuth("dev") 183 | 184 | if apiPassword: 185 | print('You can now use the apiToken variable to authenticate with your Jamf Pro API.') 186 | print(f'apiToken: \n{apiPassword}') 187 | ``` 188 | 189 | #### Examples 190 | I created a few example scripts in both `python` and `bash` to show how easy it is to use jamfAuth in your script. Check out the **examples** directory or view the [examples README.md](examples/README.md) to see them. -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/therealmacjeezy/JamfAuth/157f1cdaacbe22334eddebc9a2e902407c6075c4/__init__.py -------------------------------------------------------------------------------- /examples/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/therealmacjeezy/JamfAuth/157f1cdaacbe22334eddebc9a2e902407c6075c4/examples/.DS_Store -------------------------------------------------------------------------------- /examples/GetLastKnownIP/getLastKnownIP.py: -------------------------------------------------------------------------------- 1 | from jamfAuth import * 2 | import json, requests, subprocess 3 | 4 | def getSerialNumber(): 5 | system_profile_data = subprocess.Popen(['system_profiler', '-json', 'SPHardwareDataType'], stdout=subprocess.PIPE) 6 | data = json.loads(system_profile_data.stdout.read()) 7 | global serial_number 8 | serial_number = data.get('SPHardwareDataType', {})[0].get('serial_number') 9 | 10 | ### Use jamfAuth to start and handle the API Authentication and save it as the variable apiToken 11 | apiToken=startAuth() 12 | 13 | ### Base API URL for your Jamf Pro Server 14 | apiURL = 'https://benderisgreat.jamfcloud.com/api/v1/' 15 | 16 | ### Searches the GENERAL section of the computers-inventory and filters the results by Serial Number 17 | apiSearchFilter = f'computers-inventory?section=GENERAL&filter=hardware.serialNumber=={serial_number}' 18 | 19 | ### API Call Header.. This contains the API Token 20 | headers = {'accept': 'application/json', 'Authorization': f'Bearer {apiToken}'} 21 | 22 | apiResults = requests.get(apiURL+apiSearchFilter, headers=headers) 23 | 24 | if apiResults.status_code == 200: 25 | apiResultsJSON = apiResults.json() 26 | try: 27 | ### Get the Last Reported IP Address from the API Results 28 | lastReportedIP = apiResultsJSON['results'][0]['general']['lastReportedIp'] 29 | print(f'\nThis is the last known IP Address for the system with serial number {serial_number}:\n{lastReportedIP}') 30 | except: 31 | print(f'\nUnable to find the last known IP Address for the system with serial number {serial_number}') 32 | else: 33 | print('\noops.. something broke\n\t[apiResults.status_code]: {apiResults.status_code}') -------------------------------------------------------------------------------- /examples/GetLastKnownIP/getLastKnownIP.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ### Get the Serial Number of the System 4 | serialNumber=$(system_profiler SPHardwareDataType | grep Serial | awk '{print $4}') 5 | 6 | ### This function kicks off jamfAuth and captures the API Token saved in the keychain 7 | startAuth() { 8 | ### Use jamfAuth to start and handle the API Authentication 9 | python3 -c 'from jamfAuth import *; startAuth()' 10 | 11 | jamfHostname=$(grep -o '"jamfHostName": "[^"]*' /home/ubuntu/.local/lib/python3.8/site-packages/jamfAuth/support/.jamfauth.json | grep -o '[^"]*$') 12 | 13 | apiUsername=$(grep -o '"apiUserName": "[^"]*' /home/ubuntu/.local/lib/python3.8/site-packages/jamfAuth/support/.jamfauth.json | grep -o '[^"]*$') 14 | 15 | ### Use python3's keyring to get the API Token from the keychain 16 | apiToken=$(python3 -c 'import keyring; print(keyring.get_password("https://'$jamfHostname'/api/v1/", "'${apiUsername}API'"))') 17 | } 18 | ### Call the function 19 | startAuth 20 | 21 | ### Get the Last Known IP Address for the system 22 | lastknownIP=$(curl -s "https://$jamfHostname/api/v1/computers-inventory?section=GENERAL&filter=hardware.serialNumber==$serialNumber" -H "accept: application/json" -H "Authorization: Bearer $apiToken" | jq '.results[0].general|.lastReportedIp'| tr -d '"') 23 | 24 | if [[ "$lastknownIP" != 'null' ]]; then 25 | echo "This is the last known IP Address for the system with serial number $serialNumber:" 26 | echo "$lastknownIP" 27 | else 28 | echo "Unable to find the last known IP Address for the system with serial number $serialNumber" 29 | fi -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # jamfAuth Examples 2 | 3 | ## Usage Examples 4 | Below are four examples of how to use jamfAuth with your script. For complete example scripts, see the **Script Examples** section below. 5 | 6 | ### python [production server] 7 | ```python 8 | from jamfAuth import * 9 | 10 | ### Use jamfAuth to start and handle the API Authentication then save it as the variable "apiToken" 11 | apiToken=startAuth() 12 | 13 | #[...continue with script...] 14 | ``` 15 | 16 | ### python [development server] 17 | ```python 18 | from jamfAuth import * 19 | 20 | ### Use jamfAuth to start and handle the API Authentication then save it as the variable "apiToken" 21 | apiToken=startAuth("dev") 22 | 23 | #[...continue with script...] 24 | ``` 25 | 26 | ### bash [production server] 27 | ```shell 28 | #!/bin/bash 29 | 30 | ### This function kicks off jamfAuth and captures the API Token saved in the keychain 31 | startAuth() { 32 | ### Use jamfAuth to start and handle the API Authentication 33 | python3 -c 'from jamfAuth import *; startAuth()' 34 | 35 | jamfHostname=$(grep -o '"jamfHostName": "[^"]*' /path/to/jamfAuth/support/.jamfauth.json | grep -o '[^"]*$') 36 | 37 | apiUsername=$(grep -o '"apiUserName": "[^"]*' /path/to/jamfAuth/support/.jamfauth.json | grep -o '[^"]*$') 38 | 39 | ### Use python3's keyring to get the API Token from the keychain 40 | apiToken=$(python3 -c 'import keyring; print(keyring.get_password("https://'$jamfHostname'/api/v1/", "'${apiUsername}API'"))') 41 | } 42 | 43 | ### Call the function 44 | startAuth 45 | 46 | #[...continue with script...] 47 | ``` 48 | 49 | ### bash [development server] 50 | ```shell 51 | #!/bin/bash 52 | 53 | ### This function kicks off jamfAuth and captures the API Token saved in the keychain 54 | startAuth() { 55 | ### Use jamfAuth to start and handle the API Authentication 56 | python3 -c 'from jamfAuth import *; startAuth("dev")' 57 | 58 | jamfHostname=$(grep -o '"jamfHostName": "[^"]*' /path/to/jamfAuth/support/.jamfauth.json | grep -o '[^"]*$') 59 | 60 | apiUsername=$(grep -o '"apiUserName": "[^"]*' /path/to/jamfAuth/support/.jamfauth.json | grep -o '[^"]*$') 61 | 62 | ### Use python3's keyring to get the API Token from the keychain 63 | apiToken=$(python3 -c 'import keyring; print(keyring.get_password("https://'$jamfHostname'/api/v1/", "'${apiUsername}API'"))') 64 | } 65 | 66 | ### Call the function 67 | startAuth 68 | 69 | #[...continue with script...] 70 | ``` 71 | 72 | --- 73 | ## Script Examples 74 | I put together a few example scripts to show how jamfAuth can be used from start to finish. Each example script has been written in both bash and python. 75 | 76 | ### Get Last Known IP Address 77 | Uses the system's serial number to get the last known IP Address for that system. 78 | 79 | **Script Type** | **Name** 80 | --- | --- 81 | **python** | [`getLastKnownIP.py`](GetLastKnownIP/getLastKnownIP.py) 82 | **BASH** | [`getLastKnownIP.sh`](GetLastKnownIP/getLastKnownIP.sh) 83 | 84 | ---- 85 | ### Redeploy MDM Profile 86 | Uses the system's serial number to get the Jamf Computer ID then uses that to redeploy the MDM Profile. 87 | 88 | **Script Type** | **Name** 89 | --- | --- 90 | **python** | [`redeployMDM.py`](RedeployMDM/redeployMDM.py) 91 | **BASH** | [`redeployMDM.sh`](RedeployMDM/redeployMDM.sh) 92 | 93 | 94 | -------------------------------------------------------------------------------- /examples/RedeployMDM/redeployMDM.py: -------------------------------------------------------------------------------- 1 | from jamfAuth import * 2 | import json, requests, subprocess 3 | 4 | def getSerialNumber(): 5 | system_profile_data = subprocess.Popen(['system_profiler', '-json', 'SPHardwareDataType'], stdout=subprocess.PIPE) 6 | data = json.loads(system_profile_data.stdout.read()) 7 | global serial_number 8 | serial_number = data.get('SPHardwareDataType', {})[0].get('serial_number') 9 | 10 | ### Use jamfAuth to start and handle the API Authentication and save it as the variable apiToken 11 | apiToken=startAuth() 12 | 13 | ### Call the function to get the system's Serial Number 14 | getSerialNumber() 15 | 16 | ### Base API URL for your Jamf Pro Server 17 | apiURL = 'https://benderisgreat.jamfcloud.com/api/v1/' 18 | 19 | ### Searches the GENERAL section of the computers-inventory and filters the results by Serial Number 20 | apiSearchFilter = f'computers-inventory?section=GENERAL&filter=hardware.serialNumber=={serial_number}' 21 | 22 | ### API Call Header.. This contains the API Token 23 | headers = {'accept': 'application/json', 'Authorization': f'Bearer {apiToken}'} 24 | 25 | apiResults = requests.get(apiURL+apiSearchFilter, headers=headers) 26 | 27 | if apiResults.status_code == 200: 28 | apiResultsJSON = apiResults.json() 29 | try: 30 | ### Get the Last Reported IP Address from the API Results 31 | jamfComputerID = apiResultsJSON['results'][0]['id'] 32 | ### This kicks off the API Call to redeploy the MDM Profile 33 | mdmDeploy = requests.post(f'{apiURL}jamf-management-framework/redeploy/{jamfComputerID}', headers=headers) 34 | if mdmDeploy.status_code == 202: 35 | print(f'\nThe MDM Profile has been successfully deployed to the system with serial number {serial_number}.') 36 | if mdmDeploy.status_code == 404: 37 | print(f'\nUnable to find a computer with the Jamf Computer ID of {jamfComputerID}.') 38 | except: 39 | print(f'\nUnable to find the Jamf Computer ID for the system with serial number {serial_number}') 40 | else: 41 | print('\noops.. something broke\n\t[apiResults.status_code]: {apiResults.status_code}') -------------------------------------------------------------------------------- /examples/RedeployMDM/redeployMDM.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ### Get the Serial Number of the System 4 | serialNumber=$(system_profiler SPHardwareDataType | grep Serial | awk '{print $4}') 5 | 6 | ### This function kicks off jamfAuth and captures the API Token saved in the keychain 7 | startAuth() { 8 | ### Use jamfAuth to start and handle the API Authentication 9 | python3 -c 'from jamfAuth import *; startAuth()' 10 | 11 | jamfHostname=$(grep -o '"jamfHostName": "[^"]*' /home/ubuntu/.local/lib/python3.8/site-packages/jamfAuth/support/.jamfauth.json | grep -o '[^"]*$') 12 | 13 | apiUsername=$(grep -o '"apiUserName": "[^"]*' /home/ubuntu/.local/lib/python3.8/site-packages/jamfAuth/support/.jamfauth.json | grep -o '[^"]*$') 14 | 15 | ### Use python3's keyring to get the API Token from the keychain 16 | apiToken=$(python3 -c 'import keyring; print(keyring.get_password("https://'$jamfHostname'/api/v1/", "'${apiUsername}API'"))') 17 | } 18 | 19 | ### Call the function 20 | startAuth 21 | 22 | ### Get the Jamf Computer ID for this system using the serial number 23 | computerID=$(curl -s "https://$jamfHostName/api/v1/computers-inventory?section=HARDWARE&filter=hardware.serialNumber==$serialNumber" -H "accept: application/json" -H "Authorization: Bearer $apiToken" | jq '.results[0].id'| tr -d '"') 24 | 25 | if [[ ! -z "$computerID" ]]; then 26 | ### Redeploy the MDM Profile using the Jamf Computer ID 27 | redeployMDM=$(curl -s "https://$jamfHostName/api/v1/jamf-management-framework/redeploy/$computerID" -H "accept: application/json" -H "Authorization: Bearer $apiToken" -X POST) 28 | if [[ "$?" == "0" ]]; then 29 | echo "MDM Profile has been redeployed successfully to the system with serial number $serialNumber." 30 | fi 31 | else 32 | echo "Unable to get the Jamf Computer ID for system with serial number $serialNumber" 33 | fi -------------------------------------------------------------------------------- /jamfAuth.py: -------------------------------------------------------------------------------- 1 | from support.getToken import * 2 | from support.configCheck import * 3 | import sys 4 | 5 | def load_config(jamfSearchConfig): 6 | global data 7 | global apiUser 8 | global apiToken 9 | global baseAPIURL 10 | global theURL 11 | try: 12 | with open(jamfSearchConfig, 'r') as f: 13 | data = json.load(f) 14 | apiUser = data['apiUserName'] 15 | baseAPIURL = data['jamfAPIURL'] 16 | try: 17 | apiToken = keyring.get_password(baseAPIURL, apiUser+'API') 18 | print(f'[>jamfAuth] Loaded API Token') 19 | except Exception as errorMessage: 20 | print(f'[ERROR>jamfAuth] {errorMessage}') 21 | theURL = baseAPIURL+'auth' 22 | except Exception as errorMessage: 23 | print(f'ERROR load_config: Load Config] - {errorMessage}') 24 | 25 | def header(instance=""): 26 | pwd = os.getcwd() 27 | global jamfSearchConfig 28 | if instance == "dev": 29 | jamfSearchConfig = pwd+f'/support/.jamfauth.json' 30 | else: 31 | jamfSearchConfig = pwd+f'/support/.jamfauth-dev.json' 32 | 33 | authHeader = ''' _ __ _ _ _ 34 | (_) __ _ _ __ ___ / _| /_\ _ _| |_| |__ 35 | | |/ _` | '_ ` _ \| |_ //_\\\| | | | __| '_ \ 36 | | | (_| | | | | | | _/ _ \ |_| | |_| | | | 37 | _/ |\__,_|_| |_| |_|_| \_/ \_/\__,_|\__|_| |_| 38 | |__/ ------ jamfAuth.py (v0.3.4) [github] 39 | ----------- josh.harvey@jamf.com 40 | ----------- Created: 04/25/22 41 | ----------- Modified: 06/16/22 42 | ''' 43 | 44 | print(authHeader) 45 | print(f'> jamfAuth Config Path: \n{jamfSearchConfig}\n') 46 | 47 | 48 | def startAuth(instance=""): 49 | header(instance) 50 | pwd = os.getcwd() 51 | global jamfSearchConfig 52 | ## Adding support for a development server 53 | if instance == "dev": 54 | jamfSearchConfig = pwd+f'/support/.jamfauth.json' 55 | else: 56 | jamfSearchConfig = pwd+f'/support/.jamfauth-dev.json' 57 | 58 | #start config check 59 | check_config(jamfSearchConfig) 60 | start_config_check(jamfSearchConfig) 61 | load_config(jamfSearchConfig) 62 | check_token(apiUser, apiToken, theURL, baseAPIURL, jamfSearchConfig) 63 | load_config(jamfSearchConfig) 64 | return apiToken 65 | 66 | def main(): 67 | if len(sys.argv) > 1: 68 | if sys.argv[1] == 'reset': 69 | print('[>jamfAuth]: Resetting Settings for Production..') 70 | reset_config() 71 | if sys.argv[1] == 'reset-dev': 72 | print('[>jamfAuth]: Resetting Settings for Dev..') 73 | reset_config("dev") 74 | if sys.argv[1] == 'setup': 75 | print('[>jamfAuth]: Setting up Config for Production..') 76 | startAuth() 77 | if sys.argv[1] == 'setup-dev': 78 | print('[>jamfAuth]: Setting up Config for Dev..') 79 | startAuth("dev") 80 | else: 81 | print('no arg') 82 | startAuth() 83 | 84 | if __name__ == '__main__': 85 | main() 86 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests>=2.27.1 2 | keyring>=23.5.0 3 | keyrings.alt>=4.1.0; platform_system=="Linux" -------------------------------------------------------------------------------- /support/.jamfauth-dev.json: -------------------------------------------------------------------------------- 1 | {"apiUserName": "", "jamfHostName": "", "jamfAPIURL": ""} -------------------------------------------------------------------------------- /support/.jamfauth.json: -------------------------------------------------------------------------------- 1 | {"apiUserName": "", "jamfHostName": "", "jamfAPIURL": ""} -------------------------------------------------------------------------------- /support/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/therealmacjeezy/JamfAuth/157f1cdaacbe22334eddebc9a2e902407c6075c4/support/__init__.py -------------------------------------------------------------------------------- /support/configCheck.py: -------------------------------------------------------------------------------- 1 | import json, os 2 | 3 | def check_config(jamfSearchConfig): 4 | if os.path.exists(jamfSearchConfig) == False: 5 | print('..why no config? Lets make you one..') 6 | data = { 7 | 'apiUserName' : '', 8 | 'jamfHostName' : '', 9 | 'jamfAPIURL' : '' 10 | } 11 | 12 | with open(jamfSearchConfig, 'w') as output: 13 | json.dump(data, output) 14 | 15 | 16 | def reset_config(instance=""): 17 | pwd = os.getcwd() 18 | if instance == "dev": 19 | jamfSearchConfig = pwd+f'/support/.jamfauth.json' 20 | else: 21 | jamfSearchConfig = pwd+f'/support/.jamfauth-dev.json' 22 | 23 | data = { 24 | 'apiUserName' : '', 25 | 'jamfHostName' : '', 26 | 'jamfAPIURL' : '' 27 | } 28 | 29 | with open(jamfSearchConfig, 'w') as output: 30 | json.dump(data, output) 31 | 32 | 33 | def start_config_check(jamfSearchConfig): 34 | with open(jamfSearchConfig, 'r') as f: 35 | data = json.load(f) 36 | 37 | host_check(data['jamfHostName'], jamfSearchConfig, data['jamfAPIURL']) 38 | user_check(data['apiUserName'], jamfSearchConfig, data['jamfAPIURL']) 39 | 40 | def host_check(jamfHost, jamfSearchConfig, baseAPIURL): 41 | if jamfHost: 42 | print(f'[Jamf Pro Host Name]: {jamfHost}') 43 | else: 44 | jamfHost = input(f'Enter the Jamf Pro URL (without https://): \n\t=> ') 45 | try: 46 | with open(jamfSearchConfig, 'r') as f: 47 | data = json.load(f) 48 | 49 | with open(jamfSearchConfig, 'w') as d: 50 | data['jamfHostName'] = jamfHost 51 | data['jamfAPIURL'] = f"https://{jamfHost}/api/v1/" 52 | json.dump(data, d) 53 | 54 | except Exception as errorMessage: 55 | print(f'Unable to save Jamf Pro HostName to Local Config File..\n') 56 | 57 | if baseAPIURL: 58 | print(f'[Jamf Pro API URL]: {baseAPIURL}') 59 | else: 60 | try: 61 | with open(jamfSearchConfig, 'r') as f: 62 | data = json.load(f) 63 | 64 | with open(jamfSearchConfig, 'w') as d: 65 | data['jamfAPIURL'] = f"https://{jamfHost}/api/v1/" 66 | json.dump(data, d) 67 | 68 | except Exception as errorMessage: 69 | print(f'Unable to save Jamf Pro API URL to Local Config File..\n') 70 | 71 | def user_check(apiUser, jamfSearchConfig, baseAPIURL): 72 | if apiUser: 73 | print(f'[Jamf Pro API Username]: {apiUser}') 74 | else: 75 | apiUser = input(f'Enter the Username for API Access: \n\t=> ') 76 | 77 | try: 78 | with open(jamfSearchConfig, 'r') as f: 79 | data = json.load(f) 80 | 81 | with open(jamfSearchConfig, 'w') as d: 82 | data['apiUserName'] = apiUser 83 | json.dump(data, d) 84 | 85 | except Exception as errorMessage: 86 | print(f'Unable to save Jamf Pro API Username to Local Config File..\n') 87 | -------------------------------------------------------------------------------- /support/getToken.py: -------------------------------------------------------------------------------- 1 | import json, getpass, requests, keyring, sys 2 | 3 | def check_token(apiUser, apiToken, theURL, baseAPIURL, jamfSearchConfig): 4 | if apiToken: 5 | headers = {'accept': 'application/json', 'Authorization': f'Bearer {apiToken}'} 6 | apiResponse = requests.get(theURL, headers=headers) 7 | 8 | # print(f'status code: {apiResponse.status_code}') 9 | 10 | if apiResponse.status_code != 200: 11 | if apiResponse.status_code == 401: 12 | print('[>jamfAuth] Your token is invalid :(, attempting to renew it.. hold tight..') 13 | keep_token(apiUser, apiToken, theURL, baseAPIURL, jamfSearchConfig) 14 | else: 15 | print('[Jamf Pro API Token Status]: Valid') 16 | return apiToken 17 | else: 18 | get_token(apiUser, apiToken, theURL, baseAPIURL, jamfSearchConfig) 19 | 20 | 21 | def keep_token(apiUser, apiToken, theURL, baseAPIURL, jamfSearchConfig): 22 | theURL = baseAPIURL+'auth/keep-alive' 23 | headers = {'accept': 'application/json', 'Authorization': f'Bearer {apiToken}'} 24 | apiResponse = requests.post(theURL, headers=headers) 25 | if apiResponse.status_code != 200: 26 | if apiResponse.status_code == 401: 27 | print('[>jamfAuth] Your token is invalid and cannot be renewed. Why dont we get a new one..') 28 | keyring.delete_password(baseAPIURL, apiUser+'API') 29 | get_token(apiUser, apiToken, theURL, baseAPIURL, jamfSearchConfig) 30 | return apiToken 31 | else: 32 | print('[>jamfAuth] Renewed the API token successfully.') 33 | return apiToken 34 | 35 | def get_token(apiUser, apiToken, theURL, baseAPIURL, jamfSearchConfig): 36 | ## check for stored creds first 37 | try: 38 | apiPassword = keyring.get_password(baseAPIURL, apiUser) 39 | if not apiPassword: 40 | print('[>jamfAuth] Unable to find keychain entry. lets make one shall we?') 41 | apiPassword = getpass.getpass(f'What is the password for {apiUser}: ') 42 | keyring.set_password(baseAPIURL, apiUser, apiPassword) 43 | except Exception as errorMessage: 44 | print(f'get_token: error doing keychain things..\n{errorMessage}') 45 | theURL = baseAPIURL+'auth/token' 46 | headers = {'accept': 'application/json'} 47 | 48 | apiResponse = requests.post(theURL,auth=(apiUser,apiPassword), headers=headers) 49 | 50 | if apiResponse.status_code != 200: 51 | # Bad Request.. what were you thinking?! 52 | if apiResponse.status_code == 400: 53 | print(f'[>jamfAuth] Bad Request. Check the header and try again]') 54 | sys.exit('Bad Request.. oops.') 55 | 56 | # Unauthorized :( 57 | if apiResponse.status_code == 401: 58 | print('[>jamfAuth] Unauthorized. Lets try entering the password again.') 59 | # delete the keychain entry and create it again.. 60 | keyring.delete_password(baseAPIURL, apiUser) 61 | apiPassword = getpass.getpass(f'What is the password for {apiUser}: ') 62 | keyring.set_password(baseAPIURL, apiUser, apiPassword) 63 | try: 64 | print('[>jamfAuth] Trying to get an API Token again..') 65 | apiResponse = requests.post(theURL,auth=(apiUser,apiPassword), headers=headers) 66 | except Exception as errorMessage: 67 | print(f'ERROR [>jamfAuth] Unable to get an API Token for {apiUser}. Make sure the password and permissions are correct for {apiUser}, then try again.') 68 | sys.exit(f'Unable to get API Token for {apiUser}') 69 | 70 | # Forbidden 0_o ..oops 71 | if apiResponse.status_code == 403: 72 | print(f'[>jamfAuth] Forbidden. Make sure the user {apiUser} has the correct access to perform API Authentication calls.') 73 | sys.exit(f'Forbidden. Double check the permissions for {apiUser}.') 74 | 75 | # Not Found ..yup.. totally lost right now. 76 | if apiResponse.status_code == 404: 77 | print('[>jamfAuth] Not Found. Check the Jamf Pro Hostname and try again.') 78 | sys.exit('Not Found.. 1000 percent lost.') 79 | 80 | try: 81 | apiResponseJSON = apiResponse.json() 82 | try: 83 | apiToken = apiResponseJSON['token'] 84 | 85 | apiTokenKeychain = keyring.get_password(baseAPIURL, apiUser+'API') 86 | 87 | if not apiTokenKeychain: 88 | keyring.set_password(baseAPIURL, apiUser+'API', apiToken) 89 | print('[>jamfAuth] API Token saved to keychain.') 90 | 91 | return apiToken 92 | except Exception as errorMessage: 93 | print(f'ERROR [get_token(apiToken)]: {errorMessage}') 94 | else: 95 | print(f'[get_token]: Saved API Token..\n') 96 | except Exception as errorMessage: 97 | print(f'ERROR [get_token(apiResponseJSON)]: {errorMessage}\n') 98 | -------------------------------------------------------------------------------- /version_notes.md: -------------------------------------------------------------------------------- 1 | ### v0.3.4 (2022/06/16) 2 | **New Feature** 3 | - Added development server support 4 | **Minor Changes** 5 | - Updated README.md with manual way to delete keychain items 6 | 7 | ### v0.3.3 (2022/04/29) 8 | **New Features** 9 | - Added support for the following Operating Systems: 10 | - Ubuntu Server 20.04.4 LTS 11 | - Red Hat Enterprise Linux 9 (Beta) 12 | **Minor Changes** 13 | - [pip] Added minimum required versions to setup.cfg 14 | - [pip] Added `keychains.alt` to **install_requires** for any Linux system in setup.cfg 15 | 16 | ### v0.3.2 (2022/04/28) 17 | **Minor Changes** 18 | - Changed python classifier from **beta** to **production/stable** 19 | - Updated README.md with example scripts 20 | 21 | ### v0.3.1 (2022/04/27) 22 | **New Features** 23 | - Added error handling to `getToken.py` to better manage the HTTP status codes inside the `getToken()` function. 24 | - Added a section inside the **401** check to handle incorrect passwords. It will now delete the keychain item containing the API Password and prompt you to enter a new one. It will then try to get an API Token with the new credentials 25 | **Minor Changes** 26 | - Adjusted the formatting for how the jamfAuth Config Path appears in the header 27 | 28 | ### v0.3 (2022/04/26) 29 | - apiToken is now stored in the local keychain 30 | - added **setup** and **reset** options for `jamfAuth.py` 31 | --------------------------------------------------------------------------------