Skip to content
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ A lightweight Node.js wrapper for Jamf Pro's JSS REST API.

## Scope

Currently this module only supports `GET` requests for the JSS REST API. Support for other methods such as `PUT`, `POST`, and `DELETE` will be added later.
Currently this module only supports `GET`, `PUT`, and `POST` requests for the JSS REST API. Support for `DELETE` will be added later.

Support for the JSS Universal API may also be added later.

Expand Down Expand Up @@ -59,7 +59,7 @@ You need to configure the Jamf API client before it can make any API calls. You'
* `user`: username for the JSS user accessing the Jamf API
* `password`: password for the above account
* `jamfUrl`: the URL of your JSS instance, whether on premise or in the cloud (`https://yourdomain.jamfcloud.com`)
* `format`: the format of the returned data, must be either `xml` (Jamf API default) or `json`
* `format`: the format of the returned data, must be either `xml` (Jamf API default) or `json`. `PUT` and `POST` methods always return xml,

We recommend you create a new dedicated, least privilege API user for these scripts. The user must have the necessary privileges (create, read, update, or delete) on the JSS objects

Expand Down Expand Up @@ -102,6 +102,6 @@ All of your tests should pass before we'll accept your PR. We also request that

## Resources

* You can see all available Jamf API calls by accessing `/api` on your JSS instance. For example, visit `https://yourdomain.jamfcloud.com/api`
* You can see all available Jamf API calls by accessing `/api` on your JSS instance. For example, visit `https://yourdomain.jamfcloud.com/api`. This page also contains an API playground where you can test out requests.
* [Jamf API Documentation](https://developer.jamf.com/documentation)

* [Casper API Command Line Tool](https://github.com/eventbrite/Casper-API-Tools) - good reference for XML structure for `PUT` and `POST`.
170 changes: 134 additions & 36 deletions lib/jamfclient.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
var request = require('request');
var axios = require('axios');
var _ = require('lodash');
var promises = require('bluebird');

var validFormats = ['json', 'xml'];

Expand Down Expand Up @@ -27,53 +28,150 @@ function JamfApiClient(config) {
user: config.user,
password: config.password,
jamfUrl: config.jamfUrl,
format: config.format
format: config.format,
token: false
}
}

var successCodes = [200, 201];

JamfApiClient.prototype = {
get: function(path, callback) {

var requestUrl = this.config.jamfUrl + '/JSSResource' + path;
var dataFormat = this.config.format;
getToken: async function() {
if (this.config.token === false) {
var requestOptions = {
'method': 'GET',
'auth': {
'user': this.config.user,
'pass': this.config.password,
'sendImmediately': false
}
}

if (dataFormat === 'json') {
requestOptions.headers = {
method: 'POST',
url: this.config.jamfUrl + '/api/v1/auth/token',
auth: {
username: this.config.user,
password: this.config.password,
sendImmediately: false
},
headers: {
'Accept': 'application/json'
}
}
};

if (dataFormat === 'xml') {
requestOptions.headers = {
'Accept': 'text/xml'
}
}
let tokenResponse = await axios(requestOptions);

request(requestUrl, requestOptions, function(error, response, body){
if (!_.includes(successCodes, response.statusCode)) {
var error = new Error(response.statusCode + ' ' + response.statusMessage);
}
if (error) {
callback(error, null);
} else {
// don't JSON parse if the format is XML
if (dataFormat === 'json') {
callback(null, JSON.parse(body));
} else {
callback(null, body);
}
}
this.config.token = tokenResponse.data.token;
}
},
get: function(path) {
var promise = new Promise((resolve, reject) => {
var promises = [];

var dataFormat = this.config.format;

var requestOptions = {
method: 'GET',
url: this.config.jamfUrl + '/JSSResource' + path,
headers: {
'Authorization': `Bearer ${this.config.token}`
}
};

if (dataFormat === 'json') {
requestOptions.headers.Accept = 'application/json';
}

if (dataFormat === 'xml') {
requestOptions.headers.Accept = 'text/xml';
}

promises.push(axios(requestOptions));

Promise.all(promises)
.then((results) => {
var response = results[0];

if (response && !_.includes(successCodes, response.status)) {
reject({ error: { code: response.status, message: response.status + ' ' + response.statusText } });
}

var body = response.data;

// axios parses the body as JSON if headers indicate it is so
resolve(body);
})
.catch((err) => {
reject({ error: { code: err.response.status } });
});
});

return promise;
},
post: function(path, xml) {
var promise = new Promise((resolve, reject) => {
var promises = [];

var requestOptions = {
method: 'POST',
url: this.config.jamfUrl + '/JSSResource' + path,
headers: {
'Authorization': `Bearer ${this.config.token}`
}
}

if (xml !== '') {
requestOptions.data = xml; // Needed so axios calculates Content-Lentgh
requestOptions.body = xml;
}

promises.push(axios(requestOptions));

Promise.all(promises)
.then((results) => {
var response = results[0];

if (response && !_.includes(successCodes, response.status)) {
reject({ error: { code: response.status, message: response.status + ' ' + response.statusText } });
}

var body = response.data;

// axios parses the body as JSON if headers indicate it is so
resolve(body);
})
.catch((err) => {
reject({ error: { code: err.response.status } });
});
});

return promise;
},
put: function(path, xml) {
var promise = new Promise((resolve, reject) => {
var promises = [];

var requestOptions = {
method: 'PUT',
url: this.config.jamfUrl + '/JSSResource' + path,
headers: {
'Authorization': `Bearer ${this.config.token}`,
'Accept': '*/*',
'Content-Type': 'application/xml'
},
data: xml, // Needed so axios calculates Content-Lentgh
body: xml
};

promises.push(axios(requestOptions));

Promise.all(promises).then((results) => {
var response = results[0];

if (response && !_.includes(successCodes, response.status)) {
reject({ error: { code: response.status, message: response.status + ' ' + response.statusText } });
}

var body = response.data;

// axios parses the body as JSON if headers indicate it is so
resolve(body);
});
});

return promise;
}
}

Expand Down
Loading