zk-jira-ruby 2.1.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (154) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +13 -0
  3. data/.travis.yml +9 -0
  4. data/Gemfile +14 -0
  5. data/Guardfile +14 -0
  6. data/LICENSE +19 -0
  7. data/README.md +427 -0
  8. data/Rakefile +31 -0
  9. data/example.rb +232 -0
  10. data/http-basic-example.rb +113 -0
  11. data/jira-ruby.gemspec +35 -0
  12. data/lib/jira/base.rb +525 -0
  13. data/lib/jira/base_factory.rb +46 -0
  14. data/lib/jira/client.rb +315 -0
  15. data/lib/jira/has_many_proxy.rb +42 -0
  16. data/lib/jira/http_client.rb +112 -0
  17. data/lib/jira/http_error.rb +14 -0
  18. data/lib/jira/jwt_client.rb +67 -0
  19. data/lib/jira/oauth_client.rb +114 -0
  20. data/lib/jira/railtie.rb +10 -0
  21. data/lib/jira/request_client.rb +31 -0
  22. data/lib/jira/resource/agile.rb +79 -0
  23. data/lib/jira/resource/applinks.rb +39 -0
  24. data/lib/jira/resource/attachment.rb +50 -0
  25. data/lib/jira/resource/board.rb +91 -0
  26. data/lib/jira/resource/board_configuration.rb +9 -0
  27. data/lib/jira/resource/comment.rb +12 -0
  28. data/lib/jira/resource/component.rb +8 -0
  29. data/lib/jira/resource/createmeta.rb +44 -0
  30. data/lib/jira/resource/field.rb +83 -0
  31. data/lib/jira/resource/filter.rb +15 -0
  32. data/lib/jira/resource/issue.rb +141 -0
  33. data/lib/jira/resource/issuelink.rb +20 -0
  34. data/lib/jira/resource/issuelinktype.rb +14 -0
  35. data/lib/jira/resource/issuetype.rb +8 -0
  36. data/lib/jira/resource/priority.rb +8 -0
  37. data/lib/jira/resource/project.rb +41 -0
  38. data/lib/jira/resource/rapidview.rb +67 -0
  39. data/lib/jira/resource/remotelink.rb +26 -0
  40. data/lib/jira/resource/resolution.rb +8 -0
  41. data/lib/jira/resource/serverinfo.rb +18 -0
  42. data/lib/jira/resource/sprint.rb +105 -0
  43. data/lib/jira/resource/sprint_report.rb +8 -0
  44. data/lib/jira/resource/status.rb +8 -0
  45. data/lib/jira/resource/transition.rb +29 -0
  46. data/lib/jira/resource/user.rb +30 -0
  47. data/lib/jira/resource/version.rb +66 -0
  48. data/lib/jira/resource/watcher.rb +35 -0
  49. data/lib/jira/resource/webhook.rb +37 -0
  50. data/lib/jira/resource/worklog.rb +14 -0
  51. data/lib/jira/tasks.rb +0 -0
  52. data/lib/jira/version.rb +3 -0
  53. data/lib/jira-ruby.rb +49 -0
  54. data/lib/tasks/generate.rake +18 -0
  55. data/spec/integration/attachment_spec.rb +32 -0
  56. data/spec/integration/comment_spec.rb +52 -0
  57. data/spec/integration/component_spec.rb +39 -0
  58. data/spec/integration/field_spec.rb +32 -0
  59. data/spec/integration/issue_spec.rb +93 -0
  60. data/spec/integration/issuelinktype_spec.rb +26 -0
  61. data/spec/integration/issuetype_spec.rb +24 -0
  62. data/spec/integration/priority_spec.rb +24 -0
  63. data/spec/integration/project_spec.rb +49 -0
  64. data/spec/integration/rapidview_spec.rb +74 -0
  65. data/spec/integration/resolution_spec.rb +26 -0
  66. data/spec/integration/status_spec.rb +24 -0
  67. data/spec/integration/transition_spec.rb +49 -0
  68. data/spec/integration/user_spec.rb +41 -0
  69. data/spec/integration/version_spec.rb +39 -0
  70. data/spec/integration/watcher_spec.rb +62 -0
  71. data/spec/integration/webhook.rb +25 -0
  72. data/spec/integration/worklog_spec.rb +51 -0
  73. data/spec/jira/base_factory_spec.rb +45 -0
  74. data/spec/jira/base_spec.rb +598 -0
  75. data/spec/jira/client_spec.rb +291 -0
  76. data/spec/jira/has_many_proxy_spec.rb +46 -0
  77. data/spec/jira/http_client_spec.rb +328 -0
  78. data/spec/jira/http_error_spec.rb +24 -0
  79. data/spec/jira/jwt_uri_builder_spec.rb +59 -0
  80. data/spec/jira/oauth_client_spec.rb +162 -0
  81. data/spec/jira/request_client_spec.rb +41 -0
  82. data/spec/jira/resource/agile_spec.rb +135 -0
  83. data/spec/jira/resource/attachment_spec.rb +138 -0
  84. data/spec/jira/resource/board_spec.rb +224 -0
  85. data/spec/jira/resource/createmeta_spec.rb +258 -0
  86. data/spec/jira/resource/field_spec.rb +85 -0
  87. data/spec/jira/resource/filter_spec.rb +97 -0
  88. data/spec/jira/resource/issue_spec.rb +227 -0
  89. data/spec/jira/resource/issuelink_spec.rb +14 -0
  90. data/spec/jira/resource/project_factory_spec.rb +11 -0
  91. data/spec/jira/resource/project_spec.rb +123 -0
  92. data/spec/jira/resource/sprint_spec.rb +90 -0
  93. data/spec/jira/resource/user_factory_spec.rb +31 -0
  94. data/spec/jira/resource/worklog_spec.rb +22 -0
  95. data/spec/mock_responses/board/1.json +33 -0
  96. data/spec/mock_responses/board/1_issues.json +62 -0
  97. data/spec/mock_responses/component/10000.invalid.put.json +5 -0
  98. data/spec/mock_responses/component/10000.json +39 -0
  99. data/spec/mock_responses/component/10000.put.json +39 -0
  100. data/spec/mock_responses/component.post.json +28 -0
  101. data/spec/mock_responses/empty_issues.json +8 -0
  102. data/spec/mock_responses/field/1.json +15 -0
  103. data/spec/mock_responses/field.json +32 -0
  104. data/spec/mock_responses/issue/10002/attachments/10000.json +20 -0
  105. data/spec/mock_responses/issue/10002/comment/10000.json +29 -0
  106. data/spec/mock_responses/issue/10002/comment/10000.put.json +29 -0
  107. data/spec/mock_responses/issue/10002/comment.json +65 -0
  108. data/spec/mock_responses/issue/10002/comment.post.json +29 -0
  109. data/spec/mock_responses/issue/10002/transitions.json +49 -0
  110. data/spec/mock_responses/issue/10002/transitions.post.json +1 -0
  111. data/spec/mock_responses/issue/10002/watchers.json +13 -0
  112. data/spec/mock_responses/issue/10002/worklog/10000.json +31 -0
  113. data/spec/mock_responses/issue/10002/worklog/10000.put.json +30 -0
  114. data/spec/mock_responses/issue/10002/worklog.json +98 -0
  115. data/spec/mock_responses/issue/10002/worklog.post.json +30 -0
  116. data/spec/mock_responses/issue/10002.invalid.put.json +6 -0
  117. data/spec/mock_responses/issue/10002.json +126 -0
  118. data/spec/mock_responses/issue/10002.put.missing_field_update.json +6 -0
  119. data/spec/mock_responses/issue.json +1108 -0
  120. data/spec/mock_responses/issue.post.json +5 -0
  121. data/spec/mock_responses/issueLinkType/10000.json +7 -0
  122. data/spec/mock_responses/issueLinkType.json +25 -0
  123. data/spec/mock_responses/issuetype/5.json +8 -0
  124. data/spec/mock_responses/issuetype.json +42 -0
  125. data/spec/mock_responses/jira/rest/webhooks/1.0/webhook/2.json +11 -0
  126. data/spec/mock_responses/jira/rest/webhooks/1.0/webhook.json +11 -0
  127. data/spec/mock_responses/priority/1.json +8 -0
  128. data/spec/mock_responses/priority.json +42 -0
  129. data/spec/mock_responses/project/SAMPLEPROJECT.issues.json +1108 -0
  130. data/spec/mock_responses/project/SAMPLEPROJECT.json +84 -0
  131. data/spec/mock_responses/project.json +12 -0
  132. data/spec/mock_responses/rapidview/SAMPLEPROJECT.issues.full.json +276 -0
  133. data/spec/mock_responses/rapidview/SAMPLEPROJECT.issues.json +111 -0
  134. data/spec/mock_responses/rapidview/SAMPLEPROJECT.json +6 -0
  135. data/spec/mock_responses/rapidview.json +10 -0
  136. data/spec/mock_responses/resolution/1.json +7 -0
  137. data/spec/mock_responses/resolution.json +15 -0
  138. data/spec/mock_responses/sprint/1_issues.json +125 -0
  139. data/spec/mock_responses/status/1.json +7 -0
  140. data/spec/mock_responses/status.json +37 -0
  141. data/spec/mock_responses/user_username=admin.json +17 -0
  142. data/spec/mock_responses/version/10000.invalid.put.json +5 -0
  143. data/spec/mock_responses/version/10000.json +11 -0
  144. data/spec/mock_responses/version/10000.put.json +7 -0
  145. data/spec/mock_responses/version.post.json +7 -0
  146. data/spec/mock_responses/webhook/webhook.json +11 -0
  147. data/spec/mock_responses/webhook.json +11 -0
  148. data/spec/spec_helper.rb +21 -0
  149. data/spec/support/clients_helper.rb +16 -0
  150. data/spec/support/matchers/have_attributes.rb +11 -0
  151. data/spec/support/matchers/have_many.rb +9 -0
  152. data/spec/support/matchers/have_one.rb +5 -0
  153. data/spec/support/shared_examples/integration.rb +177 -0
  154. metadata +491 -0
