jira-ruby 2.1.3

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.
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