pwn 0.5.692 → 0.5.695
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/Gemfile +3 -3
- data/etc/default_skills/pwn/config/references/urls.md +1 -1
- data/etc/default_skills/pwn/plugins/google_workspace/SKILL.md +7 -4
- data/lib/pwn/ai/agent/prompt_builder.rb +2 -2
- data/lib/pwn/config.rb +1 -1
- data/lib/pwn/plugins/google_workspace.rb +108 -6
- data/lib/pwn/sdr/decoder/flex.rb +1 -2
- data/lib/pwn/sdr/frequency_allocation.rb +6 -6
- data/lib/pwn/version.rb +1 -1
- data/spec/integration/reinforced_feedback_loop_spec.rb +3 -4
- data/spec/lib/pwn/plugins/google_workspace_spec.rb +81 -0
- data/third_party/pwn_rdoc.jsonl +3 -0
- metadata +7 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 445532063d09a6c2edd54ac2872c295932bdbdc45d025e87fe109a5e8641ce0f
|
|
4
|
+
data.tar.gz: b8b9aab81ae7c16ffcda16cdea943b85eaf69c93b6de929e2eb9fc288cb8f000
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 85071aaac40e5ed6348a52a7858be4dd1b781db8b706324b054a67e254c365bab4d10d4aba182ee42a387e15574b8f6b7fc67d2b31b1559dd17b5d7d6dfccae5
|
|
7
|
+
data.tar.gz: a677194de81bb47090ba62cebac6f293d68fc4e4876693faae868cfe96434478610fece0478e93eb980af4d688819ea1310cb6048bb7096b27a0bdbe516bf1da
|
data/Gemfile
CHANGED
|
@@ -25,7 +25,7 @@ gem 'bundler-audit', '>=0.9.3'
|
|
|
25
25
|
gem 'bunny', '3.2.0'
|
|
26
26
|
gem 'colorize', '1.1.0'
|
|
27
27
|
gem 'credit_card_validations', '9.0.0'
|
|
28
|
-
gem 'curses', '1.
|
|
28
|
+
gem 'curses', '1.7.0'
|
|
29
29
|
gem 'diffy', '3.4.4'
|
|
30
30
|
gem 'eventmachine', '1.2.7'
|
|
31
31
|
gem 'executable-hooks', '1.7.1'
|
|
@@ -50,7 +50,7 @@ gem 'libusb', '0.8.0'
|
|
|
50
50
|
gem 'luhn', '3.0.0'
|
|
51
51
|
gem 'mail', '2.9.1'
|
|
52
52
|
gem 'mcp', '1.3.0'
|
|
53
|
-
gem 'meshtastic', '0.0.
|
|
53
|
+
gem 'meshtastic', '0.0.175'
|
|
54
54
|
gem 'metasm', '1.0.6'
|
|
55
55
|
gem 'mongo', '2.25.0'
|
|
56
56
|
gem 'msfrpc-client', '1.1.2'
|
|
@@ -81,7 +81,7 @@ gem 'rmagick', '7.1.1'
|
|
|
81
81
|
gem 'rqrcode', '3.2.0'
|
|
82
82
|
gem 'rspec', '3.13.2'
|
|
83
83
|
gem 'rtesseract', '3.1.4'
|
|
84
|
-
gem 'rubocop', '1.
|
|
84
|
+
gem 'rubocop', '1.90.0'
|
|
85
85
|
gem 'rubocop-rake', '0.7.1'
|
|
86
86
|
gem 'rubocop-rspec', '3.10.2'
|
|
87
87
|
gem 'ruby-audio', '1.6.1'
|
|
@@ -59,7 +59,6 @@ Enable in Google Cloud: Gmail, Calendar, Drive, Docs, Sheets APIs.
|
|
|
59
59
|
gw = PWN::Plugins::GoogleWorkspace
|
|
60
60
|
gw.authenticated?
|
|
61
61
|
gw.obtain_oauth_auth_url(services: 'all')
|
|
62
|
-
gw.exchange_oauth_code(code: 'http://127.0.0.1:1/?code=...')
|
|
63
62
|
gw.gmail_search(query: 'is:unread', max: 10)
|
|
64
63
|
```
|
|
65
64
|
|
|
@@ -70,10 +69,14 @@ then `pwn_eval`.
|
|
|
70
69
|
|
|
71
70
|
1. Store the Desktop OAuth client fields in the oauth hash (pwn-vault).
|
|
72
71
|
2. `obtain_oauth_auth_url(services: 'email,calendar')` — or `all`.
|
|
73
|
-
3.
|
|
74
|
-
4.
|
|
72
|
+
3. Authorize the printed URL. A loopback listener captures the redirect.
|
|
73
|
+
4. Access and refresh tokens are written immediately into
|
|
74
|
+
`PWN::Env[:plugins][:google_workspace][:oauth]` and encrypted
|
|
75
|
+
`~/.pwn/pwn.yaml` (same persist path as `PWN::AI::Grok`).
|
|
75
76
|
5. Later calls refresh automatically. `revoke` clears tokens.
|
|
76
77
|
|
|
78
|
+
`exchange_oauth_code` remains a paste fallback (`wait: false` then paste).
|
|
79
|
+
|
|
77
80
|
## Quick reference
|
|
78
81
|
|
|
79
82
|
```ruby
|
|
@@ -133,7 +136,7 @@ gw.sheets_append(id: 'SHEET_ID', range: 'Sheet1!A:C', values: [['a', 'b', 'c']])
|
|
|
133
136
|
|
|
134
137
|
## Pitfalls
|
|
135
138
|
|
|
136
|
-
- Browser fail
|
|
139
|
+
- Browser fail is not required. `obtain_oauth_auth_url` waits and persists.
|
|
137
140
|
- Placeholder vault strings are not credentials.
|
|
138
141
|
- A brief request is not a send.
|
|
139
142
|
|
|
@@ -152,8 +152,8 @@ module PWN
|
|
|
152
152
|
{
|
|
153
153
|
memory: (b[:memory] || (local ? 6 : 25)).to_i,
|
|
154
154
|
metrics: (b[:metrics] || (local ? 3 : 8)).to_i,
|
|
155
|
-
mistakes: (b[:mistakes] || (local ?
|
|
156
|
-
learning: (b[:learning] || (local ?
|
|
155
|
+
mistakes: (b[:mistakes] || (local ? 3 : 6)).to_i,
|
|
156
|
+
learning: (b[:learning] || (local ? 2 : 5)).to_i,
|
|
157
157
|
# Always inject prior user/assistant pairs from this session.
|
|
158
158
|
recent_turns: (b[:recent_turns] || (local ? 3 : 6)).to_i,
|
|
159
159
|
policy: (b[:policy] || 1).to_i,
|
data/lib/pwn/config.rb
CHANGED
|
@@ -215,7 +215,7 @@ module PWN
|
|
|
215
215
|
refresh_token: 'optional - durable Google refresh token (enables silent re-auth)',
|
|
216
216
|
bearer_token: 'optional - Google access token (short-lived; auto-refreshed)',
|
|
217
217
|
expires_at: 'optional - unix epoch seconds when bearer_token expires',
|
|
218
|
-
redirect_uri: 'optional -
|
|
218
|
+
redirect_uri: 'optional - loopback listener (default http://127.0.0.1:ephemeral/)',
|
|
219
219
|
scope: 'optional - override space-delimited Google scopes',
|
|
220
220
|
services: 'optional - email,calendar,drive,docs,sheets or all'
|
|
221
221
|
}
|
|
@@ -7,6 +7,8 @@ require 'fileutils'
|
|
|
7
7
|
require 'json'
|
|
8
8
|
require 'rest-client'
|
|
9
9
|
require 'securerandom'
|
|
10
|
+
require 'socket'
|
|
11
|
+
require 'timeout'
|
|
10
12
|
require 'time'
|
|
11
13
|
require 'tmpdir'
|
|
12
14
|
require 'uri'
|
|
@@ -21,9 +23,10 @@ module PWN
|
|
|
21
23
|
# Credentials live in PWN::Env[:plugins][:google_workspace][:oauth]
|
|
22
24
|
# (seeded by PWN::Config / pwn-vault). Desktop-app client_id +
|
|
23
25
|
# client_secret come from Google Cloud Console. After
|
|
24
|
-
# obtain_oauth_auth_url
|
|
25
|
-
#
|
|
26
|
-
#
|
|
26
|
+
# obtain_oauth_auth_url. When wait is true (default), a loopback
|
|
27
|
+
# listener captures the redirect as soon as the operator authorizes
|
|
28
|
+
# the URL; refresh/bearer are written into PWN::Env and the encrypted
|
|
29
|
+
# ~/.pwn/pwn.yaml the same way PWN::AI::Grok persists OAuth tokens.
|
|
27
30
|
module GoogleWorkspace
|
|
28
31
|
AUTH_URI = 'https://accounts.google.com/o/oauth2/v2/auth'
|
|
29
32
|
TOKEN_URI = 'https://oauth2.googleapis.com/token'
|
|
@@ -128,10 +131,18 @@ module PWN
|
|
|
128
131
|
client_id = c[:client_id]
|
|
129
132
|
raise 'client_id is required (Google Cloud Desktop OAuth client)' unless real_config_value?(value: client_id)
|
|
130
133
|
|
|
134
|
+
wait = opts.key?(:wait) ? opts[:wait] : true
|
|
135
|
+
timeout_s = (opts[:timeout] || 300).to_i
|
|
131
136
|
redirect = real_config_value?(value: c[:redirect_uri]) ? c[:redirect_uri] : REDIRECT_URI
|
|
132
137
|
wanted = scopes(opts.merge(services: c[:services], scope: c[:scope]))
|
|
133
138
|
raise 'no Google scopes selected' if wanted.empty?
|
|
134
139
|
|
|
140
|
+
server = nil
|
|
141
|
+
if wait && !opts[:probe].respond_to?(:call)
|
|
142
|
+
server = open_oauth_listener(redirect_uri: redirect)
|
|
143
|
+
redirect = listener_redirect(server: server, fallback: redirect)
|
|
144
|
+
end
|
|
145
|
+
|
|
135
146
|
pkce = pkce_pair
|
|
136
147
|
save_pending!(verifier: pkce[:verifier], redirect_uri: redirect, client_id: client_id)
|
|
137
148
|
|
|
@@ -147,7 +158,96 @@ module PWN
|
|
|
147
158
|
code_challenge_method: 'S256'
|
|
148
159
|
}
|
|
149
160
|
url = "#{AUTH_URI}?#{URI.encode_www_form(params)}"
|
|
150
|
-
{ auth_url: url, scope: wanted, redirect_uri: redirect }
|
|
161
|
+
return { auth_url: url, scope: wanted, redirect_uri: redirect } unless wait
|
|
162
|
+
|
|
163
|
+
puts "\n[*] Google Workspace OAuth — authorize this URL, then tokens persist automatically:"
|
|
164
|
+
puts " #{url}"
|
|
165
|
+
puts " Waiting for authorization on #{redirect} (timeout #{timeout_s}s)..."
|
|
166
|
+
code = await_oauth_redirect(
|
|
167
|
+
redirect_uri: redirect,
|
|
168
|
+
timeout: timeout_s,
|
|
169
|
+
probe: opts[:probe],
|
|
170
|
+
server: server
|
|
171
|
+
)
|
|
172
|
+
exchange_oauth_code(
|
|
173
|
+
opts.merge(
|
|
174
|
+
code: code,
|
|
175
|
+
redirect_uri: redirect,
|
|
176
|
+
code_verifier: pkce[:verifier],
|
|
177
|
+
client_id: client_id,
|
|
178
|
+
client_secret: c[:client_secret]
|
|
179
|
+
)
|
|
180
|
+
)
|
|
181
|
+
ensure
|
|
182
|
+
begin
|
|
183
|
+
server&.close
|
|
184
|
+
rescue StandardError
|
|
185
|
+
nil
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
private_class_method def self.open_oauth_listener(opts = {})
|
|
190
|
+
redirect = opts[:redirect_uri].to_s
|
|
191
|
+
uri = URI.parse(redirect.empty? ? REDIRECT_URI : redirect)
|
|
192
|
+
host = uri.host.to_s
|
|
193
|
+
host = '127.0.0.1' if host.empty?
|
|
194
|
+
port = uri.port.to_i
|
|
195
|
+
port = 0 if port <= 1
|
|
196
|
+
TCPServer.new(host, port)
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
private_class_method def self.listener_redirect(opts = {})
|
|
200
|
+
server = opts[:server]
|
|
201
|
+
fallback = opts[:fallback].to_s
|
|
202
|
+
return fallback unless server
|
|
203
|
+
|
|
204
|
+
addr = server.addr
|
|
205
|
+
host = addr[3].to_s
|
|
206
|
+
host = '127.0.0.1' if host.empty? || host == '0.0.0.0'
|
|
207
|
+
"http://#{host}:#{addr[1]}/"
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
private_class_method def self.await_oauth_redirect(opts = {})
|
|
211
|
+
redirect = opts[:redirect_uri].to_s
|
|
212
|
+
timeout_s = (opts[:timeout] || 300).to_i
|
|
213
|
+
probe = opts[:probe]
|
|
214
|
+
return probe.call(redirect) if probe.respond_to?(:call)
|
|
215
|
+
|
|
216
|
+
server = opts[:server]
|
|
217
|
+
owned = false
|
|
218
|
+
unless server
|
|
219
|
+
server = open_oauth_listener(redirect_uri: redirect)
|
|
220
|
+
owned = true
|
|
221
|
+
end
|
|
222
|
+
code = nil
|
|
223
|
+
Timeout.timeout(timeout_s) do
|
|
224
|
+
client = server.accept
|
|
225
|
+
request_line = client.gets.to_s
|
|
226
|
+
loop do
|
|
227
|
+
line = client.gets
|
|
228
|
+
break if line.nil? || line.strip.empty?
|
|
229
|
+
end
|
|
230
|
+
path = request_line.split[1].to_s
|
|
231
|
+
path = '/' if path.empty?
|
|
232
|
+
uri = URI.parse(path.start_with?('http') ? path : "http://127.0.0.1#{path}")
|
|
233
|
+
code = URI.decode_www_form(uri.query.to_s).to_h['code'].to_s
|
|
234
|
+
body = "PWN Google Workspace authorized. You can close this tab.\n"
|
|
235
|
+
client.print "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: #{body.bytesize}\r\nConnection: close\r\n\r\n#{body}"
|
|
236
|
+
client.close
|
|
237
|
+
end
|
|
238
|
+
raise 'Google OAuth redirect contained no code' if code.to_s.empty?
|
|
239
|
+
|
|
240
|
+
code
|
|
241
|
+
rescue Timeout::Error
|
|
242
|
+
raise 'Google OAuth timed out waiting for authorization'
|
|
243
|
+
ensure
|
|
244
|
+
if owned
|
|
245
|
+
begin
|
|
246
|
+
server&.close
|
|
247
|
+
rescue StandardError
|
|
248
|
+
nil
|
|
249
|
+
end
|
|
250
|
+
end
|
|
151
251
|
end
|
|
152
252
|
|
|
153
253
|
public_class_method def self.exchange_oauth_code(opts = {})
|
|
@@ -239,7 +339,7 @@ module PWN
|
|
|
239
339
|
exp = c[:expires_at].to_i
|
|
240
340
|
stale = !real_config_value?(value: token) || (exp.positive? && Time.now.to_i >= (exp - 120))
|
|
241
341
|
return refresh_oauth_bearer_token(opts.merge(refresh_token: c[:refresh_token])) if stale && real_config_value?(value: c[:refresh_token])
|
|
242
|
-
raise 'not authenticated — run obtain_oauth_auth_url
|
|
342
|
+
raise 'not authenticated — run obtain_oauth_auth_url (tokens persist on authorize)' unless real_config_value?(value: token)
|
|
243
343
|
|
|
244
344
|
token
|
|
245
345
|
end
|
|
@@ -779,8 +879,10 @@ module PWN
|
|
|
779
879
|
puts <<~USAGE
|
|
780
880
|
USAGE:
|
|
781
881
|
# OAuth (Desktop client from Google Cloud Console)
|
|
882
|
+
# authorize the printed URL; refresh/bearer persist to Env + pwn.yaml
|
|
782
883
|
#{self}.obtain_oauth_auth_url(services: 'email,calendar')
|
|
783
|
-
#{self}.
|
|
884
|
+
#{self}.obtain_oauth_auth_url(wait: false) # URL only; no listener
|
|
885
|
+
#{self}.exchange_oauth_code(code: 'optional paste fallback')
|
|
784
886
|
#{self}.authenticated?
|
|
785
887
|
#{self}.revoke
|
|
786
888
|
|
data/lib/pwn/sdr/decoder/flex.rb
CHANGED
|
@@ -41,7 +41,7 @@ module PWN
|
|
|
41
41
|
|
|
42
42
|
# Streaming FLEX demodulator fed by Base.run_native / Base.run_iq.
|
|
43
43
|
# Per-sample PLL symbol clock, 4-level slicer, full state machine.
|
|
44
|
-
# rubocop:disable Metrics/ClassLength
|
|
44
|
+
# rubocop:disable-next Metrics/ClassLength
|
|
45
45
|
class Demod
|
|
46
46
|
def initialize(rate: 48_000)
|
|
47
47
|
@rate = rate.to_f
|
|
@@ -222,7 +222,6 @@ module PWN
|
|
|
222
222
|
end
|
|
223
223
|
end
|
|
224
224
|
end
|
|
225
|
-
# rubocop:enable Metrics/ClassLength
|
|
226
225
|
|
|
227
226
|
# Supported Method Parameters::
|
|
228
227
|
# code, polarity = PWN::SDR::Decoder::Flex.sync_check(buf: Integer)
|
|
@@ -488,11 +488,11 @@ module PWN
|
|
|
488
488
|
|
|
489
489
|
# UHF paging bands (hospital, on-site, restaurant/coaster pagers, some public safety)
|
|
490
490
|
# UHF low — occasional private systems
|
|
491
|
-
{ start_freq: '400.000.000',
|
|
491
|
+
{ start_freq: '400.000.000', target_freq: '430.000.000' },
|
|
492
492
|
# UHF high — very common for local POCSAG (incl. 450–470 & 467.xxx for on-site)
|
|
493
|
-
{ start_freq: '440.000.000',
|
|
493
|
+
{ start_freq: '440.000.000', target_freq: '470.000.000' },
|
|
494
494
|
# Classic UHF paging allocation (454/459 MHz pairs — many reallocated)
|
|
495
|
-
{ start_freq: '454.000.000',
|
|
495
|
+
{ start_freq: '454.000.000', target_freq: '460.000.000' },
|
|
496
496
|
|
|
497
497
|
# 900 MHz exclusive paging band (mostly FLEX, but some legacy POCSAG remains)
|
|
498
498
|
# Primary nationwide commercial paging band
|
|
@@ -523,11 +523,11 @@ module PWN
|
|
|
523
523
|
|
|
524
524
|
# UHF paging bands (hospital, on-site, restaurant/coaster pagers, some public safety)
|
|
525
525
|
# UHF low — occasional private systems
|
|
526
|
-
{ start_freq: '400.000.000',
|
|
526
|
+
{ start_freq: '400.000.000', target_freq: '430.000.000' },
|
|
527
527
|
# UHF high — very common for local POCSAG (incl. 450–470 & 467.xxx for on-site)
|
|
528
|
-
{ start_freq: '440.000.000',
|
|
528
|
+
{ start_freq: '440.000.000', target_freq: '470.000.000' },
|
|
529
529
|
# Classic UHF paging allocation (454/459 MHz pairs — many reallocated)
|
|
530
|
-
{ start_freq: '454.000.000',
|
|
530
|
+
{ start_freq: '454.000.000', target_freq: '460.000.000' }
|
|
531
531
|
],
|
|
532
532
|
demodulator_mode: :FM,
|
|
533
533
|
bandwidth: '12.500',
|
data/lib/pwn/version.rb
CHANGED
|
@@ -17,7 +17,7 @@ require 'fileutils'
|
|
|
17
17
|
# regression pinpoints exactly which requirement broke.
|
|
18
18
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
19
19
|
|
|
20
|
-
# rubocop:disable Metrics/BlockLength
|
|
20
|
+
# rubocop:disable-next Metrics/BlockLength
|
|
21
21
|
RSpec.describe 'PWN::AI::Agent reinforced feedback loop', :aggregate_failures do
|
|
22
22
|
let(:reward) { PWN::AI::Agent::Reward }
|
|
23
23
|
let(:curriculum) { PWN::AI::Agent::Curriculum }
|
|
@@ -266,8 +266,8 @@ RSpec.describe 'PWN::AI::Agent reinforced feedback loop', :aggregate_failures do
|
|
|
266
266
|
skip 'PWN::MemoryIndex unavailable' unless defined?(PWN::MemoryIndex)
|
|
267
267
|
now = Time.now.utc.iso8601
|
|
268
268
|
mem = {
|
|
269
|
-
a: { value: 'use nmap -sV for service detection',
|
|
270
|
-
b: { value: 'prefer nmap -sV to detect services',
|
|
269
|
+
a: { value: 'use nmap -sV for service detection', category: 'lesson', timestamp: now, importance: 0.5 },
|
|
270
|
+
b: { value: 'prefer nmap -sV to detect services', category: 'lesson', timestamp: now, importance: 0.5 },
|
|
271
271
|
c: { value: 'nmap -sV is best for service versions', category: 'lesson', timestamp: now, importance: 0.5 },
|
|
272
272
|
d: { value: 'unrelated: rotate api keys quarterly', category: 'lesson', timestamp: now, importance: 0.5 }
|
|
273
273
|
}
|
|
@@ -1556,4 +1556,3 @@ RSpec.describe 'PWN::AI::Agent reinforced feedback loop', :aggregate_failures do
|
|
|
1556
1556
|
end
|
|
1557
1557
|
end
|
|
1558
1558
|
end
|
|
1559
|
-
# rubocop:enable Metrics/BlockLength
|
|
@@ -81,4 +81,85 @@ describe PWN::Plugins::GoogleWorkspace do
|
|
|
81
81
|
src = File.read(described_class.method(:bearer_token).source_location.first)
|
|
82
82
|
expect(src).not_to match(/hermes/i)
|
|
83
83
|
end
|
|
84
|
+
|
|
85
|
+
it 'writes refresh and bearer into Env and pwn.yaml when oauth_auth_url is authorized' do
|
|
86
|
+
Dir.mktmpdir('pwn-gw-oauth-') do |dir|
|
|
87
|
+
env_path = File.join(dir, 'pwn.yaml')
|
|
88
|
+
dec_path = "#{env_path}.decryptor"
|
|
89
|
+
key = '0' * 32
|
|
90
|
+
iv = '1' * 16
|
|
91
|
+
File.write(env_path, "plugins:\n google_workspace:\n oauth:\n client_id: cid\n client_secret: sec\n")
|
|
92
|
+
File.write(dec_path, YAML.dump(key: key, iv: iv))
|
|
93
|
+
allow(PWN::Plugins::Vault).to receive(:decrypt)
|
|
94
|
+
allow(PWN::Plugins::Vault).to receive(:encrypt)
|
|
95
|
+
allow(RestClient).to receive(:post).and_return(
|
|
96
|
+
instance_double(RestClient::Response, body: {
|
|
97
|
+
access_token: 'live-bearer',
|
|
98
|
+
refresh_token: 'live-refresh',
|
|
99
|
+
expires_in: 3600,
|
|
100
|
+
scope: 'https://www.googleapis.com/auth/gmail.modify'
|
|
101
|
+
}.to_json)
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
PWN::Env[:plugins] ||= {}
|
|
105
|
+
PWN::Env[:plugins][:google_workspace] ||= {}
|
|
106
|
+
PWN::Env[:plugins][:google_workspace][:oauth] ||= {}
|
|
107
|
+
slot = PWN::Env[:plugins][:google_workspace][:oauth]
|
|
108
|
+
prev = slot.dup
|
|
109
|
+
slot[:client_id] = 'cid'
|
|
110
|
+
slot[:client_secret] = 'sec'
|
|
111
|
+
slot[:bearer_token] = nil
|
|
112
|
+
slot[:refresh_token] = nil
|
|
113
|
+
PWN::Env[:driver_opts] ||= {}
|
|
114
|
+
PWN::Env[:driver_opts][:pwn_env_path] = env_path
|
|
115
|
+
PWN::Env[:driver_opts][:pwn_dec_path] = dec_path
|
|
116
|
+
|
|
117
|
+
result = described_class.obtain_oauth_auth_url(
|
|
118
|
+
client_id: 'cid',
|
|
119
|
+
client_secret: 'sec',
|
|
120
|
+
services: 'email',
|
|
121
|
+
wait: true,
|
|
122
|
+
probe: ->(_redirect) { 'AUTHCODE' }
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
expect(result[:bearer_token]).to eq('live-bearer')
|
|
126
|
+
expect(result[:refresh_token]).to eq('live-refresh')
|
|
127
|
+
expect(slot[:bearer_token]).to eq('live-bearer')
|
|
128
|
+
expect(slot[:refresh_token]).to eq('live-refresh')
|
|
129
|
+
vault = YAML.load_file(env_path, symbolize_names: true)
|
|
130
|
+
expect(vault.dig(:plugins, :google_workspace, :oauth, :bearer_token)).to eq('live-bearer')
|
|
131
|
+
expect(vault.dig(:plugins, :google_workspace, :oauth, :refresh_token)).to eq('live-refresh')
|
|
132
|
+
ensure
|
|
133
|
+
if prev
|
|
134
|
+
slot.clear
|
|
135
|
+
slot.merge!(prev)
|
|
136
|
+
end
|
|
137
|
+
PWN::Env[:driver_opts].delete(:pwn_env_path) if PWN::Env[:driver_opts].is_a?(Hash)
|
|
138
|
+
PWN::Env[:driver_opts].delete(:pwn_dec_path) if PWN::Env[:driver_opts].is_a?(Hash)
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
it 'captures a loopback authorize hit without a paste step' do
|
|
143
|
+
require 'net/http'
|
|
144
|
+
server = described_class.send(:open_oauth_listener, redirect_uri: 'http://127.0.0.1:0/')
|
|
145
|
+
redir = described_class.send(:listener_redirect, server: server, fallback: 'http://127.0.0.1/')
|
|
146
|
+
hit = Thread.new do
|
|
147
|
+
sleep 0.05
|
|
148
|
+
Net::HTTP.get(URI("#{redir}?code=FROMBROWSER"))
|
|
149
|
+
end
|
|
150
|
+
code = described_class.send(
|
|
151
|
+
:await_oauth_redirect,
|
|
152
|
+
redirect_uri: redir,
|
|
153
|
+
timeout: 5,
|
|
154
|
+
server: server
|
|
155
|
+
)
|
|
156
|
+
expect(code).to eq('FROMBROWSER')
|
|
157
|
+
expect(hit.value).to include('authorized')
|
|
158
|
+
ensure
|
|
159
|
+
begin
|
|
160
|
+
server&.close
|
|
161
|
+
rescue StandardError
|
|
162
|
+
nil
|
|
163
|
+
end
|
|
164
|
+
end
|
|
84
165
|
end
|
data/third_party/pwn_rdoc.jsonl
CHANGED
|
@@ -1846,6 +1846,7 @@
|
|
|
1846
1846
|
{"messages":[{"role":"user","content":"PWN::Plugins::GoogleWorkspace.api Usage"},{"role":"assistant","content":"`PWN::Plugins::GoogleWorkspace.api`: "}]}
|
|
1847
1847
|
{"messages":[{"role":"user","content":"PWN::Plugins::GoogleWorkspace.authenticated? Usage"},{"role":"assistant","content":"`PWN::Plugins::GoogleWorkspace.authenticated?`: "}]}
|
|
1848
1848
|
{"messages":[{"role":"user","content":"PWN::Plugins::GoogleWorkspace.authors Usage"},{"role":"assistant","content":"`PWN::Plugins::GoogleWorkspace.authors`: "}]}
|
|
1849
|
+
{"messages":[{"role":"user","content":"PWN::Plugins::GoogleWorkspace.await_oauth_redirect Usage"},{"role":"assistant","content":"`PWN::Plugins::GoogleWorkspace.await_oauth_redirect`: "}]}
|
|
1849
1850
|
{"messages":[{"role":"user","content":"PWN::Plugins::GoogleWorkspace.bearer_token Usage"},{"role":"assistant","content":"`PWN::Plugins::GoogleWorkspace.bearer_token`: "}]}
|
|
1850
1851
|
{"messages":[{"role":"user","content":"PWN::Plugins::GoogleWorkspace.cal_when Usage"},{"role":"assistant","content":"`PWN::Plugins::GoogleWorkspace.cal_when`: "}]}
|
|
1851
1852
|
{"messages":[{"role":"user","content":"PWN::Plugins::GoogleWorkspace.calendar_create Usage"},{"role":"assistant","content":"`PWN::Plugins::GoogleWorkspace.calendar_create`: "}]}
|
|
@@ -1876,8 +1877,10 @@
|
|
|
1876
1877
|
{"messages":[{"role":"user","content":"PWN::Plugins::GoogleWorkspace.gmail_search Usage"},{"role":"assistant","content":"`PWN::Plugins::GoogleWorkspace.gmail_search`: "}]}
|
|
1877
1878
|
{"messages":[{"role":"user","content":"PWN::Plugins::GoogleWorkspace.gmail_send Usage"},{"role":"assistant","content":"`PWN::Plugins::GoogleWorkspace.gmail_send`: "}]}
|
|
1878
1879
|
{"messages":[{"role":"user","content":"PWN::Plugins::GoogleWorkspace.help Usage"},{"role":"assistant","content":"`PWN::Plugins::GoogleWorkspace.help`: "}]}
|
|
1880
|
+
{"messages":[{"role":"user","content":"PWN::Plugins::GoogleWorkspace.listener_redirect Usage"},{"role":"assistant","content":"`PWN::Plugins::GoogleWorkspace.listener_redirect`: "}]}
|
|
1879
1881
|
{"messages":[{"role":"user","content":"PWN::Plugins::GoogleWorkspace.load_pending Usage"},{"role":"assistant","content":"`PWN::Plugins::GoogleWorkspace.load_pending`: "}]}
|
|
1880
1882
|
{"messages":[{"role":"user","content":"PWN::Plugins::GoogleWorkspace.obtain_oauth_auth_url Usage"},{"role":"assistant","content":"`PWN::Plugins::GoogleWorkspace.obtain_oauth_auth_url`: "}]}
|
|
1883
|
+
{"messages":[{"role":"user","content":"PWN::Plugins::GoogleWorkspace.open_oauth_listener Usage"},{"role":"assistant","content":"`PWN::Plugins::GoogleWorkspace.open_oauth_listener`: "}]}
|
|
1881
1884
|
{"messages":[{"role":"user","content":"PWN::Plugins::GoogleWorkspace.parse_oauth_code Usage"},{"role":"assistant","content":"`PWN::Plugins::GoogleWorkspace.parse_oauth_code`: "}]}
|
|
1882
1885
|
{"messages":[{"role":"user","content":"PWN::Plugins::GoogleWorkspace.persist_oauth_to_vault Usage"},{"role":"assistant","content":"`PWN::Plugins::GoogleWorkspace.persist_oauth_to_vault`: "}]}
|
|
1883
1886
|
{"messages":[{"role":"user","content":"PWN::Plugins::GoogleWorkspace.pkce_pair Usage"},{"role":"assistant","content":"`PWN::Plugins::GoogleWorkspace.pkce_pair`: "}]}
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: pwn
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.5.
|
|
4
|
+
version: 0.5.695
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- 0day Inc.
|
|
@@ -211,14 +211,14 @@ dependencies:
|
|
|
211
211
|
requirements:
|
|
212
212
|
- - '='
|
|
213
213
|
- !ruby/object:Gem::Version
|
|
214
|
-
version: 1.
|
|
214
|
+
version: 1.7.0
|
|
215
215
|
type: :development
|
|
216
216
|
prerelease: false
|
|
217
217
|
version_requirements: !ruby/object:Gem::Requirement
|
|
218
218
|
requirements:
|
|
219
219
|
- - '='
|
|
220
220
|
- !ruby/object:Gem::Version
|
|
221
|
-
version: 1.
|
|
221
|
+
version: 1.7.0
|
|
222
222
|
- !ruby/object:Gem::Dependency
|
|
223
223
|
name: diffy
|
|
224
224
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -519,14 +519,14 @@ dependencies:
|
|
|
519
519
|
requirements:
|
|
520
520
|
- - '='
|
|
521
521
|
- !ruby/object:Gem::Version
|
|
522
|
-
version: 0.0.
|
|
522
|
+
version: 0.0.175
|
|
523
523
|
type: :development
|
|
524
524
|
prerelease: false
|
|
525
525
|
version_requirements: !ruby/object:Gem::Requirement
|
|
526
526
|
requirements:
|
|
527
527
|
- - '='
|
|
528
528
|
- !ruby/object:Gem::Version
|
|
529
|
-
version: 0.0.
|
|
529
|
+
version: 0.0.175
|
|
530
530
|
- !ruby/object:Gem::Dependency
|
|
531
531
|
name: metasm
|
|
532
532
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -953,14 +953,14 @@ dependencies:
|
|
|
953
953
|
requirements:
|
|
954
954
|
- - '='
|
|
955
955
|
- !ruby/object:Gem::Version
|
|
956
|
-
version: 1.
|
|
956
|
+
version: 1.90.0
|
|
957
957
|
type: :runtime
|
|
958
958
|
prerelease: false
|
|
959
959
|
version_requirements: !ruby/object:Gem::Requirement
|
|
960
960
|
requirements:
|
|
961
961
|
- - '='
|
|
962
962
|
- !ruby/object:Gem::Version
|
|
963
|
-
version: 1.
|
|
963
|
+
version: 1.90.0
|
|
964
964
|
- !ruby/object:Gem::Dependency
|
|
965
965
|
name: rubocop-rake
|
|
966
966
|
requirement: !ruby/object:Gem::Requirement
|