@@ -0,0 +1,46 @@
1
+ module JIRA
2
+ # This is the base class for all the JIRA resource factory instances.
3
+ class BaseFactory
4
+ attr_reader :client
5
+
6
+ def initialize(client)
7
+ @client = client
8
+ end
9
+
10
+ # Return the name of the class which this factory generates, i.e.
11
+ # JIRA::Resource::FooFactory creates JIRA::Resource::Foo instances.
12
+ def target_class
13
+ # Need to do a little bit of work here as Module.const_get doesn't work
14
+ # with nested class names, i.e. JIRA::Resource::Foo.
15
+ #
16
+ # So create a method chain from the class components. This code will
17
+ # unroll to:
18
+ # Module.const_get('JIRA').const_get('Resource').const_get('Foo')
19
+ #
20
+ target_class_name = self.class.name.sub(/Factory$/, '')
21
+ class_components = target_class_name.split('::')
22
+
23
+ class_components.inject(Module) do |mod, const_name|
24
+ mod.const_get(const_name)
25
+ end
26
+ end
27
+
28
+ def self.delegate_to_target_class(*method_names)
29
+ method_names.each do |method_name|
30
+ define_method method_name do |*args|
31
+ target_class.send(method_name, @client, *args)
32
+ end
33
+ end
34
+ end
35
+
36
+ # The principle purpose of this class is to delegate methods to the corresponding
37
+ # non-factory class and automatically prepend the client argument to the argument
38
+ # list.
39
+ delegate_to_target_class :all, :find, :collection_path, :singular_path, :jql, :get_backlog_issues, :get_board_issues, :get_sprints, :get_sprint_issues, :get_projects, :get_projects_full
40
+
41
+ # This method needs special handling as it has a default argument value
42
+ def build(attrs = {})
43
+ target_class.build(@client, attrs)
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,315 @@
1
+ require 'json'
2
+ require 'forwardable'
3
+ require 'ostruct'
4
+
5
+ module JIRA
6
+ # This class is the main access point for all JIRA::Resource instances.
7
+ #
8
+ # The client must be initialized with an options hash containing
9
+ # configuration options. The available options are:
10
+ #
11
+ # :site => 'http://localhost:2990',
12
+ # :context_path => '/jira',
13
+ # :signature_method => 'RSA-SHA1',
14
+ # :request_token_path => "/plugins/servlet/oauth/request-token",
15
+ # :authorize_path => "/plugins/servlet/oauth/authorize",
16
+ # :access_token_path => "/plugins/servlet/oauth/access-token",
17
+ # :private_key => nil,
18
+ # :private_key_file => "rsakey.pem",
19
+ # :rest_base_path => "/rest/api/2",
20
+ # :consumer_key => nil,
21
+ # :consumer_secret => nil,
22
+ # :ssl_verify_mode => OpenSSL::SSL::VERIFY_PEER,
23
+ # :ssl_version => nil,
24
+ # :use_ssl => true,
25
+ # :username => nil,
26
+ # :password => nil,
27
+ # :auth_type => :oauth,
28
+ # :proxy_address => nil,
29
+ # :proxy_port => nil,
30
+ # :proxy_username => nil,
31
+ # :proxy_password => nil,
32
+ # :use_cookies => nil,
33
+ # :additional_cookies => nil,
34
+ # :default_headers => {},
35
+ # :use_client_cert => false,
36
+ # :read_timeout => nil,
37
+ # :http_debug => false,
38
+ # :shared_secret => nil,
39
+ # :cert_path => nil,
40
+ # :key_path => nil,
41
+ # :ssl_client_cert => nil,
42
+ # :ssl_client_key => nil
43
+ #
44
+ # See the JIRA::Base class methods for all of the available methods on these accessor
45
+ # objects.
46
+
47
+ class Client
48
+ extend Forwardable
49
+
50
+ # The OAuth::Consumer instance returned by the OauthClient
51
+ #
52
+ # The authenticated client instance returned by the respective client type
53
+ # (Oauth, Basic)
54
+ attr_accessor :consumer, :request_client, :http_debug, :cache
55
+
56
+ # The configuration options for this client instance
57
+ attr_reader :options
58
+
59
+ def_delegators :@request_client, :init_access_token, :set_access_token, :set_request_token, :request_token, :access_token, :authenticated?
60
+
61
+ DEFINED_OPTIONS = [
62
+ :site,
63
+ :context_path,
64
+ :signature_method,
65
+ :request_token_path,
66
+ :authorize_path,
67
+ :access_token_path,
68
+ :private_key,
69
+ :private_key_file,
70
+ :rest_base_path,
71
+ :consumer_key,
72
+ :consumer_secret,
73
+ :ssl_verify_mode,
74
+ :ssl_version,
75
+ :use_ssl,
76
+ :username,
77
+ :password,
78
+ :auth_type,
79
+ :proxy_address,
80
+ :proxy_port,
81
+ :proxy_username,
82
+ :proxy_password,
83
+ :use_cookies,
84
+ :additional_cookies,
85
+ :default_headers,
86
+ :use_client_cert,
87
+ :read_timeout,
88
+ :http_debug,
89
+ :issuer,
90
+ :base_url,
91
+ :shared_secret,
92
+ :cert_path,
93
+ :key_path,
94
+ :ssl_client_cert,
95
+ :ssl_client_key
96
+ ].freeze
97
+
98
+ DEFAULT_OPTIONS = {
99
+ site: 'http://localhost:2990',
100
+ context_path: '/jira',
101
+ rest_base_path: '/rest/api/2',
102
+ ssl_verify_mode: OpenSSL::SSL::VERIFY_PEER,
103
+ use_ssl: true,
104
+ use_client_cert: false,
105
+ auth_type: :oauth,
106
+ http_debug: false,
107
+ default_headers: {}
108
+ }.freeze
109
+
110
+ def initialize(options = {})
111
+ options = DEFAULT_OPTIONS.merge(options)
112
+ @options = options
113
+ @options[:rest_base_path] = @options[:context_path] + @options[:rest_base_path]
114
+
115
+ unknown_options = options.keys.reject { |o| DEFINED_OPTIONS.include?(o) }
116
+ raise ArgumentError, "Unknown option(s) given: #{unknown_options}" unless unknown_options.empty?
117
+
118
+ if options[:use_client_cert]
119
+ @options[:ssl_client_cert] = OpenSSL::X509::Certificate.new(File.read(@options[:cert_path])) if @options[:cert_path]
120
+ @options[:ssl_client_key] = OpenSSL::PKey::RSA.new(File.read(@options[:key_path])) if @options[:key_path]
121
+
122
+ raise ArgumentError, 'Options: :cert_path or :ssl_client_cert must be set when :use_client_cert is true' unless @options[:ssl_client_cert]
123
+ raise ArgumentError, 'Options: :key_path or :ssl_client_key must be set when :use_client_cert is true' unless @options[:ssl_client_key]
124
+ end
125
+
126
+ case options[:auth_type]
127
+ when :oauth, :oauth_2legged
128
+ @request_client = OauthClient.new(@options)
129
+ @consumer = @request_client.consumer
130
+ when :jwt
131
+ @request_client = JwtClient.new(@options)
132
+ when :basic
133
+ @request_client = HttpClient.new(@options)
134
+ when :cookie
135
+ raise ArgumentError, 'Options: :use_cookies must be true for :cookie authorization type' if @options.key?(:use_cookies) && !@options[:use_cookies]
136
+ @options[:use_cookies] = true
137
+ @request_client = HttpClient.new(@options)
138
+ @request_client.make_cookie_auth_request
139
+ @options.delete(:username)
140
+ @options.delete(:password)
141
+ else
142
+ raise ArgumentError, 'Options: ":auth_type" must be ":oauth",":oauth_2legged", ":cookie" or ":basic"'
143
+ end
144
+
145
+ @http_debug = @options[:http_debug]
146
+
147
+ @options.freeze
148
+
149
+ @cache = OpenStruct.new
150
+ end
151
+
152
+ def Project # :nodoc:
153
+ JIRA::Resource::ProjectFactory.new(self)
154
+ end
155
+
156
+ def Issue # :nodoc:
157
+ JIRA::Resource::IssueFactory.new(self)
158
+ end
159
+
160
+ def Filter # :nodoc:
161
+ JIRA::Resource::FilterFactory.new(self)
162
+ end
163
+
164
+ def Component # :nodoc:
165
+ JIRA::Resource::ComponentFactory.new(self)
166
+ end
167
+
168
+ def User # :nodoc:
169
+ JIRA::Resource::UserFactory.new(self)
170
+ end
171
+
172
+ def Issuetype # :nodoc:
173
+ JIRA::Resource::IssuetypeFactory.new(self)
174
+ end
175
+
176
+ def Priority # :nodoc:
177
+ JIRA::Resource::PriorityFactory.new(self)
178
+ end
179
+
180
+ def Status # :nodoc:
181
+ JIRA::Resource::StatusFactory.new(self)
182
+ end
183
+
184
+ def Resolution # :nodoc:
185
+ JIRA::Resource::ResolutionFactory.new(self)
186
+ end
187
+
188
+ def Comment # :nodoc:
189
+ JIRA::Resource::CommentFactory.new(self)
190
+ end
191
+
192
+ def Attachment # :nodoc:
193
+ JIRA::Resource::AttachmentFactory.new(self)
194
+ end
195
+
196
+ def Worklog # :nodoc:
197
+ JIRA::Resource::WorklogFactory.new(self)
198
+ end
199
+
200
+ def Version # :nodoc:
201
+ JIRA::Resource::VersionFactory.new(self)
202
+ end
203
+
204
+ def Transition # :nodoc:
205
+ JIRA::Resource::TransitionFactory.new(self)
206
+ end
207
+
208
+ def Field # :nodoc:
209
+ JIRA::Resource::FieldFactory.new(self)
210
+ end
211
+
212
+ def Board
213
+ JIRA::Resource::BoardFactory.new(self)
214
+ end
215
+
216
+ def BoardConfiguration
217
+ JIRA::Resource::BoardConfigurationFactory.new(self)
218
+ end
219
+
220
+ def RapidView
221
+ JIRA::Resource::RapidViewFactory.new(self)
222
+ end
223
+
224
+ def Sprint
225
+ JIRA::Resource::SprintFactory.new(self)
226
+ end
227
+
228
+ def SprintReport
229
+ JIRA::Resource::SprintReportFactory.new(self)
230
+ end
231
+
232
+ def ServerInfo
233
+ JIRA::Resource::ServerInfoFactory.new(self)
234
+ end
235
+
236
+ def Createmeta
237
+ JIRA::Resource::CreatemetaFactory.new(self)
238
+ end
239
+
240
+ def ApplicationLink
241
+ JIRA::Resource::ApplicationLinkFactory.new(self)
242
+ end
243
+
244
+ def Watcher
245
+ JIRA::Resource::WatcherFactory.new(self)
246
+ end
247
+
248
+ def Webhook
249
+ JIRA::Resource::WebhookFactory.new(self)
250
+ end
251
+
252
+ def Issuelink
253
+ JIRA::Resource::IssuelinkFactory.new(self)
254
+ end
255
+
256
+ def Issuelinktype
257
+ JIRA::Resource::IssuelinktypeFactory.new(self)
258
+ end
259
+
260
+ def Remotelink
261
+ JIRA::Resource::RemotelinkFactory.new(self)
262
+ end
263
+
264
+ def Agile
265
+ JIRA::Resource::AgileFactory.new(self)
266
+ end
267
+
268
+ # HTTP methods without a body
269
+ def delete(path, headers = {})
270
+ request(:delete, path, nil, merge_default_headers(headers))
271
+ end
272
+
273
+ def get(path, headers = {})
274
+ request(:get, path, nil, merge_default_headers(headers))
275
+ end
276
+
277
+ def head(path, headers = {})
278
+ request(:head, path, nil, merge_default_headers(headers))
279
+ end
280
+
281
+ # HTTP methods with a body
282
+ def post(path, body = '', headers = {})
283
+ headers = { 'Content-Type' => 'application/json' }.merge(headers)
284
+ request(:post, path, body, merge_default_headers(headers))
285
+ end
286
+
287
+ def post_multipart(path, file, headers = {})
288
+ puts "post multipart: #{path} - [#{file}]" if @http_debug
289
+ @request_client.request_multipart(path, file, headers)
290
+ end
291
+
292
+ def put(path, body = '', headers = {})
293
+ headers = { 'Content-Type' => 'application/json' }.merge(headers)
294
+ request(:put, path, body, merge_default_headers(headers))
295
+ end
296
+
297
+ # Sends the specified HTTP request to the REST API through the
298
+ # appropriate method (oauth, basic).
299
+ def request(http_method, path, body = '', headers = {})
300
+ puts "#{http_method}: #{path} - [#{body}]" if @http_debug
301
+ @request_client.request(http_method, path, body, headers)
302
+ end
303
+
304
+ # Stops sensitive client information from being displayed in logs
305
+ def inspect
306
+ "#<JIRA::Client:#{object_id}>"
307
+ end
308
+
309
+ protected
310
+
311
+ def merge_default_headers(headers)
312
+ { 'Accept' => 'application/json' }.merge(@options[:default_headers]).merge(headers)
313
+ end
314
+ end
315
+ end
@@ -0,0 +1,42 @@
1
+ #
2
+ # Whenever a collection from a has_many relationship is accessed, an instance
3
+ # of this class is returned. This instance wraps the Array of instances in
4
+ # the collection with an extra build method, which allows new instances to be
5
+ # built on the collection with the correct properties.
6
+ #
7
+ # In practice, instances of this class behave exactly like an Array.
8
+ #
9
+ class JIRA::HasManyProxy
10
+ attr_reader :target_class, :parent
11
+ attr_accessor :collection
12
+
13
+ def initialize(parent, target_class, collection = [])
14
+ @parent = parent
15
+ @target_class = target_class
16
+ @collection = collection
17
+ end
18
+
19
+ # Builds an instance of this class with the correct parent.
20
+ # For example, issue.comments.build(attrs) will initialize a
21
+ # comment as follows:
22
+ #
23
+ # JIRA::Resource::Comment.new(issue.client,
24
+ # :attrs => attrs,
25
+ # :issue => issue)
26
+ def build(attrs = {})
27
+ resource = target_class.new(parent.client, :attrs => attrs, parent.to_sym => parent)
28
+ collection << resource
29
+ resource
30
+ end
31
+
32
+ # Forces an HTTP request to fetch all instances of the target class that
33
+ # are associated with the parent
34
+ def all
35
+ target_class.all(parent.client, parent.to_sym => parent)
36
+ end
37
+
38
+ # Delegate any missing methods to the collection that this proxy wraps
39
+ def method_missing(method_name, *args, &block)
40
+ collection.send(method_name, *args, &block)
41
+ end
42
+ end
@@ -0,0 +1,112 @@
1
+ require 'json'
2
+ require 'net/https'
3
+ require 'cgi/cookie'
4
+ require 'uri'
5
+
6
+ module JIRA
7
+ class HttpClient < RequestClient
8
+ DEFAULT_OPTIONS = {
9
+ username: nil,
10
+ password: nil
11
+ }.freeze
12
+
13
+ attr_reader :options
14
+
15
+ def initialize(options)
16
+ @options = DEFAULT_OPTIONS.merge(options)
17
+ @cookies = {}
18
+ end
19
+
20
+ def make_cookie_auth_request
21
+ body = { username: @options[:username].to_s, password: @options[:password].to_s }.to_json
22
+ @options.delete(:username)
23
+ @options.delete(:password)
24
+ make_request(:post, @options[:context_path] + '/rest/auth/1/session', body, 'Content-Type' => 'application/json')
25
+ end
26
+
27
+ def make_request(http_method, url, body = '', headers = {})
28
+ # When a proxy is enabled, Net::HTTP expects that the request path omits the domain name
29
+ path = request_path(url)
30
+ request = Net::HTTP.const_get(http_method.to_s.capitalize).new(path, headers)
31
+ request.body = body unless body.nil?
32
+
33
+ execute_request(request)
34
+ end
35
+
36
+ def make_multipart_request(url, body, headers = {})
37
+ path = request_path(url)
38
+ request = Net::HTTP::Post::Multipart.new(path, body, headers)
39
+
40
+ execute_request(request)
41
+ end
42
+
43
+ def basic_auth_http_conn
44
+ http_conn(uri)
45
+ end
46
+
47
+ def http_conn(uri)
48
+ if @options[:proxy_address]
49
+ http_class = Net::HTTP::Proxy(@options[:proxy_address], @options[:proxy_port] || 80, @options[:proxy_username], @options[:proxy_password])
50
+ else
51
+ http_class = Net::HTTP
52
+ end
53
+ http_conn = http_class.new(uri.host, uri.port)
54
+ http_conn.use_ssl = @options[:use_ssl]
55
+ if @options[:use_client_cert]
56
+ http_conn.cert = @options[:ssl_client_cert]
57
+ http_conn.key = @options[:ssl_client_key]
58
+ end
59
+ http_conn.verify_mode = @options[:ssl_verify_mode]
60
+ http_conn.ssl_version = @options[:ssl_version] if @options[:ssl_version]
61
+ http_conn.read_timeout = @options[:read_timeout]
62
+ http_conn
63
+ end
64
+
65
+ def uri
66
+ URI.parse(@options[:site])
67
+ end
68
+
69
+ def authenticated?
70
+ @authenticated
71
+ end
72
+
73
+ private
74
+
75
+ def execute_request(request)
76
+ add_cookies(request) if options[:use_cookies]
77
+ request.basic_auth(@options[:username], @options[:password]) if @options[:username] && @options[:password]
78
+
79
+ response = basic_auth_http_conn.request(request)
80
+ @authenticated = response.is_a? Net::HTTPOK
81
+ store_cookies(response) if options[:use_cookies]
82
+
83
+ response
84
+ end
85
+
86
+ def request_path(url)
87
+ parsed_uri = URI(url)
88
+
89
+ return url unless parsed_uri.is_a?(URI::HTTP)
90
+
91
+ parsed_uri.request_uri
92
+ end
93
+
94
+ def store_cookies(response)
95
+ cookies = response.get_fields('set-cookie')
96
+ if cookies
97
+ cookies.each do |cookie|
98
+ data = CGI::Cookie.parse(cookie)
99
+ data.delete('Path')
100
+ @cookies.merge!(data)
101
+ end
102
+ end
103
+ end
104
+
105
+ def add_cookies(request)
106
+ cookie_array = @cookies.values.map { |cookie| "#{cookie.name}=#{cookie.value[0]}" }
107
+ cookie_array += Array(@options[:additional_cookies]) if @options.key?(:additional_cookies)
108
+ request.add_field('Cookie', cookie_array.join('; ')) if cookie_array.any?
109
+ request
110
+ end
111
+ end
112
+ end