Skip to content

Commit 96083c3

Browse files
authored
Merge pull request #731 from ninp0/master
PWN::Plugins::JiraServer module - implement attachment support in #cr…
2 parents fc201e5 + 9671992 commit 96083c3

File tree

3 files changed

+84
-22
lines changed

3 files changed

+84
-22
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ $ cd /opt/pwn
3737
$ ./install.sh
3838
$ ./install.sh ruby-gem
3939
$ pwn
40-
pwn[v0.5.295]:001 >>> PWN.help
40+
pwn[v0.5.296]:001 >>> PWN.help
4141
```
4242

4343
[![Installing the pwn Security Automation Framework](https://raw.githubusercontent.com/0dayInc/pwn/master/documentation/pwn_install.png)](https://youtu.be/G7iLUY4FzsI)
@@ -52,7 +52,7 @@ $ rvm use ruby-3.4.4@pwn
5252
$ gem uninstall --all --executables pwn
5353
$ gem install --verbose pwn
5454
$ pwn
55-
pwn[v0.5.295]:001 >>> PWN.help
55+
pwn[v0.5.296]:001 >>> PWN.help
5656
```
5757

5858
If you're using a multi-user install of RVM do:
@@ -62,7 +62,7 @@ $ rvm use ruby-3.4.4@pwn
6262
$ rvmsudo gem uninstall --all --executables pwn
6363
$ rvmsudo gem install --verbose pwn
6464
$ pwn
65-
pwn[v0.5.295]:001 >>> PWN.help
65+
pwn[v0.5.296]:001 >>> PWN.help
6666
```
6767

6868
PWN periodically upgrades to the latest version of Ruby which is reflected in `/opt/pwn/.ruby-version`. The easiest way to upgrade to the latest version of Ruby from a previous PWN installation is to run the following script:

lib/pwn/plugins/jira_server.rb

Lines changed: 80 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -22,51 +22,62 @@ module JiraServer
2222
# )
2323

2424
private_class_method def self.rest_call(opts = {})
25+
token = opts[:token]
2526
http_method = if opts[:http_method].nil?
2627
:get
2728
else
2829
opts[:http_method].to_s.scrub.to_sym
2930
end
3031
rest_call = opts[:rest_call].to_s.scrub
3132
params = opts[:params]
33+
headers = opts[:http_headers] ||= {
34+
content_type: 'application/json; charset=UTF-8',
35+
authorization: "Bearer #{token}"
36+
}
37+
3238
http_body = opts[:http_body]
3339
base_api_uri = opts[:base_api_uri]
3440

3541
raise 'ERROR: base_api_uri cannot be nil.' if base_api_uri.nil?
3642

37-
token = opts[:token]
38-
3943
browser_obj = PWN::Plugins::TransparentBrowser.open(browser_type: :rest)
4044
rest_client = browser_obj[:browser]::Request
4145

4246
spinner = TTY::Spinner.new
4347
spinner.auto_spin
4448

49+
max_request_attempts = 3
50+
tot_request_attempts ||= 1
51+
4552
case http_method
4653
when :delete, :get
54+
headers[:params] = params
4755
response = rest_client.execute(
4856
method: http_method,
4957
url: "#{base_api_uri}/#{rest_call}",
50-
headers: {
51-
content_type: 'application/json; charset=UTF-8',
52-
authorization: "Bearer #{token}",
53-
params: params
54-
},
55-
verify_ssl: false
58+
headers: headers,
59+
verify_ssl: false,
60+
timeout: 180
5661
)
5762

5863
when :post, :put
64+
if http_body.is_a?(Hash)
65+
if http_body.key?(:multipart)
66+
headers[:content_type] = 'multipart/form-data'
67+
headers[:x_atlassian_token] => 'no-check'
68+
else
69+
http_body = http_body.to_json
70+
end
71+
end
72+
5973
response = rest_client.execute(
6074
method: http_method,
6175
url: "#{base_api_uri}/#{rest_call}",
62-
headers: {
63-
content_type: 'application/json; charset=UTF-8',
64-
authorization: "Bearer #{token}"
65-
},
66-
payload: http_body.to_json,
67-
verify_ssl: false
76+
headers: headers,
77+
payload: http_body,
78+
verify_ssl: false,
79+
timeout: 180
6880
)
69-
7081
else
7182
raise @@logger.error("Unsupported HTTP Method #{http_method} for #{self} Plugin")
7283
end
@@ -80,10 +91,31 @@ module JiraServer
8091
puts "HTTP RESPONSE HEADERS: #{e.response.headers}"
8192
puts "HTTP RESPONSE BODY:\n#{e.response.body}\n\n\n"
8293
end
94+
95+
raise e
96+
rescue IO::TimeoutError => e
97+
raise e if tot_request_attempts == max_request_attempts
98+
99+
tot_request_attempts += 1
100+
@@logger.warn("Timeout Error: Retrying request (Attempt #{tot_request_attempts}/#{max_request_attempts})")
101+
sleep(2)
102+
retry
103+
rescue Errno::ECONNREFUSED => e
104+
raise e if tot_request_attempts == max_request_attempts
105+
106+
puts "\nTCP Connection Unavailable."
107+
puts "Attempt (#{tot_request_attempts} of #{max_request_attempts}) in 60s"
108+
60.downto(1) do
109+
print '.'
110+
sleep 1
111+
end
112+
tot_request_attempts += 1
113+
114+
retry
83115
rescue StandardError => e
84116
raise e
85117
ensure
86-
spinner.stop
118+
spinner.stop unless spinner.nil?
87119
end
88120

89121
# Supported Method Parameters::
@@ -150,6 +182,7 @@ module JiraServer
150182
# description: 'optional - description of the issue',
151183
# epic_name: 'optional - name of the epic',
152184
# additional_fields: 'optional - additional fields to set in the issue (e.g. labels, components, custom fields, etc.)'
185+
# attachments: 'optional - array of attachment paths to upload to the issue (e.g. ["/path/to/file1.txt", "/path/to/file2.png"])'
153186
# )
154187

155188
public_class_method def self.create_issue(opts = {})
@@ -173,6 +206,9 @@ module JiraServer
173206
additional_fields = opts[:additional_fields] ||= { fields: {} }
174207
raise 'ERROR: additional_fields Hash must contain a :fields key that is also a Hash.' unless additional_fields.is_a?(Hash) && additional_fields.key?(:fields) && additional_fields[:fields].is_a?(Hash)
175208

209+
attachments = opts[:attachments] ||= []
210+
raise 'ERROR: attachments must be an Array.' unless attachments.is_a?(Array)
211+
176212
all_fields = get_all_fields(base_api_uri: base_api_uri, token: token)
177213
epic_name_field_key = all_fields.find { |field| field[:name] == 'Epic Name' }[:id]
178214

@@ -194,13 +230,32 @@ module JiraServer
194230

195231
http_body[:fields].merge!(additional_fields[:fields])
196232

197-
rest_call(
233+
issue_resp = rest_call(
198234
http_method: :post,
199235
base_api_uri: base_api_uri,
200236
token: token,
201237
rest_call: 'issue',
202238
http_body: http_body
203239
)
240+
241+
if attachments.any?
242+
issue = issue_resp[:key]
243+
244+
http_body = {
245+
multipart: true,
246+
file: attachments.map { |attachment| File.new(attachment, 'rb') }
247+
}
248+
249+
rest_call(
250+
http_method: :post,
251+
base_api_uri: base_api_uri,
252+
token: token,
253+
rest_call: "issue/#{issue}/attachments",
254+
http_body: http_body
255+
)
256+
end
257+
258+
issue_resp
204259
rescue StandardError => e
205260
raise e
206261
end
@@ -299,7 +354,8 @@ module JiraServer
299354
issue_type: 'required - issue type (e.g. :epic, :story, :bug)',
300355
description: 'optional - description of the issue',
301356
epic_name: 'optional - name of the epic',
302-
additional_fields: 'optional - additional fields to set in the issue (e.g. labels, components, custom fields, etc.)'
357+
additional_fields: 'optional - additional fields to set in the issue (e.g. labels, components, custom fields, etc.)',
358+
attachments: 'optional - array of attachment paths to upload to the issue (e.g. ['/path/to/file1.txt', '/path/to/file2.png'])'
303359
)
304360
305361
issue_resp = #{self}.update_issue(
@@ -309,6 +365,12 @@ module JiraServer
309365
fields: 'required - fields to update in the issue (e.g. summary, description, labels, components, custom fields, etc.)'
310366
)
311367
368+
issue_resp = #{self}.delete_issue(
369+
base_api_uri: 'required - base URI for Jira (e.g. https:/jira.corp.com/rest/api/latest)',
370+
token: 'required - bearer token',
371+
issue: 'required - issue to delete (e.g. Bug, Issue, Story, or Epic ID)'
372+
)
373+
312374
**********************************************************************
313375
* For more information on the Jira Server REST API, see:
314376
* https://developer.atlassian.com/server/jira/platform/rest-apis/

lib/pwn/version.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# frozen_string_literal: true
22

33
module PWN
4-
VERSION = '0.5.295'
4+
VERSION = '0.5.296'
55
end

0 commit comments

Comments
 (0)