jira-ruby 1.7.0 → 2.1.1
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/.gitignore +2 -0
- data/.travis.yml +3 -2
- data/README.md +3 -3
- data/jira-ruby.gemspec +0 -2
- data/lib/jira-ruby.rb +1 -0
- data/lib/jira/base.rb +1 -1
- data/lib/jira/client.rb +51 -5
- data/lib/jira/http_client.rb +26 -11
- data/lib/jira/http_error.rb +1 -1
- data/lib/jira/jwt_client.rb +50 -15
- data/lib/jira/oauth_client.rb +15 -5
- data/lib/jira/request_client.rb +15 -3
- data/lib/jira/resource/attachment.rb +19 -14
- data/lib/jira/resource/board.rb +7 -0
- data/lib/jira/resource/board_configuration.rb +9 -0
- data/lib/jira/resource/sprint.rb +12 -8
- data/lib/jira/resource/watcher.rb +7 -0
- data/lib/jira/version.rb +1 -1
- data/spec/integration/watcher_spec.rb +15 -6
- data/spec/jira/base_spec.rb +12 -0
- data/spec/jira/client_spec.rb +9 -0
- data/spec/jira/http_client_spec.rb +130 -6
- data/spec/jira/jwt_uri_builder_spec.rb +59 -0
- data/spec/jira/oauth_client_spec.rb +27 -10
- data/spec/jira/request_client_spec.rb +37 -10
- data/spec/jira/resource/attachment_spec.rb +79 -22
- data/spec/jira/resource/board_spec.rb +49 -0
- data/spec/jira/resource/sprint_spec.rb +23 -11
- metadata +30 -28
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4134942b3228eb9c8b459a5c237b269fd4a0e1acf68c853aa1ecaae68a7f5c09
|
4
|
+
data.tar.gz: 1d8e205e64c61c9b27ff2a60c7d7cb5ef07de9c33c15da33d17875fc671676e4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 30e17a5592edf781b456616b61405cc1999a24214171f835577631b9ff20526cb4d2d145fdc10c6d7e95d6d73449c4b8dbb78a7c249355166b8a06a405aea4f8
|
7
|
+
data.tar.gz: f159cdc2aacd86ef037cb54419ed6c57c09b61f864377affd1b6daba15a75ab6a6dd6e3092a1d22672087717c163ac6862ffb80d2eb0a1e95f58305d3c9d6e40
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -163,7 +163,7 @@ api_token = "myApiToken"
|
|
163
163
|
options = {
|
164
164
|
:username => username,
|
165
165
|
:password => api_token,
|
166
|
-
:site => 'http://localhost:8080/', # or 'https://<your_subdomain>.atlassian.net'
|
166
|
+
:site => 'http://localhost:8080/', # or 'https://<your_subdomain>.atlassian.net/'
|
167
167
|
:context_path => '/myjira', # often blank
|
168
168
|
:auth_type => :basic,
|
169
169
|
:read_timeout => 120
|
@@ -307,7 +307,7 @@ class App < Sinatra::Base
|
|
307
307
|
# site uri, and the request token, access token, and authorize paths
|
308
308
|
before do
|
309
309
|
options = {
|
310
|
-
:site => 'http://localhost:2990',
|
310
|
+
:site => 'http://localhost:2990/',
|
311
311
|
:context_path => '/jira',
|
312
312
|
:signature_method => 'RSA-SHA1',
|
313
313
|
:request_token_path => "/plugins/servlet/oauth/request-token",
|
@@ -405,7 +405,7 @@ require 'pp'
|
|
405
405
|
require 'jira-ruby'
|
406
406
|
|
407
407
|
options = {
|
408
|
-
:site => 'http://localhost:2990',
|
408
|
+
:site => 'http://localhost:2990/',
|
409
409
|
:context_path => '/jira',
|
410
410
|
:signature_method => 'RSA-SHA1',
|
411
411
|
:private_key_file => "rsakey.pem",
|
data/jira-ruby.gemspec
CHANGED
@@ -13,8 +13,6 @@ Gem::Specification.new do |s|
|
|
13
13
|
|
14
14
|
s.required_ruby_version = '>= 1.9.3'
|
15
15
|
|
16
|
-
s.rubyforge_project = 'jira-ruby'
|
17
|
-
|
18
16
|
s.files = `git ls-files`.split("\n")
|
19
17
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
20
18
|
s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
|
data/lib/jira-ruby.rb
CHANGED
data/lib/jira/base.rb
CHANGED
@@ -424,7 +424,7 @@ module JIRA
|
|
424
424
|
end
|
425
425
|
if @attrs['self']
|
426
426
|
the_url = @attrs['self']
|
427
|
-
the_url = the_url.sub(@client.options[:site], '') if @client.options[:site]
|
427
|
+
the_url = the_url.sub(@client.options[:site].chomp('/'), '') if @client.options[:site]
|
428
428
|
the_url
|
429
429
|
elsif key_value
|
430
430
|
self.class.singular_path(client, key_value.to_s, prefix)
|
data/lib/jira/client.rb
CHANGED
@@ -19,14 +19,21 @@ module JIRA
|
|
19
19
|
# :consumer_key => nil,
|
20
20
|
# :consumer_secret => nil,
|
21
21
|
# :ssl_verify_mode => OpenSSL::SSL::VERIFY_PEER,
|
22
|
+
# :ssl_version => nil,
|
22
23
|
# :use_ssl => true,
|
23
24
|
# :username => nil,
|
24
25
|
# :password => nil,
|
25
26
|
# :auth_type => :oauth,
|
26
27
|
# :proxy_address => nil,
|
27
28
|
# :proxy_port => nil,
|
29
|
+
# :proxy_username => nil,
|
30
|
+
# :proxy_password => nil,
|
28
31
|
# :additional_cookies => nil,
|
29
|
-
# :default_headers => {}
|
32
|
+
# :default_headers => {},
|
33
|
+
# :use_client_cert => false,
|
34
|
+
# :read_timeout => nil,
|
35
|
+
# :http_debug => false,
|
36
|
+
# :shared_secret => nil
|
30
37
|
#
|
31
38
|
# See the JIRA::Base class methods for all of the available methods on these accessor
|
32
39
|
# objects.
|
@@ -45,6 +52,37 @@ module JIRA
|
|
45
52
|
|
46
53
|
def_delegators :@request_client, :init_access_token, :set_access_token, :set_request_token, :request_token, :access_token, :authenticated?
|
47
54
|
|
55
|
+
DEFINED_OPTIONS = [
|
56
|
+
:site,
|
57
|
+
:context_path,
|
58
|
+
:signature_method,
|
59
|
+
:request_token_path,
|
60
|
+
:authorize_path,
|
61
|
+
:access_token_path,
|
62
|
+
:private_key_file,
|
63
|
+
:rest_base_path,
|
64
|
+
:consumer_key,
|
65
|
+
:consumer_secret,
|
66
|
+
:ssl_verify_mode,
|
67
|
+
:ssl_version,
|
68
|
+
:use_ssl,
|
69
|
+
:username,
|
70
|
+
:password,
|
71
|
+
:auth_type,
|
72
|
+
:proxy_address,
|
73
|
+
:proxy_port,
|
74
|
+
:proxy_username,
|
75
|
+
:proxy_password,
|
76
|
+
:additional_cookies,
|
77
|
+
:default_headers,
|
78
|
+
:use_client_cert,
|
79
|
+
:read_timeout,
|
80
|
+
:http_debug,
|
81
|
+
:issuer,
|
82
|
+
:base_url,
|
83
|
+
:shared_secret
|
84
|
+
].freeze
|
85
|
+
|
48
86
|
DEFAULT_OPTIONS = {
|
49
87
|
site: 'http://localhost:2990',
|
50
88
|
context_path: '/jira',
|
@@ -62,6 +100,9 @@ module JIRA
|
|
62
100
|
@options = options
|
63
101
|
@options[:rest_base_path] = @options[:context_path] + @options[:rest_base_path]
|
64
102
|
|
103
|
+
unknown_options = options.keys.reject { |o| DEFINED_OPTIONS.include?(o) }
|
104
|
+
raise ArgumentError, "Unknown option(s) given: #{unknown_options}" unless unknown_options.empty?
|
105
|
+
|
65
106
|
if options[:use_client_cert]
|
66
107
|
raise ArgumentError, 'Options: :cert_path must be set when :use_client_cert is true' unless @options[:cert_path]
|
67
108
|
raise ArgumentError, 'Options: :key_path must be set when :use_client_cert is true' unless @options[:key_path]
|
@@ -159,6 +200,10 @@ module JIRA
|
|
159
200
|
JIRA::Resource::BoardFactory.new(self)
|
160
201
|
end
|
161
202
|
|
203
|
+
def BoardConfiguration
|
204
|
+
JIRA::Resource::BoardConfigurationFactory.new(self)
|
205
|
+
end
|
206
|
+
|
162
207
|
def RapidView
|
163
208
|
JIRA::Resource::RapidViewFactory.new(self)
|
164
209
|
end
|
@@ -203,10 +248,6 @@ module JIRA
|
|
203
248
|
JIRA::Resource::RemotelinkFactory.new(self)
|
204
249
|
end
|
205
250
|
|
206
|
-
def Sprint
|
207
|
-
JIRA::Resource::SprintFactory.new(self)
|
208
|
-
end
|
209
|
-
|
210
251
|
def Agile
|
211
252
|
JIRA::Resource::AgileFactory.new(self)
|
212
253
|
end
|
@@ -230,6 +271,11 @@ module JIRA
|
|
230
271
|
request(:post, path, body, merge_default_headers(headers))
|
231
272
|
end
|
232
273
|
|
274
|
+
def post_multipart(path, file, headers = {})
|
275
|
+
puts "post multipart: #{path} - [#{file}]" if @http_debug
|
276
|
+
@request_client.request_multipart(path, file, headers)
|
277
|
+
end
|
278
|
+
|
233
279
|
def put(path, body = '', headers = {})
|
234
280
|
headers = { 'Content-Type' => 'application/json' }.merge(headers)
|
235
281
|
request(:put, path, body, merge_default_headers(headers))
|
data/lib/jira/http_client.rb
CHANGED
@@ -6,8 +6,8 @@ require 'uri'
|
|
6
6
|
module JIRA
|
7
7
|
class HttpClient < RequestClient
|
8
8
|
DEFAULT_OPTIONS = {
|
9
|
-
username:
|
10
|
-
password:
|
9
|
+
username: nil,
|
10
|
+
password: nil
|
11
11
|
}.freeze
|
12
12
|
|
13
13
|
attr_reader :options
|
@@ -18,7 +18,7 @@ module JIRA
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def make_cookie_auth_request
|
21
|
-
body = { username: @options[:username], password: @options[:password] }.to_json
|
21
|
+
body = { username: @options[:username].to_s, password: @options[:password].to_s }.to_json
|
22
22
|
@options.delete(:username)
|
23
23
|
@options.delete(:password)
|
24
24
|
make_request(:post, @options[:context_path] + '/rest/auth/1/session', body, 'Content-Type' => 'application/json')
|
@@ -29,12 +29,15 @@ module JIRA
|
|
29
29
|
path = request_path(url)
|
30
30
|
request = Net::HTTP.const_get(http_method.to_s.capitalize).new(path, headers)
|
31
31
|
request.body = body unless body.nil?
|
32
|
-
|
33
|
-
request
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
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)
|
38
41
|
end
|
39
42
|
|
40
43
|
def basic_auth_http_conn
|
@@ -43,7 +46,7 @@ module JIRA
|
|
43
46
|
|
44
47
|
def http_conn(uri)
|
45
48
|
if @options[:proxy_address]
|
46
|
-
http_class = Net::HTTP::Proxy(@options[:proxy_address], @options[:proxy_port] || 80)
|
49
|
+
http_class = Net::HTTP::Proxy(@options[:proxy_address], @options[:proxy_port] || 80, @options[:proxy_username], @options[:proxy_password])
|
47
50
|
else
|
48
51
|
http_class = Net::HTTP
|
49
52
|
end
|
@@ -54,12 +57,13 @@ module JIRA
|
|
54
57
|
http_conn.key = @options[:key]
|
55
58
|
end
|
56
59
|
http_conn.verify_mode = @options[:ssl_verify_mode]
|
60
|
+
http_conn.ssl_version = @options[:ssl_version] if @options[:ssl_version]
|
57
61
|
http_conn.read_timeout = @options[:read_timeout]
|
58
62
|
http_conn
|
59
63
|
end
|
60
64
|
|
61
65
|
def uri
|
62
|
-
|
66
|
+
URI.parse(@options[:site])
|
63
67
|
end
|
64
68
|
|
65
69
|
def authenticated?
|
@@ -68,6 +72,17 @@ module JIRA
|
|
68
72
|
|
69
73
|
private
|
70
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
|
+
|
71
86
|
def request_path(url)
|
72
87
|
parsed_uri = URI(url)
|
73
88
|
|
data/lib/jira/http_error.rb
CHANGED
data/lib/jira/jwt_client.rb
CHANGED
@@ -3,30 +3,65 @@ require 'atlassian/jwt'
|
|
3
3
|
module JIRA
|
4
4
|
class JwtClient < HttpClient
|
5
5
|
def make_request(http_method, url, body = '', headers = {})
|
6
|
-
|
7
|
-
path = request_path(url) + "?jwt=#{jwt_header(http_method, url)}"
|
6
|
+
@http_method = http_method
|
8
7
|
|
9
|
-
|
10
|
-
|
8
|
+
super(http_method, url, body, headers)
|
9
|
+
end
|
10
|
+
|
11
|
+
def make_multipart_request(url, data, headers = {})
|
12
|
+
@http_method = :post
|
11
13
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
14
|
+
super(url, data, headers)
|
15
|
+
end
|
16
|
+
|
17
|
+
class JwtUriBuilder
|
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
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def jwt_header
|
41
|
+
claim = Atlassian::Jwt.build_claims \
|
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
|
48
|
+
|
49
|
+
JWT.encode claim, shared_secret
|
50
|
+
end
|
16
51
|
end
|
17
52
|
|
18
53
|
private
|
19
54
|
|
20
|
-
|
21
|
-
|
22
|
-
|
55
|
+
attr_reader :http_method
|
56
|
+
|
57
|
+
def request_path(url)
|
58
|
+
JwtUriBuilder.new(
|
23
59
|
url,
|
24
60
|
http_method.to_s,
|
61
|
+
@options[:shared_secret],
|
25
62
|
@options[:site],
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
JWT.encode claim, @options[:shared_secret]
|
63
|
+
@options[:issuer]
|
64
|
+
).build
|
30
65
|
end
|
31
66
|
end
|
32
67
|
end
|
data/lib/jira/oauth_client.rb
CHANGED
@@ -72,29 +72,39 @@ module JIRA
|
|
72
72
|
@access_token
|
73
73
|
end
|
74
74
|
|
75
|
-
def make_request(http_method,
|
75
|
+
def make_request(http_method, url, body = '', headers = {})
|
76
76
|
# When using oauth_2legged we need to add an empty oauth_token parameter to every request.
|
77
77
|
if @options[:auth_type] == :oauth_2legged
|
78
78
|
oauth_params_str = 'oauth_token='
|
79
|
-
uri = URI.parse(
|
79
|
+
uri = URI.parse(url)
|
80
80
|
uri.query = if uri.query.to_s == ''
|
81
81
|
oauth_params_str
|
82
82
|
else
|
83
83
|
uri.query + '&' + oauth_params_str
|
84
84
|
end
|
85
|
-
|
85
|
+
url = uri.to_s
|
86
86
|
end
|
87
87
|
|
88
88
|
case http_method
|
89
89
|
when :delete, :get, :head
|
90
|
-
response = access_token.send http_method,
|
90
|
+
response = access_token.send http_method, url, headers
|
91
91
|
when :post, :put
|
92
|
-
response = access_token.send http_method,
|
92
|
+
response = access_token.send http_method, url, body, headers
|
93
93
|
end
|
94
94
|
@authenticated = true
|
95
95
|
response
|
96
96
|
end
|
97
97
|
|
98
|
+
def make_multipart_request(url, data, headers = {})
|
99
|
+
request = Net::HTTP::Post::Multipart.new url, data, headers
|
100
|
+
|
101
|
+
access_token.sign! request
|
102
|
+
|
103
|
+
response = consumer.http.request(request)
|
104
|
+
@authenticated = true
|
105
|
+
response
|
106
|
+
end
|
107
|
+
|
98
108
|
def authenticated?
|
99
109
|
@authenticated
|
100
110
|
end
|
data/lib/jira/request_client.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
require 'oauth'
|
2
2
|
require 'json'
|
3
3
|
require 'net/https'
|
4
|
-
# require 'pry'
|
5
4
|
|
6
5
|
module JIRA
|
7
6
|
class RequestClient
|
@@ -11,9 +10,22 @@ module JIRA
|
|
11
10
|
|
12
11
|
def request(*args)
|
13
12
|
response = make_request(*args)
|
14
|
-
# binding.pry unless response.kind_of?(Net::HTTPSuccess)
|
15
13
|
raise HTTPError, response unless response.is_a?(Net::HTTPSuccess)
|
16
14
|
response
|
17
15
|
end
|
16
|
+
|
17
|
+
def request_multipart(*args)
|
18
|
+
response = make_multipart_request(*args)
|
19
|
+
raise HTTPError, response unless response.is_a?(Net::HTTPSuccess)
|
20
|
+
response
|
21
|
+
end
|
22
|
+
|
23
|
+
def make_request(*args)
|
24
|
+
raise NotImplementedError
|
25
|
+
end
|
26
|
+
|
27
|
+
def make_multipart_request(*args)
|
28
|
+
raise NotImplementedError
|
29
|
+
end
|
18
30
|
end
|
19
|
-
end
|
31
|
+
end
|
@@ -19,27 +19,32 @@ module JIRA
|
|
19
19
|
parse_json(response.body)
|
20
20
|
end
|
21
21
|
|
22
|
-
def save!(attrs)
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
request = Net::HTTP::Post::Multipart.new url, data, headers
|
27
|
-
request.basic_auth(client.request_client.options[:username],
|
28
|
-
client.request_client.options[:password])
|
22
|
+
def save!(attrs, path = url)
|
23
|
+
file = attrs['file'] || attrs[:file] # Keep supporting 'file' parameter as a string for backward compatibility
|
24
|
+
mime_type = attrs[:mimeType] || 'application/binary'
|
29
25
|
|
30
|
-
|
26
|
+
headers = { 'X-Atlassian-Token' => 'nocheck' }
|
27
|
+
data = { 'file' => UploadIO.new(file, mime_type, file) }
|
31
28
|
|
32
|
-
|
33
|
-
unless response.body.nil? || response.body.length < 2
|
34
|
-
json = self.class.parse_json(response.body)
|
35
|
-
attachment = json[0]
|
29
|
+
response = client.post_multipart(path, data , headers)
|
36
30
|
|
37
|
-
|
38
|
-
end
|
31
|
+
set_attributes(attrs, response)
|
39
32
|
|
40
33
|
@expanded = false
|
41
34
|
true
|
42
35
|
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def set_attributes(attributes, response)
|
40
|
+
set_attrs(attributes, false)
|
41
|
+
return if response.body.nil? || response.body.length < 2
|
42
|
+
|
43
|
+
json = self.class.parse_json(response.body)
|
44
|
+
attachment = json[0]
|
45
|
+
|
46
|
+
set_attrs(attachment)
|
47
|
+
end
|
43
48
|
end
|
44
49
|
end
|
45
50
|
end
|