Bearer Token, API and adding Computer to Static Group

dlondon
Valued Contributor

I watched the presentation given by @chadlawson  and uploaded to the Rocketman Tech channel on YouTube (https://youtu.be/6xVmJqpbEHI) over the weekend. I decided to dive in and change a script that puts a machine into a static group (credit to @sdagley ) but whilst I can see that I'm getting a token, the computer is not going into the Static Group. The reason for changing from Basic Auth to Bearer Token Auth is because Basic Auth is deprecated so I'm trying to get this figured out before there's a panic.

Just wondering if anyone can see where I'm going wrong or suggest a better way to do this? The first script below is my working script using Basic Auth and the second longer one is the one using Bearer Token Auth. The second script is mostly using code from Rich Trouton's blog on Bearer Tokens and I do get the Bearer Token but whilst I seem to have no errors, the machine is not added to the static group.

#!/bin/sh
# AddComputerToStaticGroup.sh
# Adds the computer to a static group
# https://www.jamf.com/jamf-nation/discussions/36323/script-to-add-to-static-group

#API login info
apiuser="apiusernamehere"
apipass='apipasswordhere'
jamfProURL="https://yourserver.jamfcloud.com"

#ComputerName=$(/usr/sbin/scutil --get ComputerName)
ComputerName="ComputerNameThatExistsInServer"

# My test static group is called TestGroup and has ID of 3
GroupID="3"
GroupName="TestGroup"

apiURL="JSSResource/computergroups/id/${GroupID}"

#XML header stuff
xmlHeader="<?xml version=\"1.0\" encoding=\"UTF-8\"?>"

apiData="<computer_group><id>${GroupID}</id><name>${GroupName}</name><computer_additions><computer><name>$ComputerName</name></computer></computer_additions></computer_group>"

curl -sSkiu ${apiuser}:${apipass} "${jamfProURL}/${apiURL}" \
    -H "Content-Type: text/xml" \
    -d "${xmlHeader}${apiData}" \
    -X PUT  > /dev/null

 

#!/bin/sh
# Adapted from https://derflounder.wordpress.com/2022/01/05/updated-script-for-obtaining-checking-and-renewing-bearer-tokens-for-the-classic-and-jamf-pro-apis/
# This script uses the Jamf Pro API to get an authentication token

# Explicitly set initial value for the api_token variable to null:
api_token=""

# Explicitly set initial value for the token_expiration variable to null:
token_expiration=""

# Set the Jamf Pro URL here if you want it hardcoded.

jamfpro_url="https://yourserver.jamfcloud.com"

jamfpro_user="apiusernamehere"

jamfpro_password='apipasswordhere'	


# Remove the trailing slash from the Jamf Pro URL if needed.
jamfpro_url=${jamfpro_url%%/}

GetJamfProAPIToken() {
  # This function uses Basic Authentication to get a new bearer token for API authentication.
  # Use user account's username and password credentials with Basic Authorization to request a bearer token.

  if [[ $(/usr/bin/sw_vers -productVersion | awk -F . '{print $1}') -lt 12 ]]; then
    api_token=$(/usr/bin/curl -X POST --silent -u "${jamfpro_user}:${jamfpro_password}" "${jamfpro_url}/api/v1/auth/token" | python -c 'import sys, json; print json.load(sys.stdin)["token"]')
  else
    api_token=$(/usr/bin/curl -X POST --silent -u "${jamfpro_user}:${jamfpro_password}" "${jamfpro_url}/api/v1/auth/token" | plutil -extract token raw -)
  fi
}

APITokenValidCheck() {
  # Verify that API authentication is using a valid token by running an API command
  # which displays the authorization details associated with the current API user. 
  # The API call will only return the HTTP status code.

  api_authentication_check=$(/usr/bin/curl --write-out %{http_code} --silent --output /dev/null "${jamfpro_url}/api/v1/auth" --request GET --header "Authorization: Bearer ${api_token}")
}

CheckAndRenewAPIToken() {
  # Verify that API authentication is using a valid token by running an API command
  # which displays the authorization details associated with the current API user. 
  # The API call will only return the HTTP status code.

  APITokenValidCheck

  # If the api_authentication_check has a value of 200, that means that the current
  # bearer token is valid and can be used to authenticate an API call.

  if [[ ${api_authentication_check} == 200 ]]; then
    # If the current bearer token is valid, it is used to connect to the keep-alive endpoint. This will
    # trigger the issuing of a new bearer token and the invalidation of the previous one.

    if [[ $(/usr/bin/sw_vers -productVersion | awk -F . '{print $1}') -lt 12 ]]; then
      api_token=$(/usr/bin/curl "${jamfpro_url}/api/v1/auth/keep-alive" --silent --request POST --header "Authorization: Bearer ${api_token}" | python -c 'import sys, json; print json.load(sys.stdin)["token"]')
    else
      api_token=$(/usr/bin/curl "${jamfpro_url}/api/v1/auth/keep-alive" --silent --request POST --header "Authorization: Bearer ${api_token}" | plutil -extract token raw -)
    fi
  else
    # If the current bearer token is not valid, this will trigger the issuing of a new bearer token
    # using Basic Authentication.

    GetJamfProAPIToken
  fi
}

InvalidateToken() {
  # Verify that API authentication is using a valid token by running an API command
  # which displays the authorization details associated with the current API user. 
  # The API call will only return the HTTP status code.

  APITokenValidCheck

  # If the api_authentication_check has a value of 200, that means that the current
  # bearer token is valid and can be used to authenticate an API call.

  if [[ ${api_authentication_check} == 200 ]]; then
    # If the current bearer token is valid, an API call is sent to invalidate the token.
    authToken=$(/usr/bin/curl "${jamfpro_url}/api/v1/auth/invalidate-token" --silent  --header "Authorization: Bearer ${api_token}" -X POST)
      
    # Explicitly set value for the api_token variable to null.
    api_token=""
  fi
}

GetJamfProAPIToken

APITokenValidCheck

echo "$api_authentication_check"

echo "$api_token"

CheckAndRenewAPIToken

APITokenValidCheck

echo "$api_authentication_check"

echo "$api_token"

#ComputerName=$(/usr/sbin/scutil --get ComputerName)
ComputerName="ComputerNameThatExistsInServer"

# My test static group is called TestGroup and has ID of 3
GroupID="3"
echo "GroupID: $GroupID"
GroupName="TestGroup"
echo "GroupName: $GroupName"

apiURL="JSSResource/computergroups/id/${GroupID}"
echo "apiURL: $apiURL"

#XML header stuff
xmlHeader="<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
echo "xmlHeader: $xmlHeader"

apiData="<computer_group><id>${GroupID}</id><name>${GroupName}</name><computer_additions><computer><name>$ComputerName</name></computer></computer_additions></computer_group>"
echo "apiData: $apiData"


curl -sSkiu "Authorization: Bearer ${api_token}" "${jamfpro_url}/${apiURL}" \
    -H "Content-Type: text/xml" \
    -d "${xmlHeader}${apiData}" \
    -X PUT  > /dev/null


InvalidateToken

APITokenValidCheck

echo "$api_authentication_check"

echo "$api_token"
1 ACCEPTED SOLUTION

sdagley
Esteemed Contributor II

@dlondon Use the following for the curl command to do the addition:

 

curl -s \
	--header "Authorization: Bearer ${api_token}" --header "Content-Type: text/xml" \
	--url "${jamfpro_url}/${apiURL}" \
	--data "${apiData}" \
    --request PUT

P.S. Thanks for taking the lead on revising the sample to use Bearer Token Auth.

 

View solution in original post

12 REPLIES 12

sdagley
Esteemed Contributor II

@dlondon Use the following for the curl command to do the addition:

 

curl -s \
	--header "Authorization: Bearer ${api_token}" --header "Content-Type: text/xml" \
	--url "${jamfpro_url}/${apiURL}" \
	--data "${apiData}" \
    --request PUT

P.S. Thanks for taking the lead on revising the sample to use Bearer Token Auth.

 

dlondon
Valued Contributor

Thanks @sdagley - made my day and I'm only 5 minutes into it :)  
I knew you and some others would get to it but the Rocketman presentation drew me in.

dlondon
Valued Contributor

Ok - I've cut out the code that wasn't used so the final and now working example looks like this:

 

 

 

#!/bin/sh
# This script uses the Jamf Pro API to get a Bearer Authentication Token and then adds a computer to a Static Group.  
#   In this case it could be the computer the script is run on if you uncomment the particular line.  I actually set the computer name for testing to a specific name
# Adapted from https://derflounder.wordpress.com/2022/01/05/updated-script-for-obtaining-checking-and-renewing-bearer-tokens-for-the-classic-and-jamf-pro-apis/ - not all functions in Rich's example are used as this is a very short access to the API
# Also help from Steve Dagley in https://community.jamf.com/t5/jamf-pro/bearer-token-api-and-adding-computer-to-static-group/td-p/261269
#   and https://community.jamf.com/t5/jamf-pro/script-to-add-computer-to-static-group/m-p/198738
# 2022-03-17 David London

# Find/Set the computer name.  I'm manually setting it here for testing.
#ComputerName=$(/usr/sbin/scutil --get ComputerName)
ComputerName="is-m-00112"

# My test static group is called TestGroup and has ID of 3.  Set these to whatever you need
GroupID="3"
GroupName="TestGroup"

# Explicitly set initial value for the api_token variable to null:
api_token=""

# Explicitly set initial value for the token_expiration variable to null:
token_expiration=""

# Set the Jamf Pro URL here if you want it hardcoded.
jamfpro_url="https://yourserver.jamfcloud.com"

# Set the username here if you want it hardcoded.
jamfpro_user="apiusernamehere"

# Set the password here if you want it hardcoded.
jamfpro_password='apipasswordhere'	


# Remove the trailing slash from the Jamf Pro URL if needed.
jamfpro_url=${jamfpro_url%%/}

GetJamfProAPIToken() {
    # This function uses Basic Authentication to get a new bearer token for API authentication.
    # Use user account's username and password credentials with Basic Authorization to request a bearer token.

    if [[ $(/usr/bin/sw_vers -productVersion | awk -F . '{print $1}') -lt 12 ]]; then
        api_token=$(/usr/bin/curl -X POST --silent -u "${jamfpro_user}:${jamfpro_password}" "${jamfpro_url}/api/v1/auth/token" | python -c 'import sys, json; print json.load(sys.stdin)["token"]')
    else
        api_token=$(/usr/bin/curl -X POST --silent -u "${jamfpro_user}:${jamfpro_password}" "${jamfpro_url}/api/v1/auth/token" | plutil -extract token raw -)
    fi
}

APITokenValidCheck() {
    # Verify that API authentication is using a valid token by running an API command
    # which displays the authorization details associated with the current API user. 
    # The API call will only return the HTTP status code.

    api_authentication_check=$(/usr/bin/curl --write-out %{http_code} --silent --output /dev/null "${jamfpro_url}/api/v1/auth" --request GET --header "Authorization: Bearer ${api_token}")
}

InvalidateToken() {
    # Verify that API authentication is using a valid token by running an API command
    # which displays the authorization details associated with the current API user. 
    # The API call will only return the HTTP status code.

    APITokenValidCheck

    # If the api_authentication_check has a value of 200, that means that the current
    # bearer token is valid and can be used to authenticate an API call.

    if [[ ${api_authentication_check} == 200 ]]; then

        # If the current bearer token is valid, an API call is sent to invalidate the token.

        authToken=$(/usr/bin/curl "${jamfpro_url}/api/v1/auth/invalidate-token" --silent  --header "Authorization: Bearer ${api_token}" -X POST)
      
        # Explicitly set value for the api_token variable to null.
        api_token=""
    fi
}

GetJamfProAPIToken

apiURL="JSSResource/computergroups/id/${GroupID}"

apiData="<computer_group><id>${GroupID}</id><name>${GroupName}</name><computer_additions><computer><name>$ComputerName</name></computer></computer_additions></computer_group>"

curl -s \
	--header "Authorization: Bearer ${api_token}" --header "Content-Type: text/xml" \
	--url "${jamfpro_url}/${apiURL}" \
	--data "${apiData}" \
    --request PUT > /dev/null

InvalidateToken

exit 0

 

 

 

 

 

dlondon
Valued Contributor

I've revisited this script and cleaned it up a little. There was no need for Group Name - just Group ID.  I also needed a way to remove a computer from a Static Group so added that in.  Finally I made it generic so it takes input parameters from Jamf


 

#!/bin/bash
# AddRemoveComputer-StaticGroup-UsingBearerAuthenticationToken.bash
# This script uses the Jamf Pro API to get an Bearer Authentication Token and then adds or removes a computer for an identified static group.  
#   In this case it's the computer the script is run on
# Adapted from https://derflounder.wordpress.com/2022/01/05/updated-script-for-obtaining-checking-and-renewing-bearer-tokens-for-the-classic-and-jamf-pro-apis/ 
#   - not all functions in Rich's example are used as this is a very short access to the API
# Also help from Steve Dagley in https://community.jamf.com/t5/jamf-pro/bearer-token-api-and-adding-computer-to-static-group/td-p/261269
#   and https://community.jamf.com/t5/jamf-pro/script-to-add-computer-to-static-group/m-p/198738
# 2022-03-17 David London
#  - some portions of the input variable stage are commented.  I've left the explicit lines in but commented them out

## Grab the serial number of the device
serialNumber="$(ioreg -rd1 -c IOPlatformExpertDevice | awk -F'"' '/IOPlatformSerialNumber/{print $4}')"

# Explicitly set initial value for the api_token variable to null:
api_token=""

# Explicitly set initial value for the token_expiration variable to null:
token_expiration=""

## Check if the variables have been provided, set them if not.  
jamfpro_user="$4"
# Hard code user if desired
# jamfpro_user="your_api_user"

jamfpro_password="$5"
# Hard code password if desired
# jamfpro_password='jamfpro_password_here'	

jamfpro_url="$6"
# Hard code JamfPro URL if desired
# jamfpro_url="https://your_jamf_pro_server_url_including_port_if_used_in_url"

# Remove the trailing slash from the Jamf Pro URL if needed.
jamfpro_url=${jamfpro_url%%/}

GroupID="$7"
# Hard code Group ID if desired
# GroupID="Group_ID_Number"

AddRemove="$8"
# Hard code AddRemove here if you want - possible values are "add" or "remove"
# AddRemove="add"
# AddRemove="remove"

# Make sure AddRemove is lower case
AddRemove=$(echo $AddRemove | tr '[:upper:]' '[:lower:]')

GetJamfProAPIToken() {
    # This function uses Basic Authentication to get a new bearer token for API authentication.
    # Use user account's username and password credentials with Basic Authorization to request a bearer token.

    if [[ $(/usr/bin/sw_vers -productVersion | awk -F . '{print $1}') -lt 12 ]]; then
        api_token=$(/usr/bin/curl -X POST --silent -u "${jamfpro_user}:${jamfpro_password}" "${jamfpro_url}/api/v1/auth/token" | python -c 'import sys, json; print json.load(sys.stdin)["token"]')
    else
        api_token=$(/usr/bin/curl -X POST --silent -u "${jamfpro_user}:${jamfpro_password}" "${jamfpro_url}/api/v1/auth/token" | plutil -extract token raw -)
    fi
}

APITokenValidCheck() {
    # Verify that API authentication is using a valid token by running an API command
    # which displays the authorization details associated with the current API user. 
    # The API call will only return the HTTP status code.

    api_authentication_check=$(/usr/bin/curl --write-out %{http_code} --silent --output /dev/null "${jamfpro_url}/api/v1/auth" --request GET --header "Authorization: Bearer ${api_token}")
}

InvalidateToken() {
    # Verify that API authentication is using a valid token by running an API command
    # which displays the authorization details associated with the current API user. 
    # The API call will only return the HTTP status code.

    APITokenValidCheck

    # If the api_authentication_check has a value of 200, that means that the current
    # bearer token is valid and can be used to authenticate an API call.

    if [[ ${api_authentication_check} == 200 ]]; then

        # If the current bearer token is valid, an API call is sent to invalidate the token.

        authToken=$(/usr/bin/curl "${jamfpro_url}/api/v1/auth/invalidate-token" --silent  --header "Authorization: Bearer ${api_token}" -X POST)
      
        # Explicitly set value for the api_token variable to null.
        api_token=""
    fi
}

GetJamfProAPIToken

apiURL="JSSResource/computergroups/id/${GroupID}"

if [ "$AddRemove" == "add" ]; then
    apiData="<computer_group><computer_additions><computer><serial_number>$serialNumber</serial_number></computer></computer_additions></computer_group>"
else
    apiData="<computer_group><computer_deletions><computer><serial_number>$serialNumber</serial_number></computer></computer_deletions></computer_group>"
fi

curl -s \
	--header "Authorization: Bearer ${api_token}" --header "Content-Type: text/xml" \
	--url "${jamfpro_url}/${apiURL}" \
	--data "${apiData}" \
    --request PUT > /dev/null

InvalidateToken

exit 0

 

Thanks for this - after struggling this morning trying to put together the proper request I came across your post. I pulled several parts of this for a script that, among other things, removes the computer from a group, and it worked great!

Anonymous
Not applicable

Thank you kindly for the script, great example for API

tegus232
Contributor

How would I go about removing multiple computers via csv or by hardcoding multiple computers or serial numbers? 

cbruce
New Contributor III

Hello @dlondon, Thanks for all the work on the script and sharing !  @tegus232 mentioned if more than 1 computer name can be added to the script or can a wildcard be used.  For example all computers beginning with dlo-  ?

Thank you !

dlondon
Valued Contributor

Hi @cbruce and @tegus232 yes it's a script.  I put it here so others (like you) can use it as a starting point for your own scripts.

I can certainly imagine how a script as described by you might work but I don't have a use for that and am flat out doing my normal work.  The above script had a direct use for me so I can justify the time spent working on it. 

There are some good free starter modules in Jamf Learning for shell scripting

There's some good documentation in https://developer.jamf.com/jamf-pro/docs/jamf-pro-api-overview  and also some of the Jamf courses cover this.








paul_burgess_fu
New Contributor

Hi, @dlondon Does this script still work for you on Jamf Pro 11? I've revisited this morning but it doesn't seem to work. Did you have to add an API role etc?

@paul_burgess_fu @dlondon

API Role just needs Update Static Computer Groups and Read Computers.

This was written from scratch to use the new authentication. I've tested it to be working fine.

 

#!/bin/bash

# Fetch parameters from Jamf policy
CLIENT_ID="$4"
CLIENT_SECRET="$5"
STATIC_GROUP_ID="$6"
JAMF_URL="$7"

# Debugging: Display static group ID and Jamf URL without sensitive info
echo "STATIC_GROUP_ID: $STATIC_GROUP_ID"
echo "JAMF_URL: $JAMF_URL"

# Get the serial number
SERIAL_NUMBER=$(ioreg -l | awk '/IOPlatformSerialNumber/ { print $4;}' | sed 's/"//g')
echo "Serial Number: $SERIAL_NUMBER"

# Authenticate and get an access token
AUTH_RESPONSE=$(curl --location --request POST "$JAMF_URL/api/oauth/token" \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode "client_id=$CLIENT_ID" \
--data-urlencode "grant_type=client_credentials" \
--data-urlencode "client_secret=$CLIENT_SECRET" --silent)

# Debugging: Display auth response status (remove sensitive details)
if [[ $AUTH_RESPONSE == *"access_token"* ]]; then
echo "Auth Response: Successfully retrieved access token"
else
echo "Auth Response: Failed to retrieve access token"
echo "Response: $AUTH_RESPONSE"
exit 1
fi

# Extract access token from the response
ACCESS_TOKEN=$(echo $AUTH_RESPONSE | sed -n 's/.*"access_token":"\([^"]*\)".*/\1/p')

# Get the Jamf Pro device ID using the access token
DEVICE_ID=$(curl -s -H "Accept: text/xml" -H "Authorization: Bearer ${ACCESS_TOKEN}" "${JAMF_URL}/JSSResource/computers/serialnumber/${SERIAL_NUMBER}" | xmllint --xpath '/computer/general/id/text()' -)
echo "Device ID: $DEVICE_ID"

# Check if DEVICE_ID was successfully retrieved
if [ -z "$DEVICE_ID" ]; then
echo "Failed to obtain Device ID"
exit 1
fi

# Add the computer to the static group
apiURL="JSSResource/computergroups/id/${STATIC_GROUP_ID}"
xmlHeader="<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
apiData="<computer_group><id>${STATIC_GROUP_ID}</id><computer_additions><computer><id>${DEVICE_ID}</id></computer></computer_additions></computer_group>"

ADD_COMPUTER_RESPONSE=$(curl -s \
--header "Authorization: Bearer ${ACCESS_TOKEN}" --header "Content-Type: text/xml" \
--url "${JAMF_URL}/${apiURL}" \
--data "${xmlHeader}${apiData}" \
--request PUT)

# Validate if the computer was added successfully
if echo "$ADD_COMPUTER_RESPONSE" | grep -q "<id>${STATIC_GROUP_ID}</id>"; then
echo "Successfully added computer to static group"
else
echo "Failed to add computer to static group"
echo "Response: $ADD_COMPUTER_RESPONSE"
exit 1
fi

exit 0

dlondon
Valued Contributor

Hi @paul_burgess_fu Still working on 11 although I've started using API roles so it's on the list to look at.  Now is not a good time for me to do any development not directly related to packaging as I'm preparing for Semester 1 where I work.  I don't know how familiar you are with scripting but try adding some echo statements and run it in Terminal and look at the value of various variables that are in the script.

You could also look at your policy log if you've just put it there before manually testing but I would encourage you always to manually test scripts before going to policy