jira-ruby 2.2.0 → 3.0.0.beta1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/ISSUE_TEMPLATE/bug_report.md +20 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
- data/.github/dependabot.yml +6 -0
- data/.github/workflows/CI.yml +28 -0
- data/.github/workflows/codeql.yml +100 -0
- data/.github/workflows/rubocop.yml +18 -0
- data/.rubocop.yml +188 -0
- data/Gemfile +11 -3
- data/Guardfile +2 -0
- data/README.md +121 -20
- data/Rakefile +4 -5
- data/jira-ruby.gemspec +11 -17
- data/lib/jira/base.rb +37 -28
- data/lib/jira/base_factory.rb +4 -1
- data/lib/jira/client.rb +65 -46
- data/lib/jira/has_many_proxy.rb +4 -2
- data/lib/jira/http_client.rb +18 -13
- data/lib/jira/http_error.rb +4 -0
- data/lib/jira/jwt_client.rb +18 -42
- data/lib/jira/oauth_client.rb +6 -3
- data/lib/jira/railtie.rb +2 -0
- data/lib/jira/request_client.rb +5 -1
- data/lib/jira/resource/agile.rb +7 -9
- data/lib/jira/resource/applinks.rb +5 -3
- data/lib/jira/resource/attachment.rb +43 -3
- data/lib/jira/resource/board.rb +5 -3
- data/lib/jira/resource/board_configuration.rb +2 -0
- data/lib/jira/resource/comment.rb +2 -0
- data/lib/jira/resource/component.rb +2 -0
- data/lib/jira/resource/createmeta.rb +3 -1
- data/lib/jira/resource/field.rb +9 -4
- data/lib/jira/resource/filter.rb +2 -0
- data/lib/jira/resource/issue.rb +35 -44
- data/lib/jira/resource/issue_picker_suggestions.rb +4 -1
- data/lib/jira/resource/issue_picker_suggestions_issue.rb +2 -0
- data/lib/jira/resource/issuelink.rb +2 -0
- data/lib/jira/resource/issuelinktype.rb +2 -0
- data/lib/jira/resource/issuetype.rb +2 -0
- data/lib/jira/resource/priority.rb +2 -0
- data/lib/jira/resource/project.rb +4 -2
- data/lib/jira/resource/rapidview.rb +5 -3
- data/lib/jira/resource/remotelink.rb +2 -0
- data/lib/jira/resource/resolution.rb +2 -0
- data/lib/jira/resource/serverinfo.rb +2 -0
- data/lib/jira/resource/sprint.rb +14 -23
- data/lib/jira/resource/status.rb +7 -1
- data/lib/jira/resource/status_category.rb +10 -0
- data/lib/jira/resource/suggested_issue.rb +2 -0
- data/lib/jira/resource/transition.rb +2 -0
- data/lib/jira/resource/user.rb +3 -1
- data/lib/jira/resource/version.rb +2 -0
- data/lib/jira/resource/watcher.rb +2 -1
- data/lib/jira/resource/webhook.rb +4 -2
- data/lib/jira/resource/worklog.rb +3 -2
- data/lib/jira/version.rb +3 -1
- data/lib/jira-ruby.rb +5 -3
- data/lib/tasks/generate.rake +4 -2
- data/spec/data/files/short.txt +1 -0
- data/spec/integration/attachment_spec.rb +3 -3
- data/spec/integration/comment_spec.rb +8 -8
- data/spec/integration/component_spec.rb +7 -7
- data/spec/integration/field_spec.rb +3 -3
- data/spec/integration/issue_spec.rb +20 -16
- data/spec/integration/issuelinktype_spec.rb +3 -3
- data/spec/integration/issuetype_spec.rb +3 -3
- data/spec/integration/priority_spec.rb +3 -3
- data/spec/integration/project_spec.rb +7 -7
- data/spec/integration/rapidview_spec.rb +9 -9
- data/spec/integration/resolution_spec.rb +3 -3
- data/spec/integration/status_category_spec.rb +20 -0
- data/spec/integration/status_spec.rb +4 -8
- data/spec/integration/transition_spec.rb +2 -2
- data/spec/integration/user_spec.rb +22 -8
- data/spec/integration/version_spec.rb +7 -7
- data/spec/integration/watcher_spec.rb +17 -18
- data/spec/integration/webhook.rb +5 -4
- data/spec/integration/worklog_spec.rb +8 -8
- data/spec/jira/base_factory_spec.rb +2 -1
- data/spec/jira/base_spec.rb +55 -41
- data/spec/jira/client_spec.rb +48 -34
- data/spec/jira/has_many_proxy_spec.rb +3 -3
- data/spec/jira/http_client_spec.rb +98 -26
- data/spec/jira/http_error_spec.rb +2 -2
- data/spec/jira/oauth_client_spec.rb +30 -8
- data/spec/jira/request_client_spec.rb +4 -4
- data/spec/jira/resource/agile_spec.rb +28 -28
- data/spec/jira/resource/attachment_spec.rb +142 -52
- data/spec/jira/resource/board_spec.rb +21 -20
- data/spec/jira/resource/createmeta_spec.rb +48 -48
- data/spec/jira/resource/field_spec.rb +30 -12
- data/spec/jira/resource/filter_spec.rb +4 -4
- data/spec/jira/resource/issue_picker_suggestions_spec.rb +18 -18
- data/spec/jira/resource/issue_spec.rb +44 -38
- data/spec/jira/resource/jira_picker_suggestions_issue_spec.rb +3 -3
- data/spec/jira/resource/project_factory_spec.rb +3 -2
- data/spec/jira/resource/project_spec.rb +16 -16
- data/spec/jira/resource/sprint_spec.rb +70 -3
- data/spec/jira/resource/status_spec.rb +21 -0
- data/spec/jira/resource/user_factory_spec.rb +4 -4
- data/spec/jira/resource/worklog_spec.rb +3 -3
- data/spec/mock_responses/sprint/1.json +13 -0
- data/spec/mock_responses/status/1.json +8 -1
- data/spec/mock_responses/status.json +40 -5
- data/spec/mock_responses/statuscategory/1.json +7 -0
- data/spec/mock_responses/statuscategory.json +30 -0
- data/spec/mock_responses/{user_username=admin.json → user_accountId=1234567890abcdef01234567.json} +2 -1
- data/spec/spec_helper.rb +1 -0
- data/spec/support/clients_helper.rb +3 -5
- data/spec/support/shared_examples/integration.rb +25 -28
- metadata +25 -257
- data/.travis.yml +0 -9
- data/example.rb +0 -232
- data/http-basic-example.rb +0 -113
- data/lib/jira/resource/sprint_report.rb +0 -8
- data/lib/jira/tasks.rb +0 -0
- data/spec/jira/jwt_uri_builder_spec.rb +0 -59
data/jira-ruby.gemspec
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
$LOAD_PATH.push File.expand_path('lib', __dir__)
|
|
2
4
|
require 'jira/version'
|
|
3
5
|
|
|
@@ -9,27 +11,19 @@ Gem::Specification.new do |s|
|
|
|
9
11
|
s.summary = 'Ruby Gem for use with the Atlassian JIRA REST API'
|
|
10
12
|
s.description = 'API for JIRA'
|
|
11
13
|
s.licenses = ['MIT']
|
|
12
|
-
s.metadata = {
|
|
14
|
+
s.metadata = {
|
|
15
|
+
'source_code_uri' => 'https://github.com/sumoheavy/jira-ruby',
|
|
16
|
+
'rubygems_mfa_required' => 'true'
|
|
17
|
+
}
|
|
13
18
|
|
|
14
|
-
s.required_ruby_version = '>= 1.
|
|
19
|
+
s.required_ruby_version = '>= 3.1.0'
|
|
15
20
|
|
|
16
21
|
s.files = `git ls-files`.split("\n")
|
|
17
|
-
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
|
18
22
|
s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
|
|
19
23
|
s.require_paths = ['lib']
|
|
20
24
|
|
|
21
|
-
|
|
22
|
-
s.
|
|
23
|
-
s.
|
|
24
|
-
s.
|
|
25
|
-
s.add_runtime_dependency 'oauth', '~> 0.5', '>= 0.5.0'
|
|
26
|
-
|
|
27
|
-
# Development Dependencies
|
|
28
|
-
s.add_development_dependency 'guard', '~> 2.13', '>= 2.13.0'
|
|
29
|
-
s.add_development_dependency 'guard-rspec', '~> 4.6', '>= 4.6.5'
|
|
30
|
-
s.add_development_dependency 'pry', '~> 0.10', '>= 0.10.3'
|
|
31
|
-
s.add_development_dependency 'railties'
|
|
32
|
-
s.add_development_dependency 'rake', '~> 10.3', '>= 10.3.2'
|
|
33
|
-
s.add_development_dependency 'rspec', '~> 3.0', '>= 3.0.0'
|
|
34
|
-
s.add_development_dependency 'webmock', '~> 1.18', '>= 1.18.0'
|
|
25
|
+
s.add_dependency 'activesupport'
|
|
26
|
+
s.add_dependency 'atlassian-jwt'
|
|
27
|
+
s.add_dependency 'multipart-post'
|
|
28
|
+
s.add_dependency 'oauth', '~> 1.0'
|
|
35
29
|
end
|
data/lib/jira/base.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'active_support/core_ext/string'
|
|
2
4
|
require 'active_support/inflector'
|
|
3
5
|
require 'set'
|
|
@@ -81,8 +83,8 @@ module JIRA
|
|
|
81
83
|
if options[relation]
|
|
82
84
|
instance_variable_set("@#{relation}", options[relation])
|
|
83
85
|
instance_variable_set("@#{relation}_id", options[relation].key_value)
|
|
84
|
-
elsif options["#{relation}_id"
|
|
85
|
-
instance_variable_set("@#{relation}_id", options["#{relation}_id"
|
|
86
|
+
elsif options[:"#{relation}_id"]
|
|
87
|
+
instance_variable_set("@#{relation}_id", options[:"#{relation}_id"])
|
|
86
88
|
else
|
|
87
89
|
raise ArgumentError, "Required option #{relation.inspect} missing" unless options[relation]
|
|
88
90
|
end
|
|
@@ -96,7 +98,7 @@ module JIRA
|
|
|
96
98
|
json = parse_json(response.body)
|
|
97
99
|
json = json[endpoint_name.pluralize] if collection_attributes_are_nested
|
|
98
100
|
json.map do |attrs|
|
|
99
|
-
new(client, { attrs:
|
|
101
|
+
new(client, { attrs: }.merge(options))
|
|
100
102
|
end
|
|
101
103
|
end
|
|
102
104
|
|
|
@@ -111,7 +113,7 @@ module JIRA
|
|
|
111
113
|
# Builds a new instance of the resource with the given attributes.
|
|
112
114
|
# These attributes will be posted to the JIRA Api if save is called.
|
|
113
115
|
def self.build(client, attrs)
|
|
114
|
-
new(client, attrs:
|
|
116
|
+
new(client, attrs:)
|
|
115
117
|
end
|
|
116
118
|
|
|
117
119
|
# Returns the name of this resource for use in URL components.
|
|
@@ -141,7 +143,7 @@ module JIRA
|
|
|
141
143
|
# JIRA::Resource::Comment.singular_path('456','/issue/123/')
|
|
142
144
|
# # => /jira/rest/api/2/issue/123/comment/456
|
|
143
145
|
def self.singular_path(client, key, prefix = '/')
|
|
144
|
-
collection_path(client, prefix)
|
|
146
|
+
"#{collection_path(client, prefix)}/#{key}"
|
|
145
147
|
end
|
|
146
148
|
|
|
147
149
|
# Returns the attribute name of the attribute used for find.
|
|
@@ -193,10 +195,11 @@ module JIRA
|
|
|
193
195
|
# # => Looks for {"foo":{"bar":{"baz":{"child":{}}}}}
|
|
194
196
|
def self.has_one(resource, options = {})
|
|
195
197
|
attribute_key = options[:attribute_key] || resource.to_s
|
|
196
|
-
child_class = options[:class] ||
|
|
198
|
+
child_class = options[:class] || "JIRA::Resource::#{resource.to_s.classify}".constantize
|
|
197
199
|
define_method(resource) do
|
|
198
200
|
attribute = maybe_nested_attribute(attribute_key, options[:nested_under])
|
|
199
201
|
return nil unless attribute
|
|
202
|
+
|
|
200
203
|
child_class.new(client, attrs: attribute)
|
|
201
204
|
end
|
|
202
205
|
end
|
|
@@ -244,7 +247,7 @@ module JIRA
|
|
|
244
247
|
# # => Looks for {"foo":{"bar":{"baz":{"children":{}}}}}
|
|
245
248
|
def self.has_many(collection, options = {})
|
|
246
249
|
attribute_key = options[:attribute_key] || collection.to_s
|
|
247
|
-
child_class = options[:class] ||
|
|
250
|
+
child_class = options[:class] || "JIRA::Resource::#{collection.to_s.classify}".constantize
|
|
248
251
|
self_class_basename = name.split('::').last.downcase.to_sym
|
|
249
252
|
define_method(collection) do
|
|
250
253
|
child_class_options = { self_class_basename => self }
|
|
@@ -322,7 +325,7 @@ module JIRA
|
|
|
322
325
|
# issue it returns '/issue'
|
|
323
326
|
def path_component
|
|
324
327
|
path_component = "/#{self.class.endpoint_name}"
|
|
325
|
-
path_component +=
|
|
328
|
+
path_component += "/#{key_value}" if key_value
|
|
326
329
|
path_component
|
|
327
330
|
end
|
|
328
331
|
|
|
@@ -331,6 +334,7 @@ module JIRA
|
|
|
331
334
|
# is not set
|
|
332
335
|
def fetch(reload = false, query_params = {})
|
|
333
336
|
return if expanded? && !reload
|
|
337
|
+
|
|
334
338
|
response = client.get(url_with_query_params(url, query_params))
|
|
335
339
|
set_attrs_from_response(response)
|
|
336
340
|
@expanded = true
|
|
@@ -359,14 +363,14 @@ module JIRA
|
|
|
359
363
|
def save(attrs, path = url)
|
|
360
364
|
begin
|
|
361
365
|
save_status = save!(attrs, path)
|
|
362
|
-
rescue JIRA::HTTPError =>
|
|
366
|
+
rescue JIRA::HTTPError => e
|
|
363
367
|
begin
|
|
364
|
-
set_attrs_from_response(
|
|
368
|
+
set_attrs_from_response(e.response) # Merge error status generated by JIRA REST API
|
|
365
369
|
rescue JSON::ParserError => parse_exception
|
|
366
370
|
set_attrs('exception' => {
|
|
367
|
-
'class' =>
|
|
368
|
-
'code' =>
|
|
369
|
-
'message' =>
|
|
371
|
+
'class' => e.response.class.name,
|
|
372
|
+
'code' => e.response.code,
|
|
373
|
+
'message' => e.response.message
|
|
370
374
|
})
|
|
371
375
|
end
|
|
372
376
|
# raise exception
|
|
@@ -378,10 +382,10 @@ module JIRA
|
|
|
378
382
|
# Sets the attributes hash from a HTTPResponse object from JIRA if it is
|
|
379
383
|
# not nil or is not a json response.
|
|
380
384
|
def set_attrs_from_response(response)
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
+
return if response.body.nil? || (response.body.length < 2)
|
|
386
|
+
json = self.class.parse_json(response.body)
|
|
387
|
+
set_attrs(json)
|
|
388
|
+
|
|
385
389
|
end
|
|
386
390
|
|
|
387
391
|
# Set the current attributes from a hash. If clobber is true, any existing
|
|
@@ -419,7 +423,7 @@ module JIRA
|
|
|
419
423
|
prefix = '/'
|
|
420
424
|
unless self.class.belongs_to_relationships.empty?
|
|
421
425
|
prefix = self.class.belongs_to_relationships.inject(prefix) do |prefix_so_far, relationship|
|
|
422
|
-
prefix_so_far
|
|
426
|
+
"#{prefix_so_far}#{relationship}/#{send("#{relationship}_id")}/"
|
|
423
427
|
end
|
|
424
428
|
end
|
|
425
429
|
if @attrs['self']
|
|
@@ -434,7 +438,8 @@ module JIRA
|
|
|
434
438
|
end
|
|
435
439
|
|
|
436
440
|
# This method fixes issue that there is no / prefix in url. It is happened when we call for instance
|
|
437
|
-
# Looks like this issue is actual only in case if you use atlassian sdk your app
|
|
441
|
+
# Looks like this issue is actual only in case if you use atlassian sdk your app pathis not root
|
|
442
|
+
# (like /jira in example below)
|
|
438
443
|
# issue.save() for existing resource.
|
|
439
444
|
# As a result we got error 400 from JIRA API:
|
|
440
445
|
# [07/Jun/2015:15:32:19 +0400] "PUT jira/rest/api/2/issue/10111 HTTP/1.1" 400 -
|
|
@@ -443,6 +448,7 @@ module JIRA
|
|
|
443
448
|
def patched_url
|
|
444
449
|
result = url
|
|
445
450
|
return result if result.start_with?('/', 'http')
|
|
451
|
+
|
|
446
452
|
"/#{result}"
|
|
447
453
|
end
|
|
448
454
|
|
|
@@ -476,15 +482,18 @@ module JIRA
|
|
|
476
482
|
|
|
477
483
|
def self.maybe_nested_attribute(attributes, attribute_name, nested_under = nil)
|
|
478
484
|
return attributes[attribute_name] if nested_under.nil?
|
|
485
|
+
|
|
479
486
|
if nested_under.instance_of? Array
|
|
480
487
|
final = nested_under.inject(attributes) do |parent, key|
|
|
481
488
|
break if parent.nil?
|
|
489
|
+
|
|
482
490
|
parent[key]
|
|
483
491
|
end
|
|
484
492
|
return nil if final.nil?
|
|
493
|
+
|
|
485
494
|
final[attribute_name]
|
|
486
495
|
else
|
|
487
|
-
|
|
496
|
+
attributes[nested_under][attribute_name]
|
|
488
497
|
end
|
|
489
498
|
end
|
|
490
499
|
|
|
@@ -493,10 +502,10 @@ module JIRA
|
|
|
493
502
|
end
|
|
494
503
|
|
|
495
504
|
def self.url_with_query_params(url, query_params)
|
|
496
|
-
if
|
|
497
|
-
"#{url}?#{hash_to_query_string query_params}"
|
|
498
|
-
else
|
|
505
|
+
if query_params.empty?
|
|
499
506
|
url
|
|
507
|
+
else
|
|
508
|
+
"#{url}?#{hash_to_query_string query_params}"
|
|
500
509
|
end
|
|
501
510
|
end
|
|
502
511
|
|
|
@@ -506,20 +515,20 @@ module JIRA
|
|
|
506
515
|
|
|
507
516
|
def self.hash_to_query_string(query_params)
|
|
508
517
|
query_params.map do |k, v|
|
|
509
|
-
CGI.escape(k.to_s)
|
|
518
|
+
"#{CGI.escape(k.to_s)}=#{CGI.escape(v.to_s)}"
|
|
510
519
|
end.join('&')
|
|
511
520
|
end
|
|
512
521
|
|
|
513
522
|
def self.query_params_for_single_fetch(options)
|
|
514
|
-
|
|
523
|
+
options.select do |k, _v|
|
|
515
524
|
QUERY_PARAMS_FOR_SINGLE_FETCH.include? k
|
|
516
|
-
end
|
|
525
|
+
end.to_h
|
|
517
526
|
end
|
|
518
527
|
|
|
519
528
|
def self.query_params_for_search(options)
|
|
520
|
-
|
|
529
|
+
options.select do |k, _v|
|
|
521
530
|
QUERY_PARAMS_FOR_SEARCH.include? k
|
|
522
|
-
end
|
|
531
|
+
end.to_h
|
|
523
532
|
end
|
|
524
533
|
end
|
|
525
534
|
end
|
data/lib/jira/base_factory.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module JIRA
|
|
2
4
|
# This is the base class for all the JIRA resource factory instances.
|
|
3
5
|
class BaseFactory
|
|
@@ -36,7 +38,8 @@ module JIRA
|
|
|
36
38
|
# The principle purpose of this class is to delegate methods to the corresponding
|
|
37
39
|
# non-factory class and automatically prepend the client argument to the argument
|
|
38
40
|
# list.
|
|
39
|
-
delegate_to_target_class :all, :find, :collection_path, :singular_path, :jql, :get_backlog_issues,
|
|
41
|
+
delegate_to_target_class :all, :find, :collection_path, :singular_path, :jql, :get_backlog_issues,
|
|
42
|
+
:get_board_issues, :get_sprints, :get_sprint_issues, :get_projects, :get_projects_full
|
|
40
43
|
|
|
41
44
|
# This method needs special handling as it has a default argument value
|
|
42
45
|
def build(attrs = {})
|
data/lib/jira/client.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'json'
|
|
2
4
|
require 'forwardable'
|
|
3
5
|
require 'ostruct'
|
|
@@ -34,12 +36,14 @@ module JIRA
|
|
|
34
36
|
# :default_headers => {},
|
|
35
37
|
# :use_client_cert => false,
|
|
36
38
|
# :read_timeout => nil,
|
|
39
|
+
# :max_retries => nil,
|
|
37
40
|
# :http_debug => false,
|
|
38
41
|
# :shared_secret => nil,
|
|
39
42
|
# :cert_path => nil,
|
|
40
43
|
# :key_path => nil,
|
|
41
44
|
# :ssl_client_cert => nil,
|
|
42
45
|
# :ssl_client_key => nil
|
|
46
|
+
# :ca_file => nil
|
|
43
47
|
#
|
|
44
48
|
# See the JIRA::Base class methods for all of the available methods on these accessor
|
|
45
49
|
# objects.
|
|
@@ -56,43 +60,45 @@ module JIRA
|
|
|
56
60
|
# The configuration options for this client instance
|
|
57
61
|
attr_reader :options
|
|
58
62
|
|
|
59
|
-
def_delegators :@request_client, :init_access_token, :set_access_token, :set_request_token, :request_token,
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
63
|
+
def_delegators :@request_client, :init_access_token, :set_access_token, :set_request_token, :request_token,
|
|
64
|
+
:access_token, :authenticated?
|
|
65
|
+
|
|
66
|
+
DEFINED_OPTIONS = %i[
|
|
67
|
+
site
|
|
68
|
+
context_path
|
|
69
|
+
signature_method
|
|
70
|
+
request_token_path
|
|
71
|
+
authorize_path
|
|
72
|
+
access_token_path
|
|
73
|
+
private_key
|
|
74
|
+
private_key_file
|
|
75
|
+
rest_base_path
|
|
76
|
+
consumer_key
|
|
77
|
+
consumer_secret
|
|
78
|
+
ssl_verify_mode
|
|
79
|
+
ssl_version
|
|
80
|
+
use_ssl
|
|
81
|
+
username
|
|
82
|
+
password
|
|
83
|
+
auth_type
|
|
84
|
+
proxy_address
|
|
85
|
+
proxy_port
|
|
86
|
+
proxy_username
|
|
87
|
+
proxy_password
|
|
88
|
+
use_cookies
|
|
89
|
+
additional_cookies
|
|
90
|
+
default_headers
|
|
91
|
+
use_client_cert
|
|
92
|
+
read_timeout
|
|
93
|
+
max_retries
|
|
94
|
+
http_debug
|
|
95
|
+
issuer
|
|
96
|
+
base_url
|
|
97
|
+
shared_secret
|
|
98
|
+
cert_path
|
|
99
|
+
key_path
|
|
100
|
+
ssl_client_cert
|
|
101
|
+
ssl_client_key
|
|
96
102
|
].freeze
|
|
97
103
|
|
|
98
104
|
DEFAULT_OPTIONS = {
|
|
@@ -116,11 +122,20 @@ module JIRA
|
|
|
116
122
|
raise ArgumentError, "Unknown option(s) given: #{unknown_options}" unless unknown_options.empty?
|
|
117
123
|
|
|
118
124
|
if options[:use_client_cert]
|
|
119
|
-
|
|
125
|
+
if @options[:cert_path]
|
|
126
|
+
@options[:ssl_client_cert] =
|
|
127
|
+
OpenSSL::X509::Certificate.new(File.read(@options[:cert_path]))
|
|
128
|
+
end
|
|
120
129
|
@options[:ssl_client_key] = OpenSSL::PKey::RSA.new(File.read(@options[:key_path])) if @options[:key_path]
|
|
121
130
|
|
|
122
|
-
|
|
123
|
-
|
|
131
|
+
unless @options[:ssl_client_cert]
|
|
132
|
+
raise ArgumentError,
|
|
133
|
+
'Options: :cert_path or :ssl_client_cert must be set when :use_client_cert is true'
|
|
134
|
+
end
|
|
135
|
+
unless @options[:ssl_client_key]
|
|
136
|
+
raise ArgumentError,
|
|
137
|
+
'Options: :key_path or :ssl_client_key must be set when :use_client_cert is true'
|
|
138
|
+
end
|
|
124
139
|
end
|
|
125
140
|
|
|
126
141
|
case options[:auth_type]
|
|
@@ -132,7 +147,11 @@ module JIRA
|
|
|
132
147
|
when :basic
|
|
133
148
|
@request_client = HttpClient.new(@options)
|
|
134
149
|
when :cookie
|
|
135
|
-
|
|
150
|
+
if @options.key?(:use_cookies) && !@options[:use_cookies]
|
|
151
|
+
raise ArgumentError,
|
|
152
|
+
'Options: :use_cookies must be true for :cookie authorization type'
|
|
153
|
+
end
|
|
154
|
+
|
|
136
155
|
@options[:use_cookies] = true
|
|
137
156
|
@request_client = HttpClient.new(@options)
|
|
138
157
|
@request_client.make_cookie_auth_request
|
|
@@ -181,6 +200,10 @@ module JIRA
|
|
|
181
200
|
JIRA::Resource::StatusFactory.new(self)
|
|
182
201
|
end
|
|
183
202
|
|
|
203
|
+
def StatusCategory # :nodoc:
|
|
204
|
+
JIRA::Resource::StatusCategoryFactory.new(self)
|
|
205
|
+
end
|
|
206
|
+
|
|
184
207
|
def Resolution # :nodoc:
|
|
185
208
|
JIRA::Resource::ResolutionFactory.new(self)
|
|
186
209
|
end
|
|
@@ -225,10 +248,6 @@ module JIRA
|
|
|
225
248
|
JIRA::Resource::SprintFactory.new(self)
|
|
226
249
|
end
|
|
227
250
|
|
|
228
|
-
def SprintReport
|
|
229
|
-
JIRA::Resource::SprintReportFactory.new(self)
|
|
230
|
-
end
|
|
231
|
-
|
|
232
251
|
def ServerInfo
|
|
233
252
|
JIRA::Resource::ServerInfoFactory.new(self)
|
|
234
253
|
end
|
|
@@ -290,7 +309,7 @@ module JIRA
|
|
|
290
309
|
|
|
291
310
|
def post_multipart(path, file, headers = {})
|
|
292
311
|
puts "post multipart: #{path} - [#{file}]" if @http_debug
|
|
293
|
-
@request_client.request_multipart(path, file, headers)
|
|
312
|
+
@request_client.request_multipart(path, file, merge_default_headers(headers))
|
|
294
313
|
end
|
|
295
314
|
|
|
296
315
|
def put(path, body = '', headers = {})
|
data/lib/jira/has_many_proxy.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
#
|
|
2
4
|
# Whenever a collection from a has_many relationship is accessed, an instance
|
|
3
5
|
# of this class is returned. This instance wraps the Array of instances in
|
|
@@ -36,7 +38,7 @@ class JIRA::HasManyProxy
|
|
|
36
38
|
end
|
|
37
39
|
|
|
38
40
|
# Delegate any missing methods to the collection that this proxy wraps
|
|
39
|
-
def method_missing(method_name,
|
|
40
|
-
collection.send(method_name,
|
|
41
|
+
def method_missing(method_name, ...)
|
|
42
|
+
collection.send(method_name, ...)
|
|
41
43
|
end
|
|
42
44
|
end
|
data/lib/jira/http_client.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'json'
|
|
2
4
|
require 'net/https'
|
|
3
5
|
require 'cgi/cookie'
|
|
@@ -21,7 +23,7 @@ module JIRA
|
|
|
21
23
|
body = { username: @options[:username].to_s, password: @options[:password].to_s }.to_json
|
|
22
24
|
@options.delete(:username)
|
|
23
25
|
@options.delete(:password)
|
|
24
|
-
make_request(:post, @options[:context_path]
|
|
26
|
+
make_request(:post, "#{@options[:context_path]}/rest/auth/1/session", body, 'Content-Type' => 'application/json')
|
|
25
27
|
end
|
|
26
28
|
|
|
27
29
|
def make_request(http_method, url, body = '', headers = {})
|
|
@@ -45,12 +47,13 @@ module JIRA
|
|
|
45
47
|
end
|
|
46
48
|
|
|
47
49
|
def http_conn(uri)
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
50
|
+
http_conn =
|
|
51
|
+
if @options[:proxy_address]
|
|
52
|
+
Net::HTTP.new(uri.host, uri.port, @options[:proxy_address], @options[:proxy_port] || 80,
|
|
53
|
+
@options[:proxy_username], @options[:proxy_password])
|
|
54
|
+
else
|
|
55
|
+
Net::HTTP.new(uri.host, uri.port)
|
|
56
|
+
end
|
|
54
57
|
http_conn.use_ssl = @options[:use_ssl]
|
|
55
58
|
if @options[:use_client_cert]
|
|
56
59
|
http_conn.cert = @options[:ssl_client_cert]
|
|
@@ -59,6 +62,8 @@ module JIRA
|
|
|
59
62
|
http_conn.verify_mode = @options[:ssl_verify_mode]
|
|
60
63
|
http_conn.ssl_version = @options[:ssl_version] if @options[:ssl_version]
|
|
61
64
|
http_conn.read_timeout = @options[:read_timeout]
|
|
65
|
+
http_conn.max_retries = @options[:max_retries] if @options[:max_retries]
|
|
66
|
+
http_conn.ca_file = @options[:ca_file] if @options[:ca_file]
|
|
62
67
|
http_conn
|
|
63
68
|
end
|
|
64
69
|
|
|
@@ -93,13 +98,13 @@ module JIRA
|
|
|
93
98
|
|
|
94
99
|
def store_cookies(response)
|
|
95
100
|
cookies = response.get_fields('set-cookie')
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
end
|
|
101
|
+
return unless cookies
|
|
102
|
+
cookies.each do |cookie|
|
|
103
|
+
data = CGI::Cookie.parse(cookie)
|
|
104
|
+
data.delete('Path')
|
|
105
|
+
@cookies.merge!(data)
|
|
102
106
|
end
|
|
107
|
+
|
|
103
108
|
end
|
|
104
109
|
|
|
105
110
|
def add_cookies(request)
|
data/lib/jira/http_error.rb
CHANGED
data/lib/jira/jwt_client.rb
CHANGED
|
@@ -1,67 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'atlassian/jwt'
|
|
2
4
|
|
|
3
5
|
module JIRA
|
|
4
6
|
class JwtClient < HttpClient
|
|
5
7
|
def make_request(http_method, url, body = '', headers = {})
|
|
6
8
|
@http_method = http_method
|
|
9
|
+
jwt_header = build_jwt_header(url)
|
|
7
10
|
|
|
8
|
-
super(http_method, url, body, headers)
|
|
11
|
+
super(http_method, url, body, headers.merge(jwt_header))
|
|
9
12
|
end
|
|
10
13
|
|
|
11
14
|
def make_multipart_request(url, data, headers = {})
|
|
12
15
|
@http_method = :post
|
|
16
|
+
jwt_header = build_jwt_header(url)
|
|
13
17
|
|
|
14
|
-
super(url, data, headers)
|
|
18
|
+
super(url, data, headers.merge(jwt_header))
|
|
15
19
|
end
|
|
16
20
|
|
|
17
|
-
|
|
18
|
-
attr_reader :request_url, :http_method, :shared_secret, :site, :issuer
|
|
19
|
-
|
|
20
|
-
def initialize(request_url, http_method, shared_secret, site, issuer)
|
|
21
|
-
@request_url = request_url
|
|
22
|
-
@http_method = http_method
|
|
23
|
-
@shared_secret = shared_secret
|
|
24
|
-
@site = site
|
|
25
|
-
@issuer = issuer
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def build
|
|
29
|
-
uri = URI.parse(request_url)
|
|
30
|
-
new_query = URI.decode_www_form(String(uri.query)) << ['jwt', jwt_header]
|
|
31
|
-
uri.query = URI.encode_www_form(new_query)
|
|
32
|
-
|
|
33
|
-
return uri.to_s unless uri.is_a?(URI::HTTP)
|
|
34
|
-
|
|
35
|
-
uri.request_uri
|
|
36
|
-
end
|
|
21
|
+
private
|
|
37
22
|
|
|
38
|
-
|
|
23
|
+
attr_reader :http_method
|
|
39
24
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
issuer,
|
|
43
|
-
request_url,
|
|
44
|
-
http_method.to_s,
|
|
45
|
-
site,
|
|
46
|
-
(Time.now - 60).to_i,
|
|
47
|
-
(Time.now + 86_400).to_i
|
|
25
|
+
def build_jwt_header(url)
|
|
26
|
+
jwt = build_jwt(url)
|
|
48
27
|
|
|
49
|
-
|
|
50
|
-
end
|
|
28
|
+
{ 'Authorization' => "JWT #{jwt}" }
|
|
51
29
|
end
|
|
52
30
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
def request_path(url)
|
|
58
|
-
JwtUriBuilder.new(
|
|
31
|
+
def build_jwt(url)
|
|
32
|
+
claim = Atlassian::Jwt.build_claims \
|
|
33
|
+
@options[:issuer],
|
|
59
34
|
url,
|
|
60
35
|
http_method.to_s,
|
|
61
|
-
@options[:shared_secret],
|
|
62
36
|
@options[:site],
|
|
63
|
-
|
|
64
|
-
|
|
37
|
+
(Time.now - 60).to_i,
|
|
38
|
+
(Time.now + 86_400).to_i
|
|
39
|
+
|
|
40
|
+
JWT.encode claim, @options[:shared_secret]
|
|
65
41
|
end
|
|
66
42
|
end
|
|
67
43
|
end
|
data/lib/jira/oauth_client.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'oauth'
|
|
2
4
|
require 'json'
|
|
3
5
|
require 'forwardable'
|
|
@@ -45,8 +47,8 @@ module JIRA
|
|
|
45
47
|
|
|
46
48
|
# Returns the current request token if it is set, else it creates
|
|
47
49
|
# and sets a new token.
|
|
48
|
-
def request_token(options = {},
|
|
49
|
-
@request_token ||= get_request_token(options,
|
|
50
|
+
def request_token(options = {}, ...)
|
|
51
|
+
@request_token ||= get_request_token(options, ...)
|
|
50
52
|
end
|
|
51
53
|
|
|
52
54
|
# Sets the request token from a given token and secret.
|
|
@@ -71,6 +73,7 @@ module JIRA
|
|
|
71
73
|
# JIRA::Client::UninitializedAccessTokenError exception if it is not set.
|
|
72
74
|
def access_token
|
|
73
75
|
raise UninitializedAccessTokenError unless @access_token
|
|
76
|
+
|
|
74
77
|
@access_token
|
|
75
78
|
end
|
|
76
79
|
|
|
@@ -82,7 +85,7 @@ module JIRA
|
|
|
82
85
|
uri.query = if uri.query.to_s == ''
|
|
83
86
|
oauth_params_str
|
|
84
87
|
else
|
|
85
|
-
uri.query
|
|
88
|
+
"#{uri.query}&#{oauth_params_str}"
|
|
86
89
|
end
|
|
87
90
|
url = uri.to_s
|
|
88
91
|
end
|
data/lib/jira/railtie.rb
CHANGED
data/lib/jira/request_client.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'oauth'
|
|
2
4
|
require 'json'
|
|
3
5
|
require 'net/https'
|
|
@@ -11,12 +13,14 @@ module JIRA
|
|
|
11
13
|
def request(*args)
|
|
12
14
|
response = make_request(*args)
|
|
13
15
|
raise HTTPError, response unless response.is_a?(Net::HTTPSuccess)
|
|
16
|
+
|
|
14
17
|
response
|
|
15
18
|
end
|
|
16
19
|
|
|
17
20
|
def request_multipart(*args)
|
|
18
21
|
response = make_multipart_request(*args)
|
|
19
22
|
raise HTTPError, response unless response.is_a?(Net::HTTPSuccess)
|
|
23
|
+
|
|
20
24
|
response
|
|
21
25
|
end
|
|
22
26
|
|
|
@@ -28,4 +32,4 @@ module JIRA
|
|
|
28
32
|
raise NotImplementedError
|
|
29
33
|
end
|
|
30
34
|
end
|
|
31
|
-
end
|
|
35
|
+
end
|