"use strict";
// Core packages
const url = require('url');
// Npm packages
const request = require('request');
const jwt = require('atlassian-jwt');
const queryString = require('query-string');
// Custom packages
var applicationProperties = require('./api/application-properties');
var attachment = require('./api/attachment');
var auditing = require('./api/auditing');
var auth = require('./api/auth');
var avatar = require('./api/avatar');
var backlog = require('./api/backlog');
var board = require('./api/board');
var comment = require('./api/comment');
var component = require('./api/component');
var customFieldOption = require('./api/customFieldOption');
var dashboard = require('./api/dashboard');
const developmentInformation = require('./api/developmentInformation');
var epic = require('./api/epic');
var errorStrings = require('./lib/error');
var field = require('./api/field');
var filter = require('./api/filter');
var group = require('./api/group');
var groupUserPicker = require('./api/groupUserPicker');
var groups = require('./api/groups');
var issue = require('./api/issue');
var issueLink = require('./api/issueLink');
var issueLinkType = require('./api/issueLinkType');
var issueType = require('./api/issueType');
var jql = require('./api/jql');
var labels = require('./api/labels');
var licenseRole = require('./api/licenseRole');
var licenseValidator = require('./api/licenseValidator');
var myPermissions = require('./api/myPermissions');
var myPreferences = require('./api/myPreferences');
var myself = require('./api/myself');
var oauth_util = require('./lib/oauth_util');
var password = require('./api/password');
var permissions = require('./api/permissions');
var permissionScheme = require('./api/permission-scheme');
var priority = require('./api/priority');
var project = require('./api/project');
var projectCategory = require('./api/projectCategory');
var projectValidate = require('./api/projectValidate');
var reindex = require('./api/reindex');
var resolution = require('./api/resolution');
var roles = require('./api/roles');
var screens = require('./api/screens');
var search = require('./api/search');
var securityLevel = require('./api/securityLevel');
var serverInfo = require('./api/serverInfo');
var settings = require('./api/settings');
var sprint = require('./api/sprint');
var status = require('./api/status');
var statusCategory = require('./api/statusCategory');
var user = require('./api/user');
var version = require('./api/version');
var webhook = require('./api/webhook');
var workflow = require('./api/workflow');
var workflowScheme = require('./api/workflowScheme');
var worklog = require('./api/worklog');
/**
* @callback callback
* @param {any} err
* @param {any} data
* @returns {void}
*/
/**
* Represents a client for the Jira REST API
*
* @constructor JiraClient
* @property {AgileBoardClient} board
* @property {AgileSprintClient} sprint
*
* @property {ApplicationPropertiesClient} applicationProperties
* @property {AttachmentClient} attachment
* @property {AuditingClient} auditing
* @property {AuthClient} auth
* @property {AvatarClient} avatar
* @property {CommentClient} comment
* @property {ComponentClient} component
* @property {CustomFieldOptionClient} customFieldOption
* @property {DashboardClient} dashboard
* @property {DevelopmentInformationClient} developmentInformation
* @property {FieldClient} field
* @property {FilterClient} filter
* @property {GroupClient} group
* @property {GroupUserPickerClient} groupUserPicker
* @property {GroupsClient} groups
* @property {IssueClient} issue
* @property {IssueLinkClient} issueLink
* @property {IssueLinkTypeClient} issueLinkType
* @property {IssueTypeClient} issueType
* @property {JqlClient} jql
* @property {LabelsClient} labels
* @property {LicenseRoleClient} licenseRole
* @property {LicenseValidatorClient} licenseValidator
* @property {MyPermissionsClient} myPermissions
* @property {MyPreferencesClient} myPreferences
* @property {MyselfClient} myself
* @property {PasswordClient} password
* @property {PermissionsClient} permissions
* @property {PermissionSchemeClient} permissionScheme
* @property {PriorityClient} priority
* @property {ProjectCategoryClient} projectCategory
* @property {ProjectClient} project
* @property {ProjectValidateClient} projectValidate
* @property {ReindexClient} reindex
* @property {ResolutionClient} resolution
* @property {RoleClient} roles
* @property {ScreensClient} screens
* @property {SearchClient} search
* @property {SecurityLevelClient} securityLevel
* @property {ServerInfoClient} serverInfo
* @property {SettingsClient} settings
* @property {StatusCategoryClient} statusCategory
* @property {StatusClient} status
* @property {UserClient} user
* @property {VersionClient} version
* @property {WebhookClient} webhook
* @property {WorkflowClient} workflow
* @property {WorkflowSchemeClient} workflowScheme
* @property {WorklogClient} worklog
*
* @param config The information needed to access the Jira API
* @param {string} config.host The hostname of the Jira API.
* @param {number} [config.timeout] request timeout (milliseconds)
* @param {string} [config.protocol=https] The protocol used to accses the Jira API.
* @param {number} [config.port=443] The port number used to connect to Jira.
* @param {string} [config.path_prefix="/"] The prefix to use in front of the path, if Jira isn't at "/"
* @param {boolean} [config.strictSSL=true] It is recommended not to turn it off for no reason (https://github.com/request/request/issues/251).
* @param {number | string} [config.version=2] The version of the Jira API to which you will be connecting. Currently, only
* version 2 is supported.
* @param {Object} [config.basic_auth] The authentication information used tp connect to Jira. Must contain EITHER username and password
* OR oauth information. Oauth information will be used over username/password authentication.
* @param {Object} [config.basic_auth.base64] base64 that contains email:api_token.
* @param {string} [config.basic_auth.username] (DEPRECATED) The username of the user that will be authenticated. MUST be included
* if using username and password authentication.
* @param {string} [config.basic_auth.password] (DEPRECATED) The password of the user that will be authenticated. MUST be included
* if using username and password authentication.
* @param {string} [config.basic_auth.email] The email of the user that will be authenticated. MUST be included
* if using email and api_token authentication.
* @param {string} [config.basic_auth.api_token] The api token of the user that will be authenticated. MUST be included
* if using email and api_token authentication.
* @param {string} [config.oauth.consumer_key] The consumer key used in the Jira Application Link for oauth
* authentication. MUST be included if using OAuth.
* @param {string} [config.oauth.private_key] The private key used for OAuth security. MUST be included if using OAuth.
* @param {string} [config.oauth.token] The VERIFIED token used to connect to the Jira API. MUST be included if using
* OAuth.
* @param {string} [config.oauth.token_secret] The secret for the above token. MUST be included if using Oauth.
* @param {Object} [config.jwt] The JWT configuration object that contains iss:secret
* @param {string} config.jwt.iss The Jira app key (can be found in the app descriptor). MUST be included
* @param {string} config.jwt.secret The JWT secret token. MUST be included
* @param {string} [config.jwt.expiry_time_seconds] The JWT token expiry time in seconds. OPTIONAL (default 180 seconds)
* @param {CookieJar} [config.cookie_jar] The CookieJar to use for every requests.
* @param {Promise} [config.promise] Any function (constructor) compatible with Promise (bluebird, Q,...).
* Default - native Promise.
* @param {Request} [config.request] Any function (constructor) compatible with Request (request, supertest,...).
* Default - require('request').
*/
var JiraClient = module.exports = function (config) {
if (!config.host) {
throw new Error(errorStrings.NO_HOST_ERROR);
}
this.host = config.host;
this.timeout = config.timeout;
this.protocol = config.protocol ? config.protocol : 'https';
this.path_prefix = config.path_prefix ? config.path_prefix : '/';
this.port = config.port;
this.apiVersion = 2;
this.strictSSL = config.hasOwnProperty('strictSSL') ? config.strictSSL : true;
this.agileApiVersion = '1.0';
this.authApiVersion = '1';
this.webhookApiVersion = '1.0';
this.promise = config.promise || Promise;
this.requestLib = config.request || request;
this.rejectUnauthorized = config.rejectUnauthorized;
if (config.oauth) {
if (!config.oauth.consumer_key) {
throw new Error(errorStrings.NO_CONSUMER_KEY_ERROR);
} else if (!config.oauth.private_key) {
throw new Error(errorStrings.NO_PRIVATE_KEY_ERROR);
} else if (!config.oauth.token) {
throw new Error(errorStrings.NO_OAUTH_TOKEN_ERROR);
} else if (!config.oauth.token_secret) {
throw new Error(errorStrings.NO_OAUTH_TOKEN_SECRET_ERROR);
}
this.oauthConfig = config.oauth;
this.oauthConfig.signature_method = 'RSA-SHA1';
} else if (config.basic_auth) {
if (config.basic_auth.base64) {
this.basic_auth = {
base64: config.basic_auth.base64
}
} else if (config.basic_auth.api_token || config.basic_auth.email) {
if (!config.basic_auth.email) {
throw new Error(errorStrings.NO_EMAIL_ERROR);
} else if (!config.basic_auth.api_token) {
throw new Error(errorStrings.NO_APITOKEN_ERROR);
}
this.basic_auth = {
user: config.basic_auth.email,
pass: config.basic_auth.api_token
};
} else {
if (!config.basic_auth.username) {
throw new Error(errorStrings.NO_USERNAME_ERROR);
} else if (!config.basic_auth.password) {
throw new Error(errorStrings.NO_PASSWORD_ERROR);
}
this.basic_auth = {
user: config.basic_auth.username,
pass: config.basic_auth.password
};
}
} else if (config.jwt) {
if (config.jwt.secret && config.jwt.iss) {
this.jwt = {
iss: config.jwt.iss,
secret: config.jwt.secret,
expiry_time_seconds: config.jwt.expiry_time_seconds || 3 * 60
};
} else {
if (!config.jwt.secret) {
throw new Error(errorStrings.NO_JWT_SECRET_KEY_ERROR);
} else if (!config.jwt.iss) {
throw new Error(errorStrings.NO_JWT_ISS_KEY_ERROR);
}
}
}
if (config.cookie_jar) {
this.cookie_jar = config.cookie_jar;
}
this.applicationProperties = new applicationProperties(this);
this.attachment = new attachment(this);
this.auditing = new auditing(this);
this.auth = new auth(this);
this.avatar = new avatar(this);
this.backlog = new backlog(this);
this.board = new board(this);
this.comment = new comment(this);
this.component = new component(this);
this.customFieldOption = new customFieldOption(this);
this.dashboard = new dashboard(this);
this.developmentInformation = new developmentInformation(this);
this.epic = new epic(this);
this.field = new field(this);
this.filter = new filter(this);
this.group = new group(this);
this.groupUserPicker = new groupUserPicker(this);
this.groups = new groups(this);
this.issue = new issue(this);
this.issueLink = new issueLink(this);
this.issueLinkType = new issueLinkType(this);
this.issueType = new issueType(this);
this.jql = new jql(this);
this.labels = new labels(this);
this.licenseRole = new licenseRole(this);
this.licenseValidator = new licenseValidator(this);
this.myPermissions = new myPermissions(this);
this.myPreferences = new myPreferences(this);
this.myself = new myself(this);
this.password = new password(this);
this.permissions = new permissions(this);
this.permissionScheme = new permissionScheme(this);
this.priority = new priority(this);
this.project = new project(this);
this.projectCategory = new projectCategory(this);
this.projectValidate = new projectValidate(this);
this.reindex = new reindex(this);
this.resolution = new resolution(this);
this.roles = new roles(this);
this.screens = new screens(this);
this.search = new search(this);
this.securityLevel = new securityLevel(this);
this.serverInfo = new serverInfo(this);
this.settings = new settings(this);
this.sprint = new sprint(this);
this.status = new status(this);
this.statusCategory = new statusCategory(this);
this.user = new user(this);
this.version = new version(this);
this.webhook = new webhook(this);
this.workflow = new workflow(this);
this.workflowScheme = new workflowScheme(this);
this.worklog = new worklog(this);
};
(function () {
/**
* Simple utility to build a REST endpoint URL for the Jira API.
*
* @method buildURL
* @memberOf JiraClient#
* @param path The path of the URL without concern for the root of the REST API.
* @param {string | number} [forcedVersion] Use this param to force a particular version
* @returns {string} The constructed URL.
*/
this.buildURL = function (path, forcedVersion) {
var apiBasePath = this.path_prefix + 'rest/api/';
var version = forcedVersion || this.apiVersion;
var requestUrl = url.format({
protocol: this.protocol,
hostname: this.host,
port: this.port,
pathname: apiBasePath + version + path
});
return decodeURIComponent(requestUrl);
};
/**
* Simple utility to build a REST endpoint URL for the Jira API without prefixes.
*
* @method buildAbstractURL
* @memberOf JiraClient#
* @param path The path of the URL without concern for the root of the REST API.
* @returns {string} The constructed URL.
*/
this.buildAbstractURL = function(path) {
const apiBasePath = this.path_prefix + 'rest/';
const requestUrl = url.format({
protocol: this.protocol,
hostname: this.host,
port: this.port,
pathname: apiBasePath + path
});
return decodeURIComponent(requestUrl);
}
/**
* Simple utility to build a REST endpoint URL for the Jira Agile API.
*
* @method buildAgileURL
* @memberOf JiraClient#
* @param path The path of the URL without concern for the root of the REST API.
* @param {string | number} [forcedVersion] Use this param to force a particular version
* @returns {string} The constructed URL.
*/
this.buildAgileURL = function (path, forcedVersion) {
var apiBasePath = this.path_prefix + 'rest/agile/';
var version = forcedVersion || this.agileApiVersion;
var requestUrl = url.format({
protocol: this.protocol,
hostname: this.host,
port: this.port,
pathname: apiBasePath + version + path
});
return decodeURIComponent(requestUrl);
};
/**
* Simple utility to build a REST endpoint URL for the Jira Auth API.
*
* @method buildAuthURL
* @memberOf JiraClient#
* @param path The path of the URL without concern for the root of the REST API.
* @param {string | number} [forcedVersion] Use this param to force a particular version
* @returns {string} The constructed URL.
*/
this.buildAuthURL = function (path, forcedVersion) {
var apiBasePath = this.path_prefix + 'rest/auth/';
var version = forcedVersion || this.authApiVersion;
var requestUrl = url.format({
protocol: this.protocol,
hostname: this.host,
port: this.port,
pathname: apiBasePath + version + path
});
return decodeURIComponent(requestUrl);
};
/**
* Simple utility to build a REST endpoint URL for the Jira webhook API.
*
* @method buildWebhookURL
* @memberOf JiraClient#
* @param path The path of the URL without concern for the root of the REST API.
* @param {string | number} [forcedVersion] Use this param to force a particular version
* @returns {string} The constructed URL.
*/
this.buildWebhookURL = function (path, forcedVersion) {
var apiBasePath = this.path_prefix + 'rest/webhooks/';
var version = forcedVersion || this.webhookApiVersion;
var requestUrl = url.format({
protocol: this.protocol,
hostname: this.host,
port: this.port,
pathname: apiBasePath + version + path
});
return decodeURIComponent(requestUrl);
};
/**
* Make a request to the Jira API and call back with it's response.
*
* @method makeRequest
* @memberOf JiraClient#
* @param options The request options.
* @param {callback} [callback] Called with the APIs response.
* @param {string} [successString] If supplied, this is reported instead of the response body.
* @return {Promise} Resolved with APIs response or rejected with error
*/
this.makeRequest = function (options, callback, successString) {
let requestLib = this.requestLib;
options.rejectUnauthorized = this.rejectUnauthorized;
options.strictSSL = this.strictSSL;
options.timeout = this.timeout;
if (this.oauthConfig) {
options.oauth = this.oauthConfig;
} else if (this.basic_auth) {
if (this.basic_auth.base64) {
if (!options.headers) {
options.headers = {}
}
options.headers['Authorization'] = 'Basic ' + this.basic_auth.base64
} else {
options.auth = this.basic_auth;
}
} else if (this.jwt) {
const pathname = new URL(options.uri).pathname;
const nowInSeconds = Math.floor(Date.now() / 1000);
const queryParam = queryString.parse(queryString.stringify(options.qs));
const jwtToken = jwt.encode({
iss: this.jwt.iss,
iat: nowInSeconds,
exp: nowInSeconds + this.jwt.expiry_time_seconds,
qsh: jwt.createQueryStringHash({
method: options.method,
pathname,
query: queryParam || {}
})
}, this.jwt.secret);
if (!options.headers) {
options.headers = {};
}
options.headers['Authorization'] = `JWT ${jwtToken}`;
}
if (this.cookie_jar) {
options.jar = this.cookie_jar;
}
if (callback) {
requestLib(options, function (err, response, body) {
if (
err ||
response.statusCode < 200 ||
response.statusCode > 399
) {
return callback(err ? err : body, null, response);
}
if (typeof body === 'string') {
try {
body = JSON.parse(body);
} catch (jsonErr) {
return callback(jsonErr, null, response);
}
}
return callback(null, successString ? successString : body, response);
});
} else if (this.promise) {
return new this.promise(function (resolve, reject) {
var req = requestLib(options);
var requestObj = null;
req.on('request', function (request) {
requestObj = request;
});
req.on('response', function (response) {
// Saving error
var error = response.statusCode < 200 || response.statusCode > 399;
// Collecting data
var body = [];
var push = body.push.bind(body);
response.on('data', push);
// Data collected
response.on('end', function () {
var result = body.join('');
// Parsing JSON
if (result[0] === '[' || result[0] === '{') {
try {
result = JSON.parse(result);
} catch (e) {
// nothing to do
}
}
if (error) {
response.body = result;
if (options.debug) {
reject({
result: JSON.stringify(response),
debug: {
options: options,
request: {
headers: requestObj._headers,
},
response: {
headers: response.headers,
},
}
});
} else {
reject(JSON.stringify(response));
}
return;
}
if (options.debug) {
resolve({
result,
debug: {
options: options,
request: {
headers: requestObj._headers,
},
response: {
headers: response.headers,
},
}
});
} else {
resolve(result);
}
});
});
req.on('error', reject);
});
}
};
}).call(JiraClient.prototype);
JiraClient.oauth_util = require('./lib/oauth_util');
exports.oauth_util = oauth_util;