Source: index.js

  1. "use strict";
  2. // Core packages
  3. const url = require('url');
  4. // Npm packages
  5. const request = require('request');
  6. const jwt = require('atlassian-jwt');
  7. const queryString = require('query-string');
  8. // Custom packages
  9. var applicationProperties = require('./api/application-properties');
  10. var attachment = require('./api/attachment');
  11. var auditing = require('./api/auditing');
  12. var auth = require('./api/auth');
  13. var avatar = require('./api/avatar');
  14. var backlog = require('./api/backlog');
  15. var board = require('./api/board');
  16. var comment = require('./api/comment');
  17. var component = require('./api/component');
  18. var customFieldOption = require('./api/customFieldOption');
  19. var dashboard = require('./api/dashboard');
  20. const developmentInformation = require('./api/developmentInformation');
  21. var epic = require('./api/epic');
  22. var errorStrings = require('./lib/error');
  23. var field = require('./api/field');
  24. var filter = require('./api/filter');
  25. var group = require('./api/group');
  26. var groupUserPicker = require('./api/groupUserPicker');
  27. var groups = require('./api/groups');
  28. var issue = require('./api/issue');
  29. var issueLink = require('./api/issueLink');
  30. var issueLinkType = require('./api/issueLinkType');
  31. var issueType = require('./api/issueType');
  32. var jql = require('./api/jql');
  33. var labels = require('./api/labels');
  34. var licenseRole = require('./api/licenseRole');
  35. var licenseValidator = require('./api/licenseValidator');
  36. var myPermissions = require('./api/myPermissions');
  37. var myPreferences = require('./api/myPreferences');
  38. var myself = require('./api/myself');
  39. var oauth_util = require('./lib/oauth_util');
  40. var password = require('./api/password');
  41. var permissions = require('./api/permissions');
  42. var permissionScheme = require('./api/permission-scheme');
  43. var priority = require('./api/priority');
  44. var project = require('./api/project');
  45. var projectCategory = require('./api/projectCategory');
  46. var projectValidate = require('./api/projectValidate');
  47. var reindex = require('./api/reindex');
  48. var resolution = require('./api/resolution');
  49. var roles = require('./api/roles');
  50. var screens = require('./api/screens');
  51. var search = require('./api/search');
  52. var securityLevel = require('./api/securityLevel');
  53. var serverInfo = require('./api/serverInfo');
  54. var settings = require('./api/settings');
  55. var sprint = require('./api/sprint');
  56. var status = require('./api/status');
  57. var statusCategory = require('./api/statusCategory');
  58. var user = require('./api/user');
  59. var version = require('./api/version');
  60. var webhook = require('./api/webhook');
  61. var workflow = require('./api/workflow');
  62. var workflowScheme = require('./api/workflowScheme');
  63. var worklog = require('./api/worklog');
  64. /**
  65. * @callback callback
  66. * @param {any} err
  67. * @param {any} data
  68. * @returns {void}
  69. */
  70. /**
  71. * Represents a client for the Jira REST API
  72. *
  73. * @constructor JiraClient
  74. * @property {AgileBoardClient} board
  75. * @property {AgileSprintClient} sprint
  76. *
  77. * @property {ApplicationPropertiesClient} applicationProperties
  78. * @property {AttachmentClient} attachment
  79. * @property {AuditingClient} auditing
  80. * @property {AuthClient} auth
  81. * @property {AvatarClient} avatar
  82. * @property {CommentClient} comment
  83. * @property {ComponentClient} component
  84. * @property {CustomFieldOptionClient} customFieldOption
  85. * @property {DashboardClient} dashboard
  86. * @property {DevelopmentInformationClient} developmentInformation
  87. * @property {FieldClient} field
  88. * @property {FilterClient} filter
  89. * @property {GroupClient} group
  90. * @property {GroupUserPickerClient} groupUserPicker
  91. * @property {GroupsClient} groups
  92. * @property {IssueClient} issue
  93. * @property {IssueLinkClient} issueLink
  94. * @property {IssueLinkTypeClient} issueLinkType
  95. * @property {IssueTypeClient} issueType
  96. * @property {JqlClient} jql
  97. * @property {LabelsClient} labels
  98. * @property {LicenseRoleClient} licenseRole
  99. * @property {LicenseValidatorClient} licenseValidator
  100. * @property {MyPermissionsClient} myPermissions
  101. * @property {MyPreferencesClient} myPreferences
  102. * @property {MyselfClient} myself
  103. * @property {PasswordClient} password
  104. * @property {PermissionsClient} permissions
  105. * @property {PermissionSchemeClient} permissionScheme
  106. * @property {PriorityClient} priority
  107. * @property {ProjectCategoryClient} projectCategory
  108. * @property {ProjectClient} project
  109. * @property {ProjectValidateClient} projectValidate
  110. * @property {ReindexClient} reindex
  111. * @property {ResolutionClient} resolution
  112. * @property {RoleClient} roles
  113. * @property {ScreensClient} screens
  114. * @property {SearchClient} search
  115. * @property {SecurityLevelClient} securityLevel
  116. * @property {ServerInfoClient} serverInfo
  117. * @property {SettingsClient} settings
  118. * @property {StatusCategoryClient} statusCategory
  119. * @property {StatusClient} status
  120. * @property {UserClient} user
  121. * @property {VersionClient} version
  122. * @property {WebhookClient} webhook
  123. * @property {WorkflowClient} workflow
  124. * @property {WorkflowSchemeClient} workflowScheme
  125. * @property {WorklogClient} worklog
  126. *
  127. * @param config The information needed to access the Jira API
  128. * @param {string} config.host The hostname of the Jira API.
  129. * @param {number} [config.timeout] request timeout (milliseconds)
  130. * @param {string} [config.protocol=https] The protocol used to accses the Jira API.
  131. * @param {number} [config.port=443] The port number used to connect to Jira.
  132. * @param {string} [config.path_prefix="/"] The prefix to use in front of the path, if Jira isn't at "/"
  133. * @param {boolean} [config.strictSSL=true] It is recommended not to turn it off for no reason (https://github.com/request/request/issues/251).
  134. * @param {number | string} [config.version=2] The version of the Jira API to which you will be connecting. Currently, only
  135. * version 2 is supported.
  136. * @param {Object} [config.basic_auth] The authentication information used tp connect to Jira. Must contain EITHER username and password
  137. * OR oauth information. Oauth information will be used over username/password authentication.
  138. * @param {Object} [config.basic_auth.base64] base64 that contains email:api_token.
  139. * @param {string} [config.basic_auth.username] (DEPRECATED) The username of the user that will be authenticated. MUST be included
  140. * if using username and password authentication.
  141. * @param {string} [config.basic_auth.password] (DEPRECATED) The password of the user that will be authenticated. MUST be included
  142. * if using username and password authentication.
  143. * @param {string} [config.basic_auth.email] The email of the user that will be authenticated. MUST be included
  144. * if using email and api_token authentication.
  145. * @param {string} [config.basic_auth.api_token] The api token of the user that will be authenticated. MUST be included
  146. * if using email and api_token authentication.
  147. * @param {string} [config.oauth.consumer_key] The consumer key used in the Jira Application Link for oauth
  148. * authentication. MUST be included if using OAuth.
  149. * @param {string} [config.oauth.private_key] The private key used for OAuth security. MUST be included if using OAuth.
  150. * @param {string} [config.oauth.token] The VERIFIED token used to connect to the Jira API. MUST be included if using
  151. * OAuth.
  152. * @param {string} [config.oauth.token_secret] The secret for the above token. MUST be included if using Oauth.
  153. * @param {Object} [config.jwt] The JWT configuration object that contains iss:secret
  154. * @param {string} config.jwt.iss The Jira app key (can be found in the app descriptor). MUST be included
  155. * @param {string} config.jwt.secret The JWT secret token. MUST be included
  156. * @param {string} [config.jwt.expiry_time_seconds] The JWT token expiry time in seconds. OPTIONAL (default 180 seconds)
  157. * @param {CookieJar} [config.cookie_jar] The CookieJar to use for every requests.
  158. * @param {Promise} [config.promise] Any function (constructor) compatible with Promise (bluebird, Q,...).
  159. * Default - native Promise.
  160. * @param {Request} [config.request] Any function (constructor) compatible with Request (request, supertest,...).
  161. * Default - require('request').
  162. */
  163. var JiraClient = module.exports = function (config) {
  164. if (!config.host) {
  165. throw new Error(errorStrings.NO_HOST_ERROR);
  166. }
  167. this.host = config.host;
  168. this.timeout = config.timeout;
  169. this.protocol = config.protocol ? config.protocol : 'https';
  170. this.path_prefix = config.path_prefix ? config.path_prefix : '/';
  171. this.port = config.port;
  172. this.apiVersion = 2;
  173. this.strictSSL = config.hasOwnProperty('strictSSL') ? config.strictSSL : true;
  174. this.agileApiVersion = '1.0';
  175. this.authApiVersion = '1';
  176. this.webhookApiVersion = '1.0';
  177. this.promise = config.promise || Promise;
  178. this.requestLib = config.request || request;
  179. this.rejectUnauthorized = config.rejectUnauthorized;
  180. if (config.oauth) {
  181. if (!config.oauth.consumer_key) {
  182. throw new Error(errorStrings.NO_CONSUMER_KEY_ERROR);
  183. } else if (!config.oauth.private_key) {
  184. throw new Error(errorStrings.NO_PRIVATE_KEY_ERROR);
  185. } else if (!config.oauth.token) {
  186. throw new Error(errorStrings.NO_OAUTH_TOKEN_ERROR);
  187. } else if (!config.oauth.token_secret) {
  188. throw new Error(errorStrings.NO_OAUTH_TOKEN_SECRET_ERROR);
  189. }
  190. this.oauthConfig = config.oauth;
  191. this.oauthConfig.signature_method = 'RSA-SHA1';
  192. } else if (config.basic_auth) {
  193. if (config.basic_auth.base64) {
  194. this.basic_auth = {
  195. base64: config.basic_auth.base64
  196. }
  197. } else if (config.basic_auth.api_token || config.basic_auth.email) {
  198. if (!config.basic_auth.email) {
  199. throw new Error(errorStrings.NO_EMAIL_ERROR);
  200. } else if (!config.basic_auth.api_token) {
  201. throw new Error(errorStrings.NO_APITOKEN_ERROR);
  202. }
  203. this.basic_auth = {
  204. user: config.basic_auth.email,
  205. pass: config.basic_auth.api_token
  206. };
  207. } else {
  208. if (!config.basic_auth.username) {
  209. throw new Error(errorStrings.NO_USERNAME_ERROR);
  210. } else if (!config.basic_auth.password) {
  211. throw new Error(errorStrings.NO_PASSWORD_ERROR);
  212. }
  213. this.basic_auth = {
  214. user: config.basic_auth.username,
  215. pass: config.basic_auth.password
  216. };
  217. }
  218. } else if (config.jwt) {
  219. if (config.jwt.secret && config.jwt.iss) {
  220. this.jwt = {
  221. iss: config.jwt.iss,
  222. secret: config.jwt.secret,
  223. expiry_time_seconds: config.jwt.expiry_time_seconds || 3 * 60
  224. };
  225. } else {
  226. if (!config.jwt.secret) {
  227. throw new Error(errorStrings.NO_JWT_SECRET_KEY_ERROR);
  228. } else if (!config.jwt.iss) {
  229. throw new Error(errorStrings.NO_JWT_ISS_KEY_ERROR);
  230. }
  231. }
  232. }
  233. if (config.cookie_jar) {
  234. this.cookie_jar = config.cookie_jar;
  235. }
  236. this.applicationProperties = new applicationProperties(this);
  237. this.attachment = new attachment(this);
  238. this.auditing = new auditing(this);
  239. this.auth = new auth(this);
  240. this.avatar = new avatar(this);
  241. this.backlog = new backlog(this);
  242. this.board = new board(this);
  243. this.comment = new comment(this);
  244. this.component = new component(this);
  245. this.customFieldOption = new customFieldOption(this);
  246. this.dashboard = new dashboard(this);
  247. this.developmentInformation = new developmentInformation(this);
  248. this.epic = new epic(this);
  249. this.field = new field(this);
  250. this.filter = new filter(this);
  251. this.group = new group(this);
  252. this.groupUserPicker = new groupUserPicker(this);
  253. this.groups = new groups(this);
  254. this.issue = new issue(this);
  255. this.issueLink = new issueLink(this);
  256. this.issueLinkType = new issueLinkType(this);
  257. this.issueType = new issueType(this);
  258. this.jql = new jql(this);
  259. this.labels = new labels(this);
  260. this.licenseRole = new licenseRole(this);
  261. this.licenseValidator = new licenseValidator(this);
  262. this.myPermissions = new myPermissions(this);
  263. this.myPreferences = new myPreferences(this);
  264. this.myself = new myself(this);
  265. this.password = new password(this);
  266. this.permissions = new permissions(this);
  267. this.permissionScheme = new permissionScheme(this);
  268. this.priority = new priority(this);
  269. this.project = new project(this);
  270. this.projectCategory = new projectCategory(this);
  271. this.projectValidate = new projectValidate(this);
  272. this.reindex = new reindex(this);
  273. this.resolution = new resolution(this);
  274. this.roles = new roles(this);
  275. this.screens = new screens(this);
  276. this.search = new search(this);
  277. this.securityLevel = new securityLevel(this);
  278. this.serverInfo = new serverInfo(this);
  279. this.settings = new settings(this);
  280. this.sprint = new sprint(this);
  281. this.status = new status(this);
  282. this.statusCategory = new statusCategory(this);
  283. this.user = new user(this);
  284. this.version = new version(this);
  285. this.webhook = new webhook(this);
  286. this.workflow = new workflow(this);
  287. this.workflowScheme = new workflowScheme(this);
  288. this.worklog = new worklog(this);
  289. };
  290. (function () {
  291. /**
  292. * Simple utility to build a REST endpoint URL for the Jira API.
  293. *
  294. * @method buildURL
  295. * @memberOf JiraClient#
  296. * @param path The path of the URL without concern for the root of the REST API.
  297. * @param {string | number} [forcedVersion] Use this param to force a particular version
  298. * @returns {string} The constructed URL.
  299. */
  300. this.buildURL = function (path, forcedVersion) {
  301. var apiBasePath = this.path_prefix + 'rest/api/';
  302. var version = forcedVersion || this.apiVersion;
  303. var requestUrl = url.format({
  304. protocol: this.protocol,
  305. hostname: this.host,
  306. port: this.port,
  307. pathname: apiBasePath + version + path
  308. });
  309. return decodeURIComponent(requestUrl);
  310. };
  311. /**
  312. * Simple utility to build a REST endpoint URL for the Jira API without prefixes.
  313. *
  314. * @method buildAbstractURL
  315. * @memberOf JiraClient#
  316. * @param path The path of the URL without concern for the root of the REST API.
  317. * @returns {string} The constructed URL.
  318. */
  319. this.buildAbstractURL = function(path) {
  320. const apiBasePath = this.path_prefix + 'rest/';
  321. const requestUrl = url.format({
  322. protocol: this.protocol,
  323. hostname: this.host,
  324. port: this.port,
  325. pathname: apiBasePath + path
  326. });
  327. return decodeURIComponent(requestUrl);
  328. }
  329. /**
  330. * Simple utility to build a REST endpoint URL for the Jira Agile API.
  331. *
  332. * @method buildAgileURL
  333. * @memberOf JiraClient#
  334. * @param path The path of the URL without concern for the root of the REST API.
  335. * @param {string | number} [forcedVersion] Use this param to force a particular version
  336. * @returns {string} The constructed URL.
  337. */
  338. this.buildAgileURL = function (path, forcedVersion) {
  339. var apiBasePath = this.path_prefix + 'rest/agile/';
  340. var version = forcedVersion || this.agileApiVersion;
  341. var requestUrl = url.format({
  342. protocol: this.protocol,
  343. hostname: this.host,
  344. port: this.port,
  345. pathname: apiBasePath + version + path
  346. });
  347. return decodeURIComponent(requestUrl);
  348. };
  349. /**
  350. * Simple utility to build a REST endpoint URL for the Jira Auth API.
  351. *
  352. * @method buildAuthURL
  353. * @memberOf JiraClient#
  354. * @param path The path of the URL without concern for the root of the REST API.
  355. * @param {string | number} [forcedVersion] Use this param to force a particular version
  356. * @returns {string} The constructed URL.
  357. */
  358. this.buildAuthURL = function (path, forcedVersion) {
  359. var apiBasePath = this.path_prefix + 'rest/auth/';
  360. var version = forcedVersion || this.authApiVersion;
  361. var requestUrl = url.format({
  362. protocol: this.protocol,
  363. hostname: this.host,
  364. port: this.port,
  365. pathname: apiBasePath + version + path
  366. });
  367. return decodeURIComponent(requestUrl);
  368. };
  369. /**
  370. * Simple utility to build a REST endpoint URL for the Jira webhook API.
  371. *
  372. * @method buildWebhookURL
  373. * @memberOf JiraClient#
  374. * @param path The path of the URL without concern for the root of the REST API.
  375. * @param {string | number} [forcedVersion] Use this param to force a particular version
  376. * @returns {string} The constructed URL.
  377. */
  378. this.buildWebhookURL = function (path, forcedVersion) {
  379. var apiBasePath = this.path_prefix + 'rest/webhooks/';
  380. var version = forcedVersion || this.webhookApiVersion;
  381. var requestUrl = url.format({
  382. protocol: this.protocol,
  383. hostname: this.host,
  384. port: this.port,
  385. pathname: apiBasePath + version + path
  386. });
  387. return decodeURIComponent(requestUrl);
  388. };
  389. /**
  390. * Make a request to the Jira API and call back with it's response.
  391. *
  392. * @method makeRequest
  393. * @memberOf JiraClient#
  394. * @param options The request options.
  395. * @param {callback} [callback] Called with the APIs response.
  396. * @param {string} [successString] If supplied, this is reported instead of the response body.
  397. * @return {Promise} Resolved with APIs response or rejected with error
  398. */
  399. this.makeRequest = function (options, callback, successString) {
  400. let requestLib = this.requestLib;
  401. options.rejectUnauthorized = this.rejectUnauthorized;
  402. options.strictSSL = this.strictSSL;
  403. options.timeout = this.timeout;
  404. if (this.oauthConfig) {
  405. options.oauth = this.oauthConfig;
  406. } else if (this.basic_auth) {
  407. if (this.basic_auth.base64) {
  408. if (!options.headers) {
  409. options.headers = {}
  410. }
  411. options.headers['Authorization'] = 'Basic ' + this.basic_auth.base64
  412. } else {
  413. options.auth = this.basic_auth;
  414. }
  415. } else if (this.jwt) {
  416. const pathname = new URL(options.uri).pathname;
  417. const nowInSeconds = Math.floor(Date.now() / 1000);
  418. const queryParam = queryString.parse(queryString.stringify(options.qs));
  419. const jwtToken = jwt.encode({
  420. iss: this.jwt.iss,
  421. iat: nowInSeconds,
  422. exp: nowInSeconds + this.jwt.expiry_time_seconds,
  423. qsh: jwt.createQueryStringHash({
  424. method: options.method,
  425. pathname,
  426. query: queryParam || {}
  427. })
  428. }, this.jwt.secret);
  429. if (!options.headers) {
  430. options.headers = {};
  431. }
  432. options.headers['Authorization'] = `JWT ${jwtToken}`;
  433. }
  434. if (this.cookie_jar) {
  435. options.jar = this.cookie_jar;
  436. }
  437. if (callback) {
  438. requestLib(options, function (err, response, body) {
  439. if (
  440. err ||
  441. response.statusCode < 200 ||
  442. response.statusCode > 399
  443. ) {
  444. return callback(err ? err : body, null, response);
  445. }
  446. if (typeof body === 'string') {
  447. try {
  448. body = JSON.parse(body);
  449. } catch (jsonErr) {
  450. return callback(jsonErr, null, response);
  451. }
  452. }
  453. return callback(null, successString ? successString : body, response);
  454. });
  455. } else if (this.promise) {
  456. return new this.promise(function (resolve, reject) {
  457. var req = requestLib(options);
  458. var requestObj = null;
  459. req.on('request', function (request) {
  460. requestObj = request;
  461. });
  462. req.on('response', function (response) {
  463. // Saving error
  464. var error = response.statusCode < 200 || response.statusCode > 399;
  465. // Collecting data
  466. var body = [];
  467. var push = body.push.bind(body);
  468. response.on('data', push);
  469. // Data collected
  470. response.on('end', function () {
  471. var result = body.join('');
  472. // Parsing JSON
  473. if (result[0] === '[' || result[0] === '{') {
  474. try {
  475. result = JSON.parse(result);
  476. } catch (e) {
  477. // nothing to do
  478. }
  479. }
  480. if (error) {
  481. response.body = result;
  482. if (options.debug) {
  483. reject({
  484. result: JSON.stringify(response),
  485. debug: {
  486. options: options,
  487. request: {
  488. headers: requestObj._headers,
  489. },
  490. response: {
  491. headers: response.headers,
  492. },
  493. }
  494. });
  495. } else {
  496. reject(JSON.stringify(response));
  497. }
  498. return;
  499. }
  500. if (options.debug) {
  501. resolve({
  502. result,
  503. debug: {
  504. options: options,
  505. request: {
  506. headers: requestObj._headers,
  507. },
  508. response: {
  509. headers: response.headers,
  510. },
  511. }
  512. });
  513. } else {
  514. resolve(result);
  515. }
  516. });
  517. });
  518. req.on('error', reject);
  519. });
  520. }
  521. };
  522. }).call(JiraClient.prototype);
  523. JiraClient.oauth_util = require('./lib/oauth_util');
  524. exports.oauth_util = oauth_util;