jira-ruby 1.6.0 → 2.1.0
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 +11 -7
- data/jira-ruby.gemspec +1 -2
- data/lib/jira-ruby.rb +2 -0
- data/lib/jira/base.rb +1 -1
- data/lib/jira/client.rb +55 -7
- data/lib/jira/http_client.rb +26 -11
- data/lib/jira/http_error.rb +1 -1
- data/lib/jira/jwt_client.rb +67 -0
- 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 +8 -1
- 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 +57 -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 +50 -1
- data/spec/jira/resource/sprint_spec.rb +23 -11
- metadata +45 -28
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2fea6e6a55a6679e8dab6437b19ccb62d29f8d31ec4db8b36c449ce3cae78f44
|
4
|
+
data.tar.gz: e4f8e7e6c1be0344db51d6a516c3da9db55343f93a49cb08072d0dbf5c462f16
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 934781eb8ab9ec5bc4da7ad9b1c048dc52adaffd17c389046c10f1078e93495f248d4af219dd10fc105a87e8ef6d58c99ada61db40c86238ee7f844ab632c831
|
7
|
+
data.tar.gz: 7ebaeb51adb377be540de240939188b3e88b85554958f3bd0cd187346778a3b45c0ee9cc70c23d4e5763f16fc2e96b36032a32253845c8a98d5c1c3de0bc2e7d
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -50,7 +50,7 @@ rake jira:generate_public_cert
|
|
50
50
|
|
51
51
|
On Mac OS,
|
52
52
|
|
53
|
-
* Follow the instructions under "Mac OSX Installer" here: https://developer.atlassian.com/
|
53
|
+
* Follow the instructions under "Mac OSX Installer" here: https://developer.atlassian.com/server/framework/atlassian-sdk/install-the-atlassian-sdk-on-a-linux-or-mac-system
|
54
54
|
* From within the archive directory, run:
|
55
55
|
|
56
56
|
```shell
|
@@ -153,14 +153,18 @@ require 'jira-ruby'
|
|
153
153
|
# Consider the use of :use_ssl and :ssl_verify_mode options if running locally
|
154
154
|
# for tests.
|
155
155
|
|
156
|
+
# NOTE basic auth no longer works with Jira, you must generate an API token, to do so you must have jira instance access rights. You can generate a token here: https://id.atlassian.com/manage/api-tokens
|
157
|
+
|
158
|
+
# You will see JIRA::HTTPError (JIRA::HTTPError) if you attempt to use basic auth with your user's password
|
159
|
+
|
156
160
|
username = "myremoteuser"
|
157
|
-
|
161
|
+
api_token = "myApiToken"
|
158
162
|
|
159
163
|
options = {
|
160
164
|
:username => username,
|
161
|
-
:password =>
|
162
|
-
:site => 'http://localhost:8080/',
|
163
|
-
:context_path => '/myjira',
|
165
|
+
:password => api_token,
|
166
|
+
:site => 'http://localhost:8080/', # or 'https://<your_subdomain>.atlassian.net/'
|
167
|
+
:context_path => '/myjira', # often blank
|
164
168
|
:auth_type => :basic,
|
165
169
|
:read_timeout => 120
|
166
170
|
}
|
@@ -303,7 +307,7 @@ class App < Sinatra::Base
|
|
303
307
|
# site uri, and the request token, access token, and authorize paths
|
304
308
|
before do
|
305
309
|
options = {
|
306
|
-
:site => 'http://localhost:2990',
|
310
|
+
:site => 'http://localhost:2990/',
|
307
311
|
:context_path => '/jira',
|
308
312
|
:signature_method => 'RSA-SHA1',
|
309
313
|
:request_token_path => "/plugins/servlet/oauth/request-token",
|
@@ -401,7 +405,7 @@ require 'pp'
|
|
401
405
|
require 'jira-ruby'
|
402
406
|
|
403
407
|
options = {
|
404
|
-
:site => 'http://localhost:2990',
|
408
|
+
:site => 'http://localhost:2990/',
|
405
409
|
:context_path => '/jira',
|
406
410
|
:signature_method => 'RSA-SHA1',
|
407
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) }
|
@@ -22,6 +20,7 @@ Gem::Specification.new do |s|
|
|
22
20
|
|
23
21
|
# Runtime Dependencies
|
24
22
|
s.add_runtime_dependency 'activesupport'
|
23
|
+
s.add_runtime_dependency 'atlassian-jwt'
|
25
24
|
s.add_runtime_dependency 'multipart-post'
|
26
25
|
s.add_runtime_dependency 'oauth', '~> 0.5', '>= 0.5.0'
|
27
26
|
|
data/lib/jira-ruby.rb
CHANGED
@@ -38,10 +38,12 @@ require 'jira/resource/createmeta'
|
|
38
38
|
require 'jira/resource/webhook'
|
39
39
|
require 'jira/resource/agile'
|
40
40
|
require 'jira/resource/board'
|
41
|
+
require 'jira/resource/board_configuration'
|
41
42
|
|
42
43
|
require 'jira/request_client'
|
43
44
|
require 'jira/oauth_client'
|
44
45
|
require 'jira/http_client'
|
46
|
+
require 'jira/jwt_client'
|
45
47
|
require 'jira/client'
|
46
48
|
|
47
49
|
require 'jira/railtie' if defined?(Rails)
|
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,13 +19,20 @@ 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,
|
28
|
-
# :
|
29
|
+
# :proxy_username => nil,
|
30
|
+
# :proxy_password => nil,
|
31
|
+
# :additional_cookies => nil,
|
32
|
+
# :default_headers => {},
|
33
|
+
# :use_client_cert => false,
|
34
|
+
# :http_debug => false,
|
35
|
+
# :shared_secret => nil
|
29
36
|
#
|
30
37
|
# See the JIRA::Base class methods for all of the available methods on these accessor
|
31
38
|
# objects.
|
@@ -44,6 +51,36 @@ module JIRA
|
|
44
51
|
|
45
52
|
def_delegators :@request_client, :init_access_token, :set_access_token, :set_request_token, :request_token, :access_token, :authenticated?
|
46
53
|
|
54
|
+
DEFINED_OPTIONS = [
|
55
|
+
:site,
|
56
|
+
:context_path,
|
57
|
+
:signature_method,
|
58
|
+
:request_token_path,
|
59
|
+
:authorize_path,
|
60
|
+
:access_token_path,
|
61
|
+
:private_key_file,
|
62
|
+
:rest_base_path,
|
63
|
+
:consumer_key,
|
64
|
+
:consumer_secret,
|
65
|
+
:ssl_verify_mode,
|
66
|
+
:ssl_version,
|
67
|
+
:use_ssl,
|
68
|
+
:username,
|
69
|
+
:password,
|
70
|
+
:auth_type,
|
71
|
+
:proxy_address,
|
72
|
+
:proxy_port,
|
73
|
+
:proxy_username,
|
74
|
+
:proxy_password,
|
75
|
+
:additional_cookies,
|
76
|
+
:default_headers,
|
77
|
+
:use_client_cert,
|
78
|
+
:http_debug,
|
79
|
+
:issuer,
|
80
|
+
:base_url,
|
81
|
+
:shared_secret
|
82
|
+
].freeze
|
83
|
+
|
47
84
|
DEFAULT_OPTIONS = {
|
48
85
|
site: 'http://localhost:2990',
|
49
86
|
context_path: '/jira',
|
@@ -52,7 +89,8 @@ module JIRA
|
|
52
89
|
use_ssl: true,
|
53
90
|
use_client_cert: false,
|
54
91
|
auth_type: :oauth,
|
55
|
-
http_debug: false
|
92
|
+
http_debug: false,
|
93
|
+
default_headers: {}
|
56
94
|
}.freeze
|
57
95
|
|
58
96
|
def initialize(options = {})
|
@@ -60,6 +98,9 @@ module JIRA
|
|
60
98
|
@options = options
|
61
99
|
@options[:rest_base_path] = @options[:context_path] + @options[:rest_base_path]
|
62
100
|
|
101
|
+
unknown_options = options.keys.reject { |o| DEFINED_OPTIONS.include?(o) }
|
102
|
+
raise ArgumentError, "Unknown option(s) given: #{unknown_options}" unless unknown_options.empty?
|
103
|
+
|
63
104
|
if options[:use_client_cert]
|
64
105
|
raise ArgumentError, 'Options: :cert_path must be set when :use_client_cert is true' unless @options[:cert_path]
|
65
106
|
raise ArgumentError, 'Options: :key_path must be set when :use_client_cert is true' unless @options[:key_path]
|
@@ -71,6 +112,8 @@ module JIRA
|
|
71
112
|
when :oauth, :oauth_2legged
|
72
113
|
@request_client = OauthClient.new(@options)
|
73
114
|
@consumer = @request_client.consumer
|
115
|
+
when :jwt
|
116
|
+
@request_client = JwtClient.new(@options)
|
74
117
|
when :basic
|
75
118
|
@request_client = HttpClient.new(@options)
|
76
119
|
when :cookie
|
@@ -155,6 +198,10 @@ module JIRA
|
|
155
198
|
JIRA::Resource::BoardFactory.new(self)
|
156
199
|
end
|
157
200
|
|
201
|
+
def BoardConfiguration
|
202
|
+
JIRA::Resource::BoardConfigurationFactory.new(self)
|
203
|
+
end
|
204
|
+
|
158
205
|
def RapidView
|
159
206
|
JIRA::Resource::RapidViewFactory.new(self)
|
160
207
|
end
|
@@ -199,10 +246,6 @@ module JIRA
|
|
199
246
|
JIRA::Resource::RemotelinkFactory.new(self)
|
200
247
|
end
|
201
248
|
|
202
|
-
def Sprint
|
203
|
-
JIRA::Resource::SprintFactory.new(self)
|
204
|
-
end
|
205
|
-
|
206
249
|
def Agile
|
207
250
|
JIRA::Resource::AgileFactory.new(self)
|
208
251
|
end
|
@@ -226,6 +269,11 @@ module JIRA
|
|
226
269
|
request(:post, path, body, merge_default_headers(headers))
|
227
270
|
end
|
228
271
|
|
272
|
+
def post_multipart(path, file, headers = {})
|
273
|
+
puts "post multipart: #{path} - [#{file}]" if @http_debug
|
274
|
+
@request_client.request_multipart(path, file, headers)
|
275
|
+
end
|
276
|
+
|
229
277
|
def put(path, body = '', headers = {})
|
230
278
|
headers = { 'Content-Type' => 'application/json' }.merge(headers)
|
231
279
|
request(:put, path, body, merge_default_headers(headers))
|
@@ -241,7 +289,7 @@ module JIRA
|
|
241
289
|
protected
|
242
290
|
|
243
291
|
def merge_default_headers(headers)
|
244
|
-
{ 'Accept' => 'application/json' }.merge(headers)
|
292
|
+
{ 'Accept' => 'application/json' }.merge(@options[:default_headers]).merge(headers)
|
245
293
|
end
|
246
294
|
end
|
247
295
|
end
|
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
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'atlassian/jwt'
|
2
|
+
|
3
|
+
module JIRA
|
4
|
+
class JwtClient < HttpClient
|
5
|
+
def make_request(http_method, url, body = '', headers = {})
|
6
|
+
@http_method = http_method
|
7
|
+
|
8
|
+
super(http_method, url, body, headers)
|
9
|
+
end
|
10
|
+
|
11
|
+
def make_multipart_request(url, data, headers = {})
|
12
|
+
@http_method = :post
|
13
|
+
|
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
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
attr_reader :http_method
|
56
|
+
|
57
|
+
def request_path(url)
|
58
|
+
JwtUriBuilder.new(
|
59
|
+
url,
|
60
|
+
http_method.to_s,
|
61
|
+
@options[:shared_secret],
|
62
|
+
@options[:site],
|
63
|
+
@options[:issuer]
|
64
|
+
).build
|
65
|
+
end
|
66
|
+
end
|
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
|