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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5433f708b438c961d0ca07b8aa60ae7733382aecb7374d4cc3ea3af1947f0e7f
4
- data.tar.gz: d6757f05e80e8c005ad0f20461fac98d5509f87f0e0bebaea025e43e7d180d3e
3
+ metadata.gz: 2fea6e6a55a6679e8dab6437b19ccb62d29f8d31ec4db8b36c449ce3cae78f44
4
+ data.tar.gz: e4f8e7e6c1be0344db51d6a516c3da9db55343f93a49cb08072d0dbf5c462f16
5
5
  SHA512:
6
- metadata.gz: 25f924c28544baf800585f9243d6d29622b0facab654aa03cf620bf76e4133731334b649c6af725fb0f246c68e3f5aa17bd8623eba5c2dd66303d2f2b2766dd2
7
- data.tar.gz: b15c6540010df98f03acd562a50520dd90705bfa58cb2815070bc0ba3a535aea00d092c61a0f58bd3e3f23345c8ee48cf14e5d602fe324c3f7197b408d64e252
6
+ metadata.gz: 934781eb8ab9ec5bc4da7ad9b1c048dc52adaffd17c389046c10f1078e93495f248d4af219dd10fc105a87e8ef6d58c99ada61db40c86238ee7f844ab632c831
7
+ data.tar.gz: 7ebaeb51adb377be540de240939188b3e88b85554958f3bd0cd187346778a3b45c0ee9cc70c23d4e5763f16fc2e96b36032a32253845c8a98d5c1c3de0bc2e7d
data/.gitignore CHANGED
@@ -9,3 +9,5 @@ pkg/*
9
9
  .DS_STORE
10
10
  doc
11
11
  .ruby-version
12
+
13
+ .rakeTasks
@@ -1,8 +1,9 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 2.3
4
3
  - 2.4
5
- - ruby-head
4
+ - 2.5
5
+ - 2.6
6
+ - 2.7
6
7
  before_script:
7
8
  - rake jira:generate_public_cert
8
9
  script: bundle exec rake spec
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/display/DOCS/Install+the+Atlassian+SDK+on+a+Linux+or+Mac+System
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
- password = "myuserspassword"
161
+ api_token = "myApiToken"
158
162
 
159
163
  options = {
160
164
  :username => username,
161
- :password => 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",
@@ -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
 
@@ -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)
@@ -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)
@@ -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
- # :additional_cookies => nil
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
@@ -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
- add_cookies(request) if options[:use_cookies]
33
- request.basic_auth(@options[:username], @options[:password]) if @options[:username] && @options[:password]
34
- response = basic_auth_http_conn.request(request)
35
- @authenticated = response.is_a? Net::HTTPOK
36
- store_cookies(response) if options[:use_cookies]
37
- response
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
- uri = URI.parse(@options[:site])
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
 
@@ -8,7 +8,7 @@ module JIRA
8
8
 
9
9
  def initialize(response)
10
10
  @response = response
11
- @message = response.try(:message) || response.try(:body)
11
+ @message = response.try(:message).presence || response.try(:body)
12
12
  end
13
13
  end
14
14
  end
@@ -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
@@ -72,29 +72,39 @@ module JIRA
72
72
  @access_token
73
73
  end
74
74
 
75
- def make_request(http_method, path, body = '', headers = {})
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(path)
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
- path = uri.to_s
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, path, headers
90
+ response = access_token.send http_method, url, headers
91
91
  when :post, :put
92
- response = access_token.send http_method, path, body, headers
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