jira-ruby 2.1.3

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 +224 -0
  10. data/http-basic-example.rb +113 -0
  11. data/jira-ruby.gemspec +35 -0
  12. data/lib/jira-ruby.rb +49 -0
  13. data/lib/jira/base.rb +525 -0
  14. data/lib/jira/base_factory.rb +46 -0
  15. data/lib/jira/client.rb +308 -0
  16. data/lib/jira/has_many_proxy.rb +42 -0
  17. data/lib/jira/http_client.rb +112 -0
  18. data/lib/jira/http_error.rb +14 -0
  19. data/lib/jira/jwt_client.rb +67 -0
  20. data/lib/jira/oauth_client.rb +114 -0
  21. data/lib/jira/railtie.rb +10 -0
  22. data/lib/jira/request_client.rb +31 -0
  23. data/lib/jira/resource/agile.rb +79 -0
  24. data/lib/jira/resource/applinks.rb +39 -0
  25. data/lib/jira/resource/attachment.rb +50 -0
  26. data/lib/jira/resource/board.rb +91 -0
  27. data/lib/jira/resource/board_configuration.rb +9 -0
  28. data/lib/jira/resource/comment.rb +12 -0
  29. data/lib/jira/resource/component.rb +8 -0
  30. data/lib/jira/resource/createmeta.rb +44 -0
  31. data/lib/jira/resource/field.rb +83 -0
  32. data/lib/jira/resource/filter.rb +15 -0
  33. data/lib/jira/resource/issue.rb +141 -0
  34. data/lib/jira/resource/issuelink.rb +20 -0
  35. data/lib/jira/resource/issuelinktype.rb +14 -0
  36. data/lib/jira/resource/issuetype.rb +8 -0
  37. data/lib/jira/resource/priority.rb +8 -0
  38. data/lib/jira/resource/project.rb +41 -0
  39. data/lib/jira/resource/rapidview.rb +67 -0
  40. data/lib/jira/resource/remotelink.rb +26 -0
  41. data/lib/jira/resource/resolution.rb +8 -0
  42. data/lib/jira/resource/serverinfo.rb +18 -0
  43. data/lib/jira/resource/sprint.rb +105 -0
  44. data/lib/jira/resource/sprint_report.rb +8 -0
  45. data/lib/jira/resource/status.rb +8 -0
  46. data/lib/jira/resource/transition.rb +29 -0
  47. data/lib/jira/resource/user.rb +30 -0
  48. data/lib/jira/resource/version.rb +8 -0
  49. data/lib/jira/resource/watcher.rb +35 -0
  50. data/lib/jira/resource/webhook.rb +37 -0
  51. data/lib/jira/resource/worklog.rb +14 -0
  52. data/lib/jira/tasks.rb +0 -0
  53. data/lib/jira/version.rb +3 -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.post.json +28 -0
  98. data/spec/mock_responses/component/10000.invalid.put.json +5 -0
  99. data/spec/mock_responses/component/10000.json +39 -0
  100. data/spec/mock_responses/component/10000.put.json +39 -0
  101. data/spec/mock_responses/empty_issues.json +8 -0
  102. data/spec/mock_responses/field.json +32 -0
  103. data/spec/mock_responses/field/1.json +15 -0
  104. data/spec/mock_responses/issue.json +1108 -0
  105. data/spec/mock_responses/issue.post.json +5 -0
  106. data/spec/mock_responses/issue/10002.invalid.put.json +6 -0
  107. data/spec/mock_responses/issue/10002.json +126 -0
  108. data/spec/mock_responses/issue/10002.put.missing_field_update.json +6 -0
  109. data/spec/mock_responses/issue/10002/attachments/10000.json +20 -0
  110. data/spec/mock_responses/issue/10002/comment.json +65 -0
  111. data/spec/mock_responses/issue/10002/comment.post.json +29 -0
  112. data/spec/mock_responses/issue/10002/comment/10000.json +29 -0
  113. data/spec/mock_responses/issue/10002/comment/10000.put.json +29 -0
  114. data/spec/mock_responses/issue/10002/transitions.json +49 -0
  115. data/spec/mock_responses/issue/10002/transitions.post.json +1 -0
  116. data/spec/mock_responses/issue/10002/watchers.json +13 -0
  117. data/spec/mock_responses/issue/10002/worklog.json +98 -0
  118. data/spec/mock_responses/issue/10002/worklog.post.json +30 -0
  119. data/spec/mock_responses/issue/10002/worklog/10000.json +31 -0
  120. data/spec/mock_responses/issue/10002/worklog/10000.put.json +30 -0
  121. data/spec/mock_responses/issueLinkType.json +25 -0
  122. data/spec/mock_responses/issueLinkType/10000.json +7 -0
  123. data/spec/mock_responses/issuetype.json +42 -0
  124. data/spec/mock_responses/issuetype/5.json +8 -0
  125. data/spec/mock_responses/jira/rest/webhooks/1.0/webhook.json +11 -0
  126. data/spec/mock_responses/jira/rest/webhooks/1.0/webhook/2.json +11 -0
  127. data/spec/mock_responses/priority.json +42 -0
  128. data/spec/mock_responses/priority/1.json +8 -0
  129. data/spec/mock_responses/project.json +12 -0
  130. data/spec/mock_responses/project/SAMPLEPROJECT.issues.json +1108 -0
  131. data/spec/mock_responses/project/SAMPLEPROJECT.json +84 -0
  132. data/spec/mock_responses/rapidview.json +10 -0
  133. data/spec/mock_responses/rapidview/SAMPLEPROJECT.issues.full.json +276 -0
  134. data/spec/mock_responses/rapidview/SAMPLEPROJECT.issues.json +111 -0
  135. data/spec/mock_responses/rapidview/SAMPLEPROJECT.json +6 -0
  136. data/spec/mock_responses/resolution.json +15 -0
  137. data/spec/mock_responses/resolution/1.json +7 -0
  138. data/spec/mock_responses/sprint/1_issues.json +125 -0
  139. data/spec/mock_responses/status.json +37 -0
  140. data/spec/mock_responses/status/1.json +7 -0
  141. data/spec/mock_responses/user_username=admin.json +17 -0
  142. data/spec/mock_responses/version.post.json +7 -0
  143. data/spec/mock_responses/version/10000.invalid.put.json +5 -0
  144. data/spec/mock_responses/version/10000.json +11 -0
  145. data/spec/mock_responses/version/10000.put.json +7 -0
  146. data/spec/mock_responses/webhook.json +11 -0
  147. data/spec/mock_responses/webhook/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,308 @@
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
+ # :additional_cookies => nil,
33
+ # :default_headers => {},
34
+ # :use_client_cert => false,
35
+ # :read_timeout => nil,
36
+ # :http_debug => false,
37
+ # :shared_secret => nil,
38
+ # :cert_path => nil,
39
+ # :key_path => nil,
40
+ # :ssl_client_cert => nil,
41
+ # :ssl_client_key => nil
42
+ #
43
+ # See the JIRA::Base class methods for all of the available methods on these accessor
44
+ # objects.
45
+
46
+ class Client
47
+ extend Forwardable
48
+
49
+ # The OAuth::Consumer instance returned by the OauthClient
50
+ #
51
+ # The authenticated client instance returned by the respective client type
52
+ # (Oauth, Basic)
53
+ attr_accessor :consumer, :request_client, :http_debug, :cache
54
+
55
+ # The configuration options for this client instance
56
+ attr_reader :options
57
+
58
+ def_delegators :@request_client, :init_access_token, :set_access_token, :set_request_token, :request_token, :access_token, :authenticated?
59
+
60
+ DEFINED_OPTIONS = [
61
+ :site,
62
+ :context_path,
63
+ :signature_method,
64
+ :request_token_path,
65
+ :authorize_path,
66
+ :access_token_path,
67
+ :private_key,
68
+ :private_key_file,
69
+ :rest_base_path,
70
+ :consumer_key,
71
+ :consumer_secret,
72
+ :ssl_verify_mode,
73
+ :ssl_version,
74
+ :use_ssl,
75
+ :username,
76
+ :password,
77
+ :auth_type,
78
+ :proxy_address,
79
+ :proxy_port,
80
+ :proxy_username,
81
+ :proxy_password,
82
+ :additional_cookies,
83
+ :default_headers,
84
+ :use_client_cert,
85
+ :read_timeout,
86
+ :http_debug,
87
+ :issuer,
88
+ :base_url,
89
+ :shared_secret,
90
+ :cert_path,
91
+ :key_path,
92
+ :ssl_client_cert,
93
+ :ssl_client_key
94
+ ].freeze
95
+
96
+ DEFAULT_OPTIONS = {
97
+ site: 'http://localhost:2990',
98
+ context_path: '/jira',
99
+ rest_base_path: '/rest/api/2',
100
+ ssl_verify_mode: OpenSSL::SSL::VERIFY_PEER,
101
+ use_ssl: true,
102
+ use_client_cert: false,
103
+ auth_type: :oauth,
104
+ http_debug: false,
105
+ default_headers: {}
106
+ }.freeze
107
+
108
+ def initialize(options = {})
109
+ options = DEFAULT_OPTIONS.merge(options)
110
+ @options = options
111
+ @options[:rest_base_path] = @options[:context_path] + @options[:rest_base_path]
112
+
113
+ unknown_options = options.keys.reject { |o| DEFINED_OPTIONS.include?(o) }
114
+ raise ArgumentError, "Unknown option(s) given: #{unknown_options}" unless unknown_options.empty?
115
+
116
+ if options[:use_client_cert]
117
+ @options[:ssl_client_cert] = OpenSSL::X509::Certificate.new(File.read(@options[:cert_path])) if @options[:cert_path]
118
+ @options[:ssl_client_key] = OpenSSL::PKey::RSA.new(File.read(@options[:key_path])) if @options[:key_path]
119
+
120
+ raise ArgumentError, 'Options: :cert_path or :ssl_client_cert must be set when :use_client_cert is true' unless @options[:ssl_client_cert]
121
+ raise ArgumentError, 'Options: :key_path or :ssl_client_key must be set when :use_client_cert is true' unless @options[:ssl_client_key]
122
+ end
123
+
124
+ case options[:auth_type]
125
+ when :oauth, :oauth_2legged
126
+ @request_client = OauthClient.new(@options)
127
+ @consumer = @request_client.consumer
128
+ when :jwt
129
+ @request_client = JwtClient.new(@options)
130
+ when :basic
131
+ @request_client = HttpClient.new(@options)
132
+ when :cookie
133
+ raise ArgumentError, 'Options: :use_cookies must be true for :cookie authorization type' if @options.key?(:use_cookies) && !@options[:use_cookies]
134
+ @options[:use_cookies] = true
135
+ @request_client = HttpClient.new(@options)
136
+ @request_client.make_cookie_auth_request
137
+ @options.delete(:username)
138
+ @options.delete(:password)
139
+ else
140
+ raise ArgumentError, 'Options: ":auth_type" must be ":oauth",":oauth_2legged", ":cookie" or ":basic"'
141
+ end
142
+
143
+ @http_debug = @options[:http_debug]
144
+
145
+ @options.freeze
146
+
147
+ @cache = OpenStruct.new
148
+ end
149
+
150
+ def Project # :nodoc:
151
+ JIRA::Resource::ProjectFactory.new(self)
152
+ end
153
+
154
+ def Issue # :nodoc:
155
+ JIRA::Resource::IssueFactory.new(self)
156
+ end
157
+
158
+ def Filter # :nodoc:
159
+ JIRA::Resource::FilterFactory.new(self)
160
+ end
161
+
162
+ def Component # :nodoc:
163
+ JIRA::Resource::ComponentFactory.new(self)
164
+ end
165
+
166
+ def User # :nodoc:
167
+ JIRA::Resource::UserFactory.new(self)
168
+ end
169
+
170
+ def Issuetype # :nodoc:
171
+ JIRA::Resource::IssuetypeFactory.new(self)
172
+ end
173
+
174
+ def Priority # :nodoc:
175
+ JIRA::Resource::PriorityFactory.new(self)
176
+ end
177
+
178
+ def Status # :nodoc:
179
+ JIRA::Resource::StatusFactory.new(self)
180
+ end
181
+
182
+ def Resolution # :nodoc:
183
+ JIRA::Resource::ResolutionFactory.new(self)
184
+ end
185
+
186
+ def Comment # :nodoc:
187
+ JIRA::Resource::CommentFactory.new(self)
188
+ end
189
+
190
+ def Attachment # :nodoc:
191
+ JIRA::Resource::AttachmentFactory.new(self)
192
+ end
193
+
194
+ def Worklog # :nodoc:
195
+ JIRA::Resource::WorklogFactory.new(self)
196
+ end
197
+
198
+ def Version # :nodoc:
199
+ JIRA::Resource::VersionFactory.new(self)
200
+ end
201
+
202
+ def Transition # :nodoc:
203
+ JIRA::Resource::TransitionFactory.new(self)
204
+ end
205
+
206
+ def Field # :nodoc:
207
+ JIRA::Resource::FieldFactory.new(self)
208
+ end
209
+
210
+ def Board
211
+ JIRA::Resource::BoardFactory.new(self)
212
+ end
213
+
214
+ def BoardConfiguration
215
+ JIRA::Resource::BoardConfigurationFactory.new(self)
216
+ end
217
+
218
+ def RapidView
219
+ JIRA::Resource::RapidViewFactory.new(self)
220
+ end
221
+
222
+ def Sprint
223
+ JIRA::Resource::SprintFactory.new(self)
224
+ end
225
+
226
+ def SprintReport
227
+ JIRA::Resource::SprintReportFactory.new(self)
228
+ end
229
+
230
+ def ServerInfo
231
+ JIRA::Resource::ServerInfoFactory.new(self)
232
+ end
233
+
234
+ def Createmeta
235
+ JIRA::Resource::CreatemetaFactory.new(self)
236
+ end
237
+
238
+ def ApplicationLink
239
+ JIRA::Resource::ApplicationLinkFactory.new(self)
240
+ end
241
+
242
+ def Watcher
243
+ JIRA::Resource::WatcherFactory.new(self)
244
+ end
245
+
246
+ def Webhook
247
+ JIRA::Resource::WebhookFactory.new(self)
248
+ end
249
+
250
+ def Issuelink
251
+ JIRA::Resource::IssuelinkFactory.new(self)
252
+ end
253
+
254
+ def Issuelinktype
255
+ JIRA::Resource::IssuelinktypeFactory.new(self)
256
+ end
257
+
258
+ def Remotelink
259
+ JIRA::Resource::RemotelinkFactory.new(self)
260
+ end
261
+
262
+ def Agile
263
+ JIRA::Resource::AgileFactory.new(self)
264
+ end
265
+
266
+ # HTTP methods without a body
267
+ def delete(path, headers = {})
268
+ request(:delete, path, nil, merge_default_headers(headers))
269
+ end
270
+
271
+ def get(path, headers = {})
272
+ request(:get, path, nil, merge_default_headers(headers))
273
+ end
274
+
275
+ def head(path, headers = {})
276
+ request(:head, path, nil, merge_default_headers(headers))
277
+ end
278
+
279
+ # HTTP methods with a body
280
+ def post(path, body = '', headers = {})
281
+ headers = { 'Content-Type' => 'application/json' }.merge(headers)
282
+ request(:post, path, body, merge_default_headers(headers))
283
+ end
284
+
285
+ def post_multipart(path, file, headers = {})
286
+ puts "post multipart: #{path} - [#{file}]" if @http_debug
287
+ @request_client.request_multipart(path, file, headers)
288
+ end
289
+
290
+ def put(path, body = '', headers = {})
291
+ headers = { 'Content-Type' => 'application/json' }.merge(headers)
292
+ request(:put, path, body, merge_default_headers(headers))
293
+ end
294
+
295
+ # Sends the specified HTTP request to the REST API through the
296
+ # appropriate method (oauth, basic).
297
+ def request(http_method, path, body = '', headers = {})
298
+ puts "#{http_method}: #{path} - [#{body}]" if @http_debug
299
+ @request_client.request(http_method, path, body, headers)
300
+ end
301
+
302
+ protected
303
+
304
+ def merge_default_headers(headers)
305
+ { 'Accept' => 'application/json' }.merge(@options[:default_headers]).merge(headers)
306
+ end
307
+ end
308
+ 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