vaultkit 0.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 +7 -0
- data/README.md +961 -0
- data/bin/funl +0 -0
- data/bin/vkit +30 -0
- data/lib/vkit/cli/api/client.rb +115 -0
- data/lib/vkit/cli/base_cli.rb +173 -0
- data/lib/vkit/cli/commands/approval_command.rb +94 -0
- data/lib/vkit/cli/commands/base_command.rb +42 -0
- data/lib/vkit/cli/commands/datasource_command.rb +93 -0
- data/lib/vkit/cli/commands/fetch_command.rb +48 -0
- data/lib/vkit/cli/commands/login_command.rb +136 -0
- data/lib/vkit/cli/commands/logout_command.rb +12 -0
- data/lib/vkit/cli/commands/policy_bundle_command.rb +62 -0
- data/lib/vkit/cli/commands/policy_deploy_command.rb +32 -0
- data/lib/vkit/cli/commands/policy_validate_command.rb +31 -0
- data/lib/vkit/cli/commands/request_command.rb +102 -0
- data/lib/vkit/cli/commands/requests_list_command.rb +47 -0
- data/lib/vkit/cli/commands/scan_command.rb +47 -0
- data/lib/vkit/cli/commands/whoami_command.rb +14 -0
- data/lib/vkit/cli/commands.rb +5 -0
- data/lib/vkit/cli/errors.rb +6 -0
- data/lib/vkit/cli/policy_bundle_validator.rb +71 -0
- data/lib/vkit/cli/requests_cli.rb +23 -0
- data/lib/vkit/cli.rb +4 -0
- data/lib/vkit/core/auth_client.rb +104 -0
- data/lib/vkit/core/credential_resolver.rb +37 -0
- data/lib/vkit/core/credential_store.rb +186 -0
- data/lib/vkit/core/table_formatter.rb +36 -0
- data/lib/vkit/policy/bundle_compiler.rb +154 -0
- data/lib/vkit/policy/schema/policy_bundle.schema.json +296 -0
- data/lib/vkit/policy/validate_bundle.rb +37 -0
- data/lib/vkit/utils/banner.rb +0 -0
- data/lib/vkit/utils/config_loader.rb +0 -0
- data/lib/vkit/utils/logger.rb +0 -0
- data/lib/vkit.rb +3 -0
- metadata +94 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
require "io/console"
|
|
2
|
+
require_relative "../../core/auth_client"
|
|
3
|
+
require_relative "../../core/credential_store"
|
|
4
|
+
require "base64"
|
|
5
|
+
require "json"
|
|
6
|
+
|
|
7
|
+
module Vkit
|
|
8
|
+
module CLI
|
|
9
|
+
module Commands
|
|
10
|
+
class LoginCommand < BaseCommand
|
|
11
|
+
def requires_auth?
|
|
12
|
+
false
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def initialize(endpoint: nil, email: nil)
|
|
16
|
+
@endpoint = endpoint
|
|
17
|
+
@email = email
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def call
|
|
21
|
+
endpoint =
|
|
22
|
+
@endpoint ||
|
|
23
|
+
ENV["VKIT_ENDPOINT"] ||
|
|
24
|
+
credential_store.endpoint ||
|
|
25
|
+
prompt("VaultKit Control Plane URL")
|
|
26
|
+
client = Vkit::Core::AuthClient.new(base_url: endpoint)
|
|
27
|
+
|
|
28
|
+
discovery = client.discover
|
|
29
|
+
auth = discovery["preferred"]
|
|
30
|
+
|
|
31
|
+
result =
|
|
32
|
+
case auth
|
|
33
|
+
when "oidc"
|
|
34
|
+
oidc_flow(client, discovery["oidc"]["login_url"])
|
|
35
|
+
when "password"
|
|
36
|
+
password_flow(client)
|
|
37
|
+
when "token"
|
|
38
|
+
token_flow(client)
|
|
39
|
+
else
|
|
40
|
+
raise "Unsupported auth mode: #{auth}"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
store = Vkit::Core::CredentialStore.new
|
|
44
|
+
store.save(
|
|
45
|
+
endpoint: endpoint,
|
|
46
|
+
token: result[:token],
|
|
47
|
+
user: result[:user]
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
puts "ā
Logged in as #{result[:user]['email']}"
|
|
51
|
+
rescue => e
|
|
52
|
+
puts "ā Login failed: #{e.message}"
|
|
53
|
+
exit 1
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
def oidc_flow(client, login_url)
|
|
59
|
+
start = client.start_cli_login
|
|
60
|
+
poll_token = start["poll_token"]
|
|
61
|
+
|
|
62
|
+
open_browser(login_url)
|
|
63
|
+
puts "ā³ Waiting for authentication to complete..."
|
|
64
|
+
|
|
65
|
+
loop do
|
|
66
|
+
res = client.poll_cli_login(poll_token)
|
|
67
|
+
|
|
68
|
+
case res.code.to_i
|
|
69
|
+
when 204
|
|
70
|
+
sleep 2
|
|
71
|
+
next
|
|
72
|
+
when 200
|
|
73
|
+
body = JSON.parse(res.body)
|
|
74
|
+
return {
|
|
75
|
+
token: body["token"],
|
|
76
|
+
user: body["user"]
|
|
77
|
+
}
|
|
78
|
+
when 410
|
|
79
|
+
raise "Login session expired"
|
|
80
|
+
when 404
|
|
81
|
+
raise "Invalid login session"
|
|
82
|
+
else
|
|
83
|
+
raise "Unexpected response: #{res.code}"
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def password_flow(client)
|
|
89
|
+
email = @email || prompt("Email")
|
|
90
|
+
password = prompt_password("Password")
|
|
91
|
+
|
|
92
|
+
res = client.password_login(email: email, password: password)
|
|
93
|
+
|
|
94
|
+
{
|
|
95
|
+
token: res[:token],
|
|
96
|
+
user: res[:user]
|
|
97
|
+
}
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def token_flow(client)
|
|
101
|
+
token = ENV["VAULTKIT_TOKEN"] || prompt_password("API Token")
|
|
102
|
+
user = client.whoami(token)
|
|
103
|
+
|
|
104
|
+
{
|
|
105
|
+
token: token,
|
|
106
|
+
user: user
|
|
107
|
+
}
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def prompt(label)
|
|
111
|
+
print "#{label}: "
|
|
112
|
+
STDIN.gets.strip
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def prompt_password(label)
|
|
116
|
+
print "#{label}: "
|
|
117
|
+
STDIN.noecho(&:gets).to_s.strip.tap { puts }
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def open_browser(url)
|
|
121
|
+
os = RbConfig::CONFIG["host_os"]
|
|
122
|
+
|
|
123
|
+
if os =~ /darwin/
|
|
124
|
+
system("open", url)
|
|
125
|
+
elsif os =~ /linux/
|
|
126
|
+
system("xdg-open", url)
|
|
127
|
+
elsif os =~ /mswin|mingw|cygwin/
|
|
128
|
+
system("start", url)
|
|
129
|
+
else
|
|
130
|
+
puts "Open this URL in your browser:\n#{url}"
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
require_relative "../../policy/bundle_compiler"
|
|
3
|
+
|
|
4
|
+
module Vkit
|
|
5
|
+
module CLI
|
|
6
|
+
module Commands
|
|
7
|
+
class PolicyBundleCommand
|
|
8
|
+
def call(policies_dir:, registry_dir:, out:, org:, version:)
|
|
9
|
+
policies_dir = File.expand_path(policies_dir)
|
|
10
|
+
registry_dir = File.expand_path(registry_dir)
|
|
11
|
+
out = File.expand_path(out)
|
|
12
|
+
|
|
13
|
+
raise "Policies dir not found: #{policies_dir}" unless Dir.exist?(policies_dir)
|
|
14
|
+
raise "Registry dir not found: #{registry_dir}" unless Dir.exist?(registry_dir)
|
|
15
|
+
|
|
16
|
+
version ||= git_sha
|
|
17
|
+
|
|
18
|
+
bundle = Vkit::Policy::BundleCompiler.compile!(
|
|
19
|
+
org_slug: org || "unknown",
|
|
20
|
+
bundle_version: version,
|
|
21
|
+
policies_dir: policies_dir,
|
|
22
|
+
registry_dir: registry_dir,
|
|
23
|
+
source: {
|
|
24
|
+
repo: git_repo,
|
|
25
|
+
ref: git_ref,
|
|
26
|
+
commit_sha: version
|
|
27
|
+
}
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
FileUtils.mkdir_p(File.dirname(out))
|
|
31
|
+
File.write(out, JSON.pretty_generate(bundle))
|
|
32
|
+
|
|
33
|
+
puts "ā
Policy bundle created"
|
|
34
|
+
puts " Org: #{bundle.dig("bundle", "org_slug")}"
|
|
35
|
+
puts " Version: #{bundle.dig("bundle", "bundle_version")}"
|
|
36
|
+
puts " Checksum: #{bundle.dig("bundle", "checksum")}"
|
|
37
|
+
puts " Output: #{out}"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def git_sha
|
|
43
|
+
`git rev-parse HEAD`.strip
|
|
44
|
+
rescue
|
|
45
|
+
Time.now.to_i.to_s
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def git_repo
|
|
49
|
+
`git config --get remote.origin.url`.strip
|
|
50
|
+
rescue
|
|
51
|
+
nil
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def git_ref
|
|
55
|
+
`git rev-parse --abbrev-ref HEAD`.strip
|
|
56
|
+
rescue
|
|
57
|
+
nil
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module Vkit
|
|
6
|
+
module CLI
|
|
7
|
+
module Commands
|
|
8
|
+
class PolicyDeployCommand < BaseCommand
|
|
9
|
+
def call(bundle_path:, org:, activate:)
|
|
10
|
+
with_auth do
|
|
11
|
+
bundle_path = File.expand_path(bundle_path)
|
|
12
|
+
raise "Bundle not found: #{bundle_path}" unless File.exist?(bundle_path)
|
|
13
|
+
|
|
14
|
+
bundle = JSON.parse(File.read(bundle_path))
|
|
15
|
+
|
|
16
|
+
response = authenticated_client.post(
|
|
17
|
+
"/api/v1/orgs/#{org}/policy_bundles",
|
|
18
|
+
body: {
|
|
19
|
+
bundle: bundle,
|
|
20
|
+
activate: activate
|
|
21
|
+
}
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
puts "š Policy bundle deployed"
|
|
25
|
+
puts " Version: #{response['bundle_version']}"
|
|
26
|
+
puts " State: #{response['state']}"
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
require_relative "../policy_bundle_validator"
|
|
2
|
+
|
|
3
|
+
module Vkit
|
|
4
|
+
module CLI
|
|
5
|
+
module Commands
|
|
6
|
+
class PolicyValidateCommand
|
|
7
|
+
def call(bundle_path:, schema_path:)
|
|
8
|
+
bundle_path = File.expand_path(bundle_path)
|
|
9
|
+
raise "Bundle not found: #{bundle_path}" unless File.exist?(bundle_path)
|
|
10
|
+
|
|
11
|
+
schema_path ||= default_schema_path
|
|
12
|
+
raise "Schema not found: #{schema_path}" unless File.exist?(schema_path)
|
|
13
|
+
|
|
14
|
+
validator = Vkit::CLI::PolicyBundleValidator.new(
|
|
15
|
+
schema_path: schema_path
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
validator.validate!(bundle_path)
|
|
19
|
+
|
|
20
|
+
puts "ā
Policy bundle is valid"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def default_schema_path
|
|
26
|
+
File.expand_path("../../policy/schema/policy_bundle.schema.json", __dir__)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "time"
|
|
5
|
+
require_relative "../../core/table_formatter"
|
|
6
|
+
|
|
7
|
+
module Vkit
|
|
8
|
+
module CLI
|
|
9
|
+
module Commands
|
|
10
|
+
class RequestCommand < BaseCommand
|
|
11
|
+
def call(aql_str, options)
|
|
12
|
+
with_auth do
|
|
13
|
+
user = credential_store.user
|
|
14
|
+
org = user["organization_slug"]
|
|
15
|
+
|
|
16
|
+
aql =
|
|
17
|
+
if aql_str&.strip&.length&.positive?
|
|
18
|
+
JSON.parse(aql_str)
|
|
19
|
+
else
|
|
20
|
+
JSON.parse(STDIN.read)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
response = authenticated_client.post(
|
|
24
|
+
"/api/v1/orgs/#{org}/requests",
|
|
25
|
+
body: {
|
|
26
|
+
aql: aql,
|
|
27
|
+
options: {
|
|
28
|
+
environment: options[:env],
|
|
29
|
+
requester_region: options[:requester_region],
|
|
30
|
+
dataset_region: options[:dataset_region],
|
|
31
|
+
requester_clearance: options[:requester_clearance],
|
|
32
|
+
datasource: options[:datasource]
|
|
33
|
+
}.compact
|
|
34
|
+
}
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
handle_result(response, options)
|
|
38
|
+
end
|
|
39
|
+
rescue JSON::ParserError
|
|
40
|
+
raise "Invalid JSON AQL"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def handle_result(result, options)
|
|
46
|
+
status = result["status"]
|
|
47
|
+
|
|
48
|
+
if options[:format] == "json"
|
|
49
|
+
puts JSON.pretty_generate(result)
|
|
50
|
+
exit status == "denied" ? 2 : 0
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
case status
|
|
54
|
+
when "denied"
|
|
55
|
+
puts "ā DENIED"
|
|
56
|
+
puts "Reason: #{result["reason"]}"
|
|
57
|
+
exit 2
|
|
58
|
+
|
|
59
|
+
when "queued"
|
|
60
|
+
puts "ā³ QUEUED for approval"
|
|
61
|
+
puts "Request ID: #{result["request_id"]}"
|
|
62
|
+
exit 0
|
|
63
|
+
|
|
64
|
+
when "granted"
|
|
65
|
+
expires_at = Time.parse(result["expires_at"]).getlocal
|
|
66
|
+
|
|
67
|
+
puts "ā
ACCESS GRANTED"
|
|
68
|
+
puts "Grant ID: #{result["grant_id"]}"
|
|
69
|
+
puts "Expires: #{expires_at.strftime("%Y-%m-%d %H:%M:%S %Z")}"
|
|
70
|
+
|
|
71
|
+
if (mask = result["masked_fields"]) && mask.any?
|
|
72
|
+
puts "Masked Fields: #{mask.join(', ')}"
|
|
73
|
+
else
|
|
74
|
+
puts "Masked Fields: none"
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
puts
|
|
78
|
+
puts "To execute and retrieve the data, run:"
|
|
79
|
+
puts " vkit fetch --grant #{result["grant_ref"] || result["grant_id"]}"
|
|
80
|
+
exit 0
|
|
81
|
+
|
|
82
|
+
when "ok"
|
|
83
|
+
rows = result["rows"] || []
|
|
84
|
+
|
|
85
|
+
puts "ā
OK ā #{rows.size} rows"
|
|
86
|
+
puts "ā¹ļø Query Metadata:"
|
|
87
|
+
puts JSON.pretty_generate(result["meta"] || {})
|
|
88
|
+
puts
|
|
89
|
+
puts "ā¹ļø Data Rows:"
|
|
90
|
+
Vkit::Core::TableFormatter.render(rows)
|
|
91
|
+
exit 0
|
|
92
|
+
|
|
93
|
+
else
|
|
94
|
+
puts "ā Unexpected response:"
|
|
95
|
+
puts result.inspect
|
|
96
|
+
exit 1
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require_relative "../../core/table_formatter"
|
|
5
|
+
|
|
6
|
+
module Vkit
|
|
7
|
+
module CLI
|
|
8
|
+
module Commands
|
|
9
|
+
class RequestsListCommand < BaseCommand
|
|
10
|
+
def call(state:, format:)
|
|
11
|
+
with_auth do
|
|
12
|
+
user = credential_store.user
|
|
13
|
+
org = user["organization_slug"]
|
|
14
|
+
|
|
15
|
+
params = {}
|
|
16
|
+
params[:state] = state unless state == "all"
|
|
17
|
+
|
|
18
|
+
response = authenticated_client.get(
|
|
19
|
+
"/api/v1/orgs/#{org}/requests",
|
|
20
|
+
params: params
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
render(response, format)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def render(rows, format)
|
|
30
|
+
if rows.empty?
|
|
31
|
+
puts "š No requests found"
|
|
32
|
+
return
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
case format
|
|
36
|
+
when "json"
|
|
37
|
+
puts JSON.pretty_generate(rows)
|
|
38
|
+
when "table"
|
|
39
|
+
Vkit::Core::TableFormatter.render(rows)
|
|
40
|
+
else
|
|
41
|
+
raise "Unknown format: #{format}"
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module Vkit
|
|
6
|
+
module CLI
|
|
7
|
+
module Commands
|
|
8
|
+
class ScanCommand < BaseCommand
|
|
9
|
+
def call(datasource_name, mode: "diff_only")
|
|
10
|
+
with_auth do
|
|
11
|
+
user = credential_store.user
|
|
12
|
+
org = user["organization_slug"]
|
|
13
|
+
|
|
14
|
+
puts "š Running scan for datasource '#{datasource_name}' (mode=#{mode})..."
|
|
15
|
+
|
|
16
|
+
response = authenticated_client.post(
|
|
17
|
+
"/api/v1/orgs/#{org}/datasources/#{datasource_name}/scan",
|
|
18
|
+
body: {
|
|
19
|
+
datasource: datasource_name,
|
|
20
|
+
mode: mode
|
|
21
|
+
}
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
puts "ā
Scan completed"
|
|
25
|
+
puts "š Scan ID: #{response["scan_id"]}"
|
|
26
|
+
puts "š Mode: #{response["mode"]}"
|
|
27
|
+
|
|
28
|
+
diff = response["diff"] || {}
|
|
29
|
+
|
|
30
|
+
if diff.empty?
|
|
31
|
+
puts "⨠No changes detected"
|
|
32
|
+
else
|
|
33
|
+
puts "š Registry diff:"
|
|
34
|
+
puts "ā" * 50
|
|
35
|
+
puts JSON.pretty_generate(diff)
|
|
36
|
+
puts "ā" * 50
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
if response.key?("applied")
|
|
40
|
+
puts response["applied"] ? "ā
Changes applied" : "ā¹ļø Changes not applied"
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module Vkit
|
|
2
|
+
module CLI
|
|
3
|
+
module Commands
|
|
4
|
+
class WhoamiCommand < BaseCommand
|
|
5
|
+
def call
|
|
6
|
+
with_auth do
|
|
7
|
+
user = credential_store.user
|
|
8
|
+
puts "š¤ #{user['email']} (role: #{user['role']}, org: #{user['organization_slug']})"
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
require "json_schemer"
|
|
3
|
+
|
|
4
|
+
module Vkit
|
|
5
|
+
module CLI
|
|
6
|
+
class PolicyBundleValidator
|
|
7
|
+
def initialize(schema_path:)
|
|
8
|
+
@schema = JSON.parse(File.read(schema_path))
|
|
9
|
+
@schemer = JSONSchemer.schema(@schema)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def validate!(bundle_path)
|
|
13
|
+
bundle = JSON.parse(File.read(bundle_path))
|
|
14
|
+
errors = @schemer.validate(bundle).to_a
|
|
15
|
+
|
|
16
|
+
return if errors.empty?
|
|
17
|
+
|
|
18
|
+
puts "\nā Policy bundle validation failed\n\n"
|
|
19
|
+
|
|
20
|
+
errors.each do |err|
|
|
21
|
+
puts format_error(err, bundle)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
puts "\nFix the errors above and re-run validation."
|
|
25
|
+
exit 1
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def format_error(err, bundle)
|
|
31
|
+
pointer = err["data_pointer"]
|
|
32
|
+
policy = policy_from_pointer(pointer, bundle)
|
|
33
|
+
|
|
34
|
+
msg = []
|
|
35
|
+
msg << "Policy: #{policy || 'global'}"
|
|
36
|
+
msg << "Path: #{human_path(pointer)}"
|
|
37
|
+
msg << "Error: #{human_message(err)}"
|
|
38
|
+
|
|
39
|
+
msg.join("\n ")
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def policy_from_pointer(pointer, bundle)
|
|
43
|
+
return nil unless pointer =~ %r{/policies/(\d+)}
|
|
44
|
+
|
|
45
|
+
index = Regexp.last_match(1).to_i
|
|
46
|
+
bundle.dig("policies", index, "id")
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def human_path(pointer)
|
|
50
|
+
pointer
|
|
51
|
+
.gsub("/", ".")
|
|
52
|
+
.sub(".", "")
|
|
53
|
+
.gsub(/\.(\d+)/, '[\1]')
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def human_message(err)
|
|
57
|
+
case err["error"]
|
|
58
|
+
when /missing required properties/
|
|
59
|
+
missing = err.dig("details", "missing_keys")&.join(", ")
|
|
60
|
+
"Missing required field(s): #{missing}"
|
|
61
|
+
when /disallowed additional property/
|
|
62
|
+
"This field is not allowed in the compiled policy bundle"
|
|
63
|
+
when /type/
|
|
64
|
+
"Invalid value type"
|
|
65
|
+
else
|
|
66
|
+
err["error"]
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module Vkit
|
|
2
|
+
module CLI
|
|
3
|
+
class RequestsCLI < Thor
|
|
4
|
+
desc "list", "List your past requests"
|
|
5
|
+
option :state,
|
|
6
|
+
type: :string,
|
|
7
|
+
default: "all",
|
|
8
|
+
enum: %w[all pending approved denied]
|
|
9
|
+
|
|
10
|
+
option :format,
|
|
11
|
+
type: :string,
|
|
12
|
+
default: "table",
|
|
13
|
+
enum: %w[json table]
|
|
14
|
+
|
|
15
|
+
def list
|
|
16
|
+
Commands::RequestsListCommand.new.call(
|
|
17
|
+
state: options[:state],
|
|
18
|
+
format: options[:format]
|
|
19
|
+
)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
data/lib/vkit/cli.rb
ADDED