keeper_secrets_manager 17.0.3
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/.rspec +3 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +49 -0
- data/Gemfile +13 -0
- data/LICENSE +21 -0
- data/README.md +305 -0
- data/Rakefile +30 -0
- data/examples/basic_usage.rb +139 -0
- data/examples/config_string_example.rb +99 -0
- data/examples/debug_secrets.rb +84 -0
- data/examples/demo_list_secrets.rb +182 -0
- data/examples/download_files.rb +100 -0
- data/examples/flexible_records_example.rb +94 -0
- data/examples/folder_hierarchy_demo.rb +109 -0
- data/examples/full_demo.rb +176 -0
- data/examples/my_test_standalone.rb +176 -0
- data/examples/simple_test.rb +162 -0
- data/examples/storage_examples.rb +126 -0
- data/lib/keeper_secrets_manager/config_keys.rb +27 -0
- data/lib/keeper_secrets_manager/core.rb +1231 -0
- data/lib/keeper_secrets_manager/crypto.rb +348 -0
- data/lib/keeper_secrets_manager/dto/payload.rb +152 -0
- data/lib/keeper_secrets_manager/dto.rb +221 -0
- data/lib/keeper_secrets_manager/errors.rb +79 -0
- data/lib/keeper_secrets_manager/field_types.rb +152 -0
- data/lib/keeper_secrets_manager/folder_manager.rb +114 -0
- data/lib/keeper_secrets_manager/keeper_globals.rb +59 -0
- data/lib/keeper_secrets_manager/notation.rb +354 -0
- data/lib/keeper_secrets_manager/notation_enhancements.rb +67 -0
- data/lib/keeper_secrets_manager/storage.rb +254 -0
- data/lib/keeper_secrets_manager/totp.rb +140 -0
- data/lib/keeper_secrets_manager/utils.rb +196 -0
- data/lib/keeper_secrets_manager/version.rb +3 -0
- data/lib/keeper_secrets_manager.rb +38 -0
- metadata +82 -0
@@ -0,0 +1,84 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# Add the Ruby SDK to the load path
|
4
|
+
require_relative '../lib/keeper_secrets_manager'
|
5
|
+
|
6
|
+
# Load the SDK
|
7
|
+
require 'json'
|
8
|
+
|
9
|
+
# Your token
|
10
|
+
TOKEN = ENV['KSM_TOKEN'] || 'US:YOUR_ONE_TIME_TOKEN_HERE'
|
11
|
+
|
12
|
+
puts "=== Debugging Keeper Secrets Manager ==="
|
13
|
+
|
14
|
+
begin
|
15
|
+
# Connect to Keeper with debug logging
|
16
|
+
puts "\n1. Connecting to Keeper..."
|
17
|
+
# Option 1: Use base64 config
|
18
|
+
config_base64 = ENV['KSM_CONFIG_BASE64'] || 'YOUR_BASE64_CONFIG_HERE'
|
19
|
+
storage_from_b64 = KeeperSecretsManager::Storage::InMemoryStorage.new(config_base64)
|
20
|
+
sm = KeeperSecretsManager.new(config: storage_from_b64)
|
21
|
+
# Alternative: Use in-memory storage
|
22
|
+
# storage = KeeperSecretsManager::Storage::InMemoryStorage.new
|
23
|
+
# sm = KeeperSecretsManager.new(token: TOKEN, config: storage)
|
24
|
+
#
|
25
|
+
# The storage now contains the credentials, so you can reuse it:
|
26
|
+
# sm2 = KeeperSecretsManager.new(config: storage) # No token needed!
|
27
|
+
|
28
|
+
# Get all secrets with raw response
|
29
|
+
puts "\n2. Fetching secrets (this calls the internal method)..."
|
30
|
+
|
31
|
+
# Let's look at what the SDK is actually doing internally
|
32
|
+
# Get the raw response to see what's coming from the server
|
33
|
+
response = sm.instance_eval do
|
34
|
+
fetch_and_decrypt_secrets
|
35
|
+
end
|
36
|
+
|
37
|
+
puts "\n3. Response Analysis:"
|
38
|
+
puts " - Total records returned: #{response[:records].length}"
|
39
|
+
puts " - Total folders returned: #{response[:folders].length}"
|
40
|
+
puts " - Warnings: #{response[:warnings]}"
|
41
|
+
|
42
|
+
# Check if there are any records at all
|
43
|
+
if response[:records].empty?
|
44
|
+
puts "\n ⚠️ No Secrets Manager-compatible records found!"
|
45
|
+
puts " The warning indicates you have 4 'general' type records."
|
46
|
+
puts " These are not accessible via the Secrets Manager SDK."
|
47
|
+
else
|
48
|
+
puts "\n4. Records found:"
|
49
|
+
response[:records].each_with_index do |record, i|
|
50
|
+
puts " #{i+1}. #{record.title} (Type: #{record.type}, UID: #{record.uid})"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Try to get records with specific UIDs from the warning
|
55
|
+
puts "\n5. Attempting to fetch specific UIDs from warning..."
|
56
|
+
warning_uids = ['AwOCl5W2RubtkFSuL72jXg', 'bX7uYVNsujLoPjrvEFA3Rg', 'gSnSFvd_UI9CnK_5kSZ85Q', 'wIT61_GnuBIpUOPxOrgPeQ']
|
57
|
+
|
58
|
+
warning_uids.each do |uid|
|
59
|
+
begin
|
60
|
+
records = sm.get_secrets([uid])
|
61
|
+
if records.any?
|
62
|
+
puts " ✓ Found: #{uid}"
|
63
|
+
else
|
64
|
+
puts " ✗ Not accessible: #{uid} (general type)"
|
65
|
+
end
|
66
|
+
rescue => e
|
67
|
+
puts " ✗ Error fetching #{uid}: #{e.message}"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
puts "\n6. Summary:"
|
72
|
+
puts " - Your vault contains 'general' type records"
|
73
|
+
puts " - These are legacy Keeper records not designed for API access"
|
74
|
+
puts " - To use Secrets Manager, create records with specific types like:"
|
75
|
+
puts " • login"
|
76
|
+
puts " • databaseCredentials"
|
77
|
+
puts " • sshKeys"
|
78
|
+
puts " • serverCredentials"
|
79
|
+
puts " • etc."
|
80
|
+
|
81
|
+
rescue => e
|
82
|
+
puts "\nError: #{e.message}"
|
83
|
+
puts e.backtrace.first(5).join("\n")
|
84
|
+
end
|
@@ -0,0 +1,182 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# This script demonstrates the Ruby SDK by listing all secrets in your vault
|
4
|
+
# It uses the real config.base64 file to connect to Keeper
|
5
|
+
|
6
|
+
require_relative 'lib/keeper_secrets_manager'
|
7
|
+
require 'base64'
|
8
|
+
require 'json'
|
9
|
+
|
10
|
+
puts "=== Keeper Secrets Manager Ruby SDK Demo ==="
|
11
|
+
puts "Connecting to Keeper and listing all secrets..."
|
12
|
+
puts
|
13
|
+
|
14
|
+
begin
|
15
|
+
# Load the real configuration
|
16
|
+
config_file = File.expand_path('config.base64', __dir__)
|
17
|
+
unless File.exist?(config_file)
|
18
|
+
puts "Error: config.base64 not found at #{config_file}"
|
19
|
+
exit 1
|
20
|
+
end
|
21
|
+
|
22
|
+
config_base64 = File.read(config_file).strip
|
23
|
+
config_json = Base64.decode64(config_base64)
|
24
|
+
config_data = JSON.parse(config_json)
|
25
|
+
|
26
|
+
# Display connection info (partial)
|
27
|
+
puts "Configuration loaded:"
|
28
|
+
puts " Hostname: #{config_data['hostname']}"
|
29
|
+
puts " Client ID: #{config_data['clientId'][0..30]}..."
|
30
|
+
puts " Has appKey: #{config_data.key?('appKey')}"
|
31
|
+
puts " Has privateKey: #{config_data.key?('privateKey')}"
|
32
|
+
puts " Has serverPublicKeyId: #{config_data['serverPublicKeyId']}"
|
33
|
+
puts
|
34
|
+
|
35
|
+
# Create storage and SDK instance
|
36
|
+
storage = KeeperSecretsManager::Storage::InMemoryStorage.new(config_data)
|
37
|
+
|
38
|
+
# Enable debug logging
|
39
|
+
require 'logger'
|
40
|
+
logger = Logger.new(STDOUT)
|
41
|
+
logger.level = Logger::DEBUG
|
42
|
+
|
43
|
+
secrets_manager = KeeperSecretsManager.new(config: storage, logger: logger, log_level: Logger::DEBUG)
|
44
|
+
|
45
|
+
# Get all secrets
|
46
|
+
puts "Fetching all secrets..."
|
47
|
+
records = secrets_manager.get_secrets
|
48
|
+
|
49
|
+
if records.empty?
|
50
|
+
puts "\nNo secrets found in the vault."
|
51
|
+
else
|
52
|
+
puts "\nFound #{records.length} secret(s):"
|
53
|
+
puts "-" * 80
|
54
|
+
|
55
|
+
records.each_with_index do |record, index|
|
56
|
+
puts "\n#{index + 1}. #{record.title}"
|
57
|
+
puts " UID: #{record.uid}"
|
58
|
+
puts " Type: #{record.type}"
|
59
|
+
|
60
|
+
# Show fields (hide sensitive values)
|
61
|
+
if record.fields.any?
|
62
|
+
puts " Fields:"
|
63
|
+
record.fields.each do |field|
|
64
|
+
field_type = field['type']
|
65
|
+
field_label = field['label'] ? " (#{field['label']})" : ""
|
66
|
+
|
67
|
+
# Show field info but mask sensitive data
|
68
|
+
case field_type
|
69
|
+
when 'login', 'email'
|
70
|
+
value = record.get_field_value_single(field_type)
|
71
|
+
puts " - #{field_type}#{field_label}: #{value}"
|
72
|
+
when 'password', 'secret', 'privateKey'
|
73
|
+
puts " - #{field_type}#{field_label}: [HIDDEN]"
|
74
|
+
when 'url'
|
75
|
+
values = record.get_field_value(field_type)
|
76
|
+
if values.length == 1
|
77
|
+
puts " - #{field_type}#{field_label}: #{values.first}"
|
78
|
+
else
|
79
|
+
puts " - #{field_type}#{field_label}: #{values.length} URLs"
|
80
|
+
end
|
81
|
+
when 'host'
|
82
|
+
host = record.get_field_value_single(field_type)
|
83
|
+
if host.is_a?(Hash)
|
84
|
+
puts " - #{field_type}#{field_label}: #{host['hostName']}:#{host['port'] || '22'}"
|
85
|
+
end
|
86
|
+
when 'phone'
|
87
|
+
phone = record.get_field_value_single(field_type)
|
88
|
+
if phone.is_a?(Hash)
|
89
|
+
puts " - #{field_type}#{field_label}: #{phone['number']} (#{phone['region']})"
|
90
|
+
end
|
91
|
+
when 'name'
|
92
|
+
name = record.get_field_value_single(field_type)
|
93
|
+
if name.is_a?(Hash)
|
94
|
+
full_name = [name['first'], name['middle'], name['last']].compact.join(' ')
|
95
|
+
puts " - #{field_type}#{field_label}: #{full_name}"
|
96
|
+
end
|
97
|
+
else
|
98
|
+
puts " - #{field_type}#{field_label}: [#{field['value']&.length || 0} value(s)]"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# Show custom fields
|
104
|
+
if record.custom.any?
|
105
|
+
puts " Custom Fields:"
|
106
|
+
record.custom.each do |field|
|
107
|
+
label = field['label'] || field['type']
|
108
|
+
value_count = field['value']&.length || 0
|
109
|
+
puts " - #{label}: [#{value_count} value(s)]"
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# Show notes (first 50 chars)
|
114
|
+
if record.notes && !record.notes.empty?
|
115
|
+
notes_preview = record.notes[0..50]
|
116
|
+
notes_preview += "..." if record.notes.length > 50
|
117
|
+
puts " Notes: #{notes_preview}"
|
118
|
+
end
|
119
|
+
|
120
|
+
# Show file count
|
121
|
+
if record.files && record.files.any?
|
122
|
+
puts " Files: #{record.files.length} attachment(s)"
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
puts "\n" + "-" * 80
|
127
|
+
end
|
128
|
+
|
129
|
+
# Get folders
|
130
|
+
puts "\nFetching folders..."
|
131
|
+
folders = secrets_manager.get_folders
|
132
|
+
|
133
|
+
if folders.empty?
|
134
|
+
puts "No folders found."
|
135
|
+
else
|
136
|
+
puts "Found #{folders.length} folder(s):"
|
137
|
+
folders.each do |folder|
|
138
|
+
puts " - #{folder.name} (#{folder.uid})"
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
# Demonstrate notation
|
143
|
+
if records.any?
|
144
|
+
puts "\n" + "=" * 80
|
145
|
+
puts "Notation Examples (using first record):"
|
146
|
+
puts "=" * 80
|
147
|
+
|
148
|
+
first_record = records.first
|
149
|
+
|
150
|
+
# Try various notations
|
151
|
+
notations = [
|
152
|
+
"keeper://#{first_record.uid}/type",
|
153
|
+
"keeper://#{first_record.uid}/title",
|
154
|
+
"keeper://#{first_record.uid}/field/login"
|
155
|
+
]
|
156
|
+
|
157
|
+
notations.each do |notation|
|
158
|
+
begin
|
159
|
+
value = secrets_manager.get_notation(notation)
|
160
|
+
puts "#{notation}"
|
161
|
+
puts " => #{value}"
|
162
|
+
rescue => e
|
163
|
+
puts "#{notation}"
|
164
|
+
puts " => Error: #{e.message}"
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
puts "\n✅ SDK is working correctly!"
|
170
|
+
puts "\nCapabilities demonstrated:"
|
171
|
+
puts "- ✓ Connected to Keeper"
|
172
|
+
puts "- ✓ Retrieved all secrets"
|
173
|
+
puts "- ✓ Accessed field values"
|
174
|
+
puts "- ✓ Listed folders"
|
175
|
+
puts "- ✓ Notation parsing"
|
176
|
+
|
177
|
+
rescue => e
|
178
|
+
puts "\n❌ Error: #{e.class} - #{e.message}"
|
179
|
+
puts "\nBacktrace:"
|
180
|
+
puts e.backtrace.first(5).join("\n")
|
181
|
+
exit 1
|
182
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# Add the Ruby SDK to the load path
|
4
|
+
$LOAD_PATH.unshift('/Users/mustinov/Source/secrets-manager/sdk/ruby/lib')
|
5
|
+
|
6
|
+
# Load the SDK
|
7
|
+
require 'keeper_secrets_manager'
|
8
|
+
require 'fileutils'
|
9
|
+
|
10
|
+
# Helper method to sanitize filename
|
11
|
+
def sanitize_filename(filename)
|
12
|
+
# Remove or replace invalid characters
|
13
|
+
filename.gsub(/[<>:"|?*\/\\]/, '_').strip
|
14
|
+
end
|
15
|
+
|
16
|
+
begin
|
17
|
+
# Connect to Keeper
|
18
|
+
puts "Connecting to Keeper..."
|
19
|
+
# IMPORTANT: Replace with your own configuration
|
20
|
+
# Option 1: Use one-time token
|
21
|
+
# token = 'US:YOUR_ONE_TIME_TOKEN_HERE'
|
22
|
+
# sm = KeeperSecretsManager.from_token(token)
|
23
|
+
#
|
24
|
+
# Option 2: Use base64 config
|
25
|
+
config_base64 = ENV['KSM_CONFIG_BASE64'] || 'YOUR_BASE64_CONFIG_HERE'
|
26
|
+
storage_from_b64 = KeeperSecretsManager::Storage::InMemoryStorage.new(config_base64)
|
27
|
+
sm = KeeperSecretsManager.new(config: storage_from_b64)
|
28
|
+
|
29
|
+
# Get all secrets
|
30
|
+
puts "\nFetching secrets..."
|
31
|
+
response = sm.get_secrets(full_response: true)
|
32
|
+
secrets = response.records
|
33
|
+
|
34
|
+
# Find secrets with files
|
35
|
+
secrets_with_files = secrets.select { |s| s.files && s.files.any? }
|
36
|
+
|
37
|
+
if secrets_with_files.empty?
|
38
|
+
puts "\nNo secrets with files found."
|
39
|
+
exit
|
40
|
+
end
|
41
|
+
|
42
|
+
puts "\nFound #{secrets_with_files.length} secrets with files:"
|
43
|
+
|
44
|
+
# Create downloads directory
|
45
|
+
download_dir = "downloads"
|
46
|
+
FileUtils.mkdir_p(download_dir)
|
47
|
+
|
48
|
+
total_files = 0
|
49
|
+
downloaded_files = 0
|
50
|
+
failed_files = 0
|
51
|
+
|
52
|
+
secrets_with_files.each do |secret|
|
53
|
+
puts "\n📁 #{secret.title} (#{secret.files.length} files)"
|
54
|
+
|
55
|
+
secret.files.each do |file|
|
56
|
+
total_files += 1
|
57
|
+
file_name = file['title'] || file['name'] || 'unnamed'
|
58
|
+
file_size = file['size'] || 0
|
59
|
+
file_uid = file['fileUid'] || file['uid']
|
60
|
+
|
61
|
+
print " - #{file_name} (#{file_size} bytes) ... "
|
62
|
+
|
63
|
+
if file_uid
|
64
|
+
begin
|
65
|
+
# Download the file (pass the whole file object)
|
66
|
+
downloaded_file = sm.download_file(file)
|
67
|
+
|
68
|
+
# Create subdirectory for the secret
|
69
|
+
secret_dir = File.join(download_dir, sanitize_filename(secret.title))
|
70
|
+
FileUtils.mkdir_p(secret_dir)
|
71
|
+
|
72
|
+
# Save the file (sanitize the filename to remove path components)
|
73
|
+
safe_filename = File.basename(downloaded_file['name'] || file_name)
|
74
|
+
file_path = File.join(secret_dir, safe_filename)
|
75
|
+
File.binwrite(file_path, downloaded_file['data'])
|
76
|
+
|
77
|
+
downloaded_files += 1
|
78
|
+
puts "✅ Saved"
|
79
|
+
rescue => e
|
80
|
+
failed_files += 1
|
81
|
+
puts "❌ Failed: #{e.message}"
|
82
|
+
end
|
83
|
+
else
|
84
|
+
failed_files += 1
|
85
|
+
puts "❌ No file UID"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
puts "\n" + "=" * 60
|
91
|
+
puts "Download Summary:"
|
92
|
+
puts " Total files: #{total_files}"
|
93
|
+
puts " ✅ Downloaded: #{downloaded_files}"
|
94
|
+
puts " ❌ Failed: #{failed_files}"
|
95
|
+
puts "\nFiles saved to: #{File.expand_path(download_dir)}/"
|
96
|
+
|
97
|
+
rescue => e
|
98
|
+
puts "\nError: #{e.message}"
|
99
|
+
puts e.backtrace
|
100
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require_relative '../lib/keeper_secrets_manager'
|
4
|
+
|
5
|
+
# Example showing flexible record creation in multiple ways
|
6
|
+
|
7
|
+
# Method 1: Direct hash-based creation (JavaScript-style)
|
8
|
+
record1 = KeeperSecretsManager::Dto::KeeperRecord.new(
|
9
|
+
title: 'My Login',
|
10
|
+
type: 'login',
|
11
|
+
fields: [
|
12
|
+
{ 'type' => 'login', 'value' => ['my_username'] },
|
13
|
+
{ 'type' => 'password', 'value' => ['my_password'] },
|
14
|
+
{ 'type' => 'url', 'value' => ['https://example.com'] },
|
15
|
+
{
|
16
|
+
'type' => 'host',
|
17
|
+
'value' => [{ 'hostName' => '192.168.1.1', 'port' => '8080' }],
|
18
|
+
'label' => 'Custom Host'
|
19
|
+
}
|
20
|
+
],
|
21
|
+
custom: [
|
22
|
+
{
|
23
|
+
'type' => 'phone',
|
24
|
+
'value' => [{ 'region' => 'US', 'number' => '555-1234' }],
|
25
|
+
'label' => 'Work Phone'
|
26
|
+
}
|
27
|
+
],
|
28
|
+
notes: 'Example login record'
|
29
|
+
)
|
30
|
+
|
31
|
+
# Method 2: Using field helpers (optional convenience)
|
32
|
+
record2 = KeeperSecretsManager::Dto::KeeperRecord.new(
|
33
|
+
title: 'My Server',
|
34
|
+
type: 'sshKeys'
|
35
|
+
)
|
36
|
+
|
37
|
+
# Add fields dynamically
|
38
|
+
record2.set_field('login', 'root')
|
39
|
+
record2.set_field('sshKey', {
|
40
|
+
'privateKey' => '-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----',
|
41
|
+
'publicKey' => 'ssh-rsa AAAAB3...'
|
42
|
+
})
|
43
|
+
|
44
|
+
# Method 3: Using dynamic accessors (Ruby magic)
|
45
|
+
record3 = KeeperSecretsManager::Dto::KeeperRecord.new(
|
46
|
+
title: 'Dynamic Record',
|
47
|
+
type: 'login'
|
48
|
+
)
|
49
|
+
|
50
|
+
# These create fields dynamically!
|
51
|
+
record3.login = 'john_doe'
|
52
|
+
record3.password = 'secure_pass123'
|
53
|
+
record3.url = 'https://myapp.com'
|
54
|
+
|
55
|
+
# Method 4: Mixing approaches
|
56
|
+
record4 = KeeperSecretsManager::Dto::KeeperRecord.new(
|
57
|
+
title: 'Payment Info',
|
58
|
+
type: 'bankCard'
|
59
|
+
)
|
60
|
+
|
61
|
+
# Use helper for complex field
|
62
|
+
card_field = KeeperSecretsManager::FieldTypes::Helpers.payment_card(
|
63
|
+
number: '4111111111111111',
|
64
|
+
expiration_date: '12/25',
|
65
|
+
security_code: '123',
|
66
|
+
cardholder_name: 'John Doe'
|
67
|
+
)
|
68
|
+
|
69
|
+
# Add to record
|
70
|
+
record4.fields << card_field.to_h
|
71
|
+
|
72
|
+
# Add custom field with any structure
|
73
|
+
record4.set_field('customData', {
|
74
|
+
'someKey' => 'someValue',
|
75
|
+
'nested' => {
|
76
|
+
'data' => 'structure'
|
77
|
+
}
|
78
|
+
}, 'My Custom Data', true) # true = custom field
|
79
|
+
|
80
|
+
# Access field values
|
81
|
+
puts "Login: #{record1.get_field_value_single('login')}"
|
82
|
+
puts "Password field: #{record1.get_field('password')}"
|
83
|
+
puts "All custom fields: #{record1.custom}"
|
84
|
+
|
85
|
+
# Dynamic accessor
|
86
|
+
puts "URL from record3: #{record3.url}"
|
87
|
+
|
88
|
+
# Access complex field values
|
89
|
+
host_field = record1.get_field_value_single('host')
|
90
|
+
puts "Host: #{host_field['hostName']}:#{host_field['port']}" if host_field
|
91
|
+
|
92
|
+
# Convert to hash for API
|
93
|
+
puts "\nRecord as hash for API:"
|
94
|
+
puts record1.to_h.to_json
|
@@ -0,0 +1,109 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require_relative '../lib/keeper_secrets_manager'
|
4
|
+
|
5
|
+
# This example demonstrates folder hierarchy functionality in the Ruby SDK
|
6
|
+
|
7
|
+
begin
|
8
|
+
# Initialize client (assumes KSM_CONFIG environment variable is set)
|
9
|
+
client = KeeperSecretsManager.new
|
10
|
+
|
11
|
+
puts "=== Folder Hierarchy Demo ==="
|
12
|
+
puts ""
|
13
|
+
|
14
|
+
# Get all folders
|
15
|
+
folders = client.get_folders
|
16
|
+
puts "Found #{folders.length} folders"
|
17
|
+
puts ""
|
18
|
+
|
19
|
+
# Get folder manager for advanced operations
|
20
|
+
fm = client.folder_manager
|
21
|
+
|
22
|
+
# Build and display folder tree
|
23
|
+
puts "Folder Tree Structure:"
|
24
|
+
puts "-" * 40
|
25
|
+
fm.print_tree
|
26
|
+
puts ""
|
27
|
+
|
28
|
+
# Demonstrate path retrieval
|
29
|
+
if folders.any?
|
30
|
+
first_folder = folders.first
|
31
|
+
path = client.get_folder_path(first_folder.uid)
|
32
|
+
puts "Path to '#{first_folder.name}': #{path}"
|
33
|
+
|
34
|
+
# Show ancestors
|
35
|
+
ancestors = fm.get_ancestors(first_folder.uid)
|
36
|
+
if ancestors.any?
|
37
|
+
puts "Ancestors of '#{first_folder.name}':"
|
38
|
+
ancestors.each { |a| puts " - #{a.name}" }
|
39
|
+
else
|
40
|
+
puts "No ancestors (root folder or orphan)"
|
41
|
+
end
|
42
|
+
|
43
|
+
# Show descendants
|
44
|
+
descendants = fm.get_descendants(first_folder.uid)
|
45
|
+
if descendants.any?
|
46
|
+
puts "Descendants of '#{first_folder.name}':"
|
47
|
+
descendants.each { |d| puts " - #{d.name}" }
|
48
|
+
else
|
49
|
+
puts "No descendants (leaf folder)"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
puts ""
|
54
|
+
|
55
|
+
# Find folder by name
|
56
|
+
puts "Searching for folders:"
|
57
|
+
puts "-" * 40
|
58
|
+
|
59
|
+
# Example: Find a folder named "Personal"
|
60
|
+
personal = client.find_folder_by_name("Personal")
|
61
|
+
if personal
|
62
|
+
puts "Found folder 'Personal' with UID: #{personal.uid}"
|
63
|
+
puts "Path: #{client.get_folder_path(personal.uid)}"
|
64
|
+
else
|
65
|
+
puts "No folder named 'Personal' found"
|
66
|
+
end
|
67
|
+
|
68
|
+
# Get all records organized by folder
|
69
|
+
puts ""
|
70
|
+
puts "Records by Folder:"
|
71
|
+
puts "-" * 40
|
72
|
+
|
73
|
+
response = client.get_secrets
|
74
|
+
|
75
|
+
# Group records by folder
|
76
|
+
records_by_folder = {}
|
77
|
+
root_records = []
|
78
|
+
|
79
|
+
response.records.each do |record|
|
80
|
+
if record.folder_uid && !record.folder_uid.empty?
|
81
|
+
records_by_folder[record.folder_uid] ||= []
|
82
|
+
records_by_folder[record.folder_uid] << record
|
83
|
+
else
|
84
|
+
root_records << record
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Display root records
|
89
|
+
if root_records.any?
|
90
|
+
puts "Root Records (no folder):"
|
91
|
+
root_records.each { |r| puts " - #{r.title}" }
|
92
|
+
puts ""
|
93
|
+
end
|
94
|
+
|
95
|
+
# Display records in each folder
|
96
|
+
folders.each do |folder|
|
97
|
+
folder_records = records_by_folder[folder.uid]
|
98
|
+
if folder_records && folder_records.any?
|
99
|
+
path = client.get_folder_path(folder.uid)
|
100
|
+
puts "Records in '#{path}':"
|
101
|
+
folder_records.each { |r| puts " - #{r.title}" }
|
102
|
+
puts ""
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
rescue => e
|
107
|
+
puts "Error: #{e.message}"
|
108
|
+
puts e.backtrace.first(5).join("\n")
|
109
|
+
end
|