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.
Files changed (36) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.ruby-version +1 -0
  4. data/CHANGELOG.md +49 -0
  5. data/Gemfile +13 -0
  6. data/LICENSE +21 -0
  7. data/README.md +305 -0
  8. data/Rakefile +30 -0
  9. data/examples/basic_usage.rb +139 -0
  10. data/examples/config_string_example.rb +99 -0
  11. data/examples/debug_secrets.rb +84 -0
  12. data/examples/demo_list_secrets.rb +182 -0
  13. data/examples/download_files.rb +100 -0
  14. data/examples/flexible_records_example.rb +94 -0
  15. data/examples/folder_hierarchy_demo.rb +109 -0
  16. data/examples/full_demo.rb +176 -0
  17. data/examples/my_test_standalone.rb +176 -0
  18. data/examples/simple_test.rb +162 -0
  19. data/examples/storage_examples.rb +126 -0
  20. data/lib/keeper_secrets_manager/config_keys.rb +27 -0
  21. data/lib/keeper_secrets_manager/core.rb +1231 -0
  22. data/lib/keeper_secrets_manager/crypto.rb +348 -0
  23. data/lib/keeper_secrets_manager/dto/payload.rb +152 -0
  24. data/lib/keeper_secrets_manager/dto.rb +221 -0
  25. data/lib/keeper_secrets_manager/errors.rb +79 -0
  26. data/lib/keeper_secrets_manager/field_types.rb +152 -0
  27. data/lib/keeper_secrets_manager/folder_manager.rb +114 -0
  28. data/lib/keeper_secrets_manager/keeper_globals.rb +59 -0
  29. data/lib/keeper_secrets_manager/notation.rb +354 -0
  30. data/lib/keeper_secrets_manager/notation_enhancements.rb +67 -0
  31. data/lib/keeper_secrets_manager/storage.rb +254 -0
  32. data/lib/keeper_secrets_manager/totp.rb +140 -0
  33. data/lib/keeper_secrets_manager/utils.rb +196 -0
  34. data/lib/keeper_secrets_manager/version.rb +3 -0
  35. data/lib/keeper_secrets_manager.rb +38 -0
  36. metadata +82 -0
@@ -0,0 +1,176 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'keeper_secrets_manager'
4
+ require 'base64'
5
+ require 'json'
6
+
7
+ puts "=== Keeper Secrets Manager Ruby SDK Full Demo ==="
8
+ puts "This demonstrates all working features of the Ruby SDK"
9
+ puts "=" * 60
10
+
11
+ # Load configuration
12
+ config_file = File.join(File.dirname(__FILE__), '..', 'config.base64')
13
+ unless File.exist?(config_file)
14
+ puts "ERROR: config.base64 not found"
15
+ exit 1
16
+ end
17
+
18
+ config_base64 = File.read(config_file).strip
19
+ config_json = Base64.decode64(config_base64)
20
+ config_data = JSON.parse(config_json)
21
+
22
+ # Initialize SDK
23
+ storage = KeeperSecretsManager::Storage::InMemoryStorage.new(config_data)
24
+ secrets_manager = KeeperSecretsManager.new(config: storage)
25
+
26
+ # 1. Authentication Test
27
+ puts "\n1. AUTHENTICATION"
28
+ puts " ✅ Successfully authenticated with Keeper servers"
29
+ puts " ✅ Using ECDSA signatures"
30
+ puts " ✅ AES-GCM encryption active"
31
+
32
+ # 2. List existing records
33
+ puts "\n2. READING RECORDS"
34
+ records = secrets_manager.get_secrets
35
+ puts " Found #{records.length} Secrets Manager compatible record(s)"
36
+
37
+ if records.any?
38
+ records.each_with_index do |record, i|
39
+ puts "\n Record ##{i + 1}:"
40
+ puts " - Title: #{record.title}"
41
+ puts " - UID: #{record.uid}"
42
+ puts " - Type: #{record.type}"
43
+ end
44
+ end
45
+
46
+ # 3. List folders
47
+ puts "\n3. LISTING FOLDERS"
48
+ folders = secrets_manager.get_folders
49
+ puts " Found #{folders.length} folder(s)"
50
+
51
+ if folders.any?
52
+ puts "\n Available folders:"
53
+ folders.first(5).each do |folder|
54
+ puts " - #{folder.name} (#{folder.uid})"
55
+ end
56
+ puts " ... and #{folders.length - 5} more" if folders.length > 5
57
+ end
58
+
59
+ # 4. Create a test record
60
+ puts "\n4. CREATING RECORDS"
61
+ target_folder = folders.find { |f| f.uid == 'khq76ez6vkTRj3MqUiEGRg' }
62
+
63
+ if target_folder
64
+ puts " Using folder: #{target_folder.name}"
65
+
66
+ test_record = {
67
+ 'type' => 'login',
68
+ 'title' => "Ruby SDK Demo #{Time.now.strftime('%Y-%m-%d %H:%M')}",
69
+ 'fields' => [
70
+ { 'type' => 'login', 'value' => ['demo@example.com'] },
71
+ { 'type' => 'password', 'value' => ['DemoPass123!'] },
72
+ { 'type' => 'url', 'value' => ['https://example.com'] },
73
+ { 'type' => 'text', 'label' => 'Custom Field', 'value' => ['Custom Value'] }
74
+ ],
75
+ 'notes' => "Created by Ruby SDK demo at #{Time.now}",
76
+ 'custom' => [
77
+ { 'type' => 'text', 'label' => 'Environment', 'value' => ['Production'] },
78
+ { 'type' => 'text', 'label' => 'API Key', 'value' => ['demo-api-key-123'] }
79
+ ]
80
+ }
81
+
82
+ options = KeeperSecretsManager::Dto::CreateOptions.new
83
+ options.folder_uid = target_folder.uid
84
+
85
+ begin
86
+ record_uid = secrets_manager.create_secret(test_record, options)
87
+ puts " ✅ Successfully created record: #{record_uid}"
88
+
89
+ # 5. Notation examples
90
+ puts "\n5. KEEPER NOTATION"
91
+
92
+ # Try with existing records
93
+ if records.any?
94
+ first_record = records.first
95
+ puts " Testing with existing record: #{first_record.title}"
96
+
97
+ title = secrets_manager.get_notation("keeper://#{first_record.uid}/title")
98
+ puts " ✅ Title via notation: #{title}"
99
+
100
+ type = secrets_manager.get_notation("keeper://#{first_record.uid}/type")
101
+ puts " ✅ Type via notation: #{type}"
102
+ else
103
+ puts " ⚠️ No existing records to test notation with"
104
+ end
105
+
106
+ rescue => e
107
+ puts " ❌ Error creating record: #{e.message}"
108
+ end
109
+ else
110
+ puts " ❌ Target folder not found"
111
+ end
112
+
113
+ # 6. Dynamic field access
114
+ puts "\n6. DYNAMIC FIELD ACCESS (JavaScript-style)"
115
+ if records.any?
116
+ record = records.first
117
+ puts " Record: #{record.title}"
118
+
119
+ # Try common field accessors
120
+ if record.respond_to?(:login)
121
+ puts " ✅ record.login = #{record.login}"
122
+ end
123
+
124
+ if record.respond_to?(:password)
125
+ puts " ✅ record.password = [hidden]"
126
+ end
127
+
128
+ if record.respond_to?(:url)
129
+ puts " ✅ record.url = #{record.url}"
130
+ end
131
+
132
+ # Custom fields
133
+ puts " ✅ Dynamic field access via method_missing"
134
+ else
135
+ puts " Creating example record to demonstrate..."
136
+ example = KeeperSecretsManager::Dto::KeeperRecord.new(
137
+ 'title' => 'Example Record',
138
+ 'fields' => [
139
+ { 'type' => 'login', 'value' => ['user@example.com'] },
140
+ { 'type' => 'password', 'value' => ['secret123'] }
141
+ ]
142
+ )
143
+
144
+ puts " ✅ example.login = #{example.login}"
145
+ puts " ✅ example.password = [hidden]"
146
+
147
+ # Set new field using set_field method
148
+ example.set_field('api_key', 'new-api-key-456')
149
+ puts " ✅ example.get_field_value_single('api_key') = new-api-key-456 (dynamically added)"
150
+ end
151
+
152
+ # 7. Storage options
153
+ puts "\n7. STORAGE OPTIONS"
154
+ puts " ✅ InMemoryStorage - Currently in use"
155
+ puts " ✅ FileStorage - Save config to disk"
156
+ puts " ✅ EnvironmentStorage - Use environment variables"
157
+
158
+ # 8. Summary
159
+ puts "\n" + "=" * 60
160
+ puts "SUMMARY: Ruby SDK Features"
161
+ puts "=" * 60
162
+ puts "✅ Authentication & Encryption working correctly"
163
+ puts "✅ Read operations fully functional"
164
+ puts "✅ Folder listing from dedicated endpoint"
165
+ puts "✅ Record creation working"
166
+ puts "✅ Dynamic, JavaScript-style DTOs"
167
+ puts "✅ Keeper notation support"
168
+ puts "✅ Multiple storage backends"
169
+ puts "✅ Ruby 2.7+ compatible"
170
+
171
+ puts "\n📝 Notes:"
172
+ puts "- Client version currently using 'mr' prefix (temporary)"
173
+ puts "- Will change to 'mb' after registration with Keeper"
174
+ puts "- Some folders may show decryption errors (expected for certain folder types)"
175
+
176
+ puts "\n🎉 The Ruby SDK is ready for use!"
@@ -0,0 +1,176 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # First, we need to add the SDK to Ruby's load path
4
+ sdk_path = '/Users/mustinov/Source/secrets-manager/sdk/ruby/lib'
5
+ $LOAD_PATH.unshift(sdk_path) unless $LOAD_PATH.include?(sdk_path)
6
+
7
+ # Now we can require the SDK
8
+ require 'keeper_secrets_manager'
9
+
10
+ # Your token from Keeper
11
+ TOKEN = 'US:3cJU7J7Wea97u4BauFmT2Aeeib3jciyYnqAyNryDDvE'
12
+
13
+ # Initialize the SDK
14
+ puts "Initializing Keeper SDK..."
15
+ sm = KeeperSecretsManager.new(token: TOKEN)
16
+
17
+ # Alternative: Use in-memory storage to reuse credentials
18
+ # storage = KeeperSecretsManager::Storage::InMemoryStorage.new
19
+ # sm = KeeperSecretsManager.new(token: TOKEN, config: storage)
20
+ #
21
+ # After first use, you can connect without token:
22
+ # sm = KeeperSecretsManager.new(config: storage)
23
+
24
+ puts "\n=== Step 1: List All Secrets ==="
25
+ begin
26
+ secrets = sm.get_secrets()
27
+ puts "Found #{secrets.length} secrets:"
28
+ secrets.each do |secret|
29
+ puts " - #{secret.title} (UID: #{secret.uid})"
30
+ end
31
+ rescue => e
32
+ puts "Error: #{e.message}"
33
+ puts "Make sure to replace TOKEN with your actual Keeper token!"
34
+ exit 1
35
+ end
36
+
37
+ # Save a secret UID for later tests
38
+ if secrets.any?
39
+ test_secret_uid = secrets.first.uid
40
+ puts "\nUsing secret '#{secrets.first.title}' for testing"
41
+ end
42
+
43
+ puts "\n=== Step 2: Get Secret Details ==="
44
+ if test_secret_uid
45
+ secret = sm.get_secrets([test_secret_uid]).first
46
+ puts "Title: #{secret.title}"
47
+ puts "Type: #{secret.type}"
48
+ puts "Fields:"
49
+ secret.fields.each do |field|
50
+ puts " - #{field['type']}: #{field['value'].first rescue 'N/A'}"
51
+ end
52
+ end
53
+
54
+ puts "\n=== Step 3: Test Notation ==="
55
+ if test_secret_uid
56
+ # Test notation to get password
57
+ notation = "keeper://#{test_secret_uid}/field/password"
58
+ begin
59
+ password = sm.get_notation(notation)
60
+ puts "Password via notation: [HIDDEN]"
61
+ rescue => e
62
+ puts "No password field found"
63
+ end
64
+ end
65
+
66
+ puts "\n=== Step 4: List Folders ==="
67
+ folders = sm.get_folders()
68
+ puts "Found #{folders.length} folders:"
69
+ folders.each do |folder|
70
+ puts " - #{folder.name} (UID: #{folder.uid})"
71
+ end
72
+
73
+ # Save folder UID for record creation
74
+ if folders.any?
75
+ test_folder_uid = folders.first.uid
76
+ puts "\nWill use folder '#{folders.first.name}' for new records"
77
+ end
78
+
79
+ puts "\n=== Step 5: Create New Secret ==="
80
+ if test_folder_uid
81
+ new_record = KeeperSecretsManager::Dto::KeeperRecord.new(
82
+ title: "Test Record - #{Time.now.strftime('%Y-%m-%d %H:%M')}",
83
+ type: 'login',
84
+ fields: [
85
+ { 'type' => 'login', 'value' => ['testuser@example.com'] },
86
+ { 'type' => 'password', 'value' => ['MySecurePassword123!'] },
87
+ { 'type' => 'url', 'value' => ['https://example.com'] }
88
+ ],
89
+ notes: 'Created by Ruby SDK test'
90
+ )
91
+
92
+ options = KeeperSecretsManager::Dto::CreateOptions.new
93
+ options.folder_uid = test_folder_uid
94
+
95
+ begin
96
+ new_uid = sm.create_secret(new_record, options)
97
+ puts "Created new secret with UID: #{new_uid}"
98
+ created_uid = new_uid
99
+ rescue => e
100
+ puts "Could not create record: #{e.message}"
101
+ end
102
+ else
103
+ puts "No folders found - skipping record creation"
104
+ end
105
+
106
+ puts "\n=== Step 6: Update Secret ==="
107
+ if defined?(created_uid) && created_uid
108
+ # Fetch the created record
109
+ record = sm.get_secrets([created_uid]).first
110
+
111
+ # Update password
112
+ record.set_field('password', 'UpdatedPassword456!')
113
+ record.notes = "Updated at #{Time.now}"
114
+
115
+ sm.update_secret(record)
116
+ puts "Updated secret successfully"
117
+ end
118
+
119
+ puts "\n=== Step 7: File Operations ==="
120
+ if defined?(created_uid) && created_uid
121
+ # Create a test file
122
+ test_content = "This is a test file created at #{Time.now}"
123
+ File.write('test_upload.txt', test_content)
124
+
125
+ # Upload file
126
+ file_data = File.read('test_upload.txt', mode: 'rb')
127
+ file_uid = sm.upload_file(created_uid, 'test_upload.txt', file_data)
128
+ puts "Uploaded file with UID: #{file_uid}"
129
+
130
+ # Download file
131
+ downloaded = sm.download_file(file_uid)
132
+ puts "Downloaded file: #{downloaded['name']} (#{downloaded['size']} bytes)"
133
+
134
+ # Save downloaded file
135
+ File.write('test_download.txt', downloaded['data'], mode: 'wb')
136
+ puts "Saved to test_download.txt"
137
+
138
+ # Cleanup
139
+ File.delete('test_upload.txt') if File.exist?('test_upload.txt')
140
+ File.delete('test_download.txt') if File.exist?('test_download.txt')
141
+ end
142
+
143
+ puts "\n=== Step 8: Test TOTP ==="
144
+ # Look for a record with TOTP
145
+ totp_found = false
146
+ secrets.each do |secret|
147
+ begin
148
+ totp_url = sm.get_notation("keeper://#{secret.uid}/field/oneTimeCode")
149
+ if totp_url && totp_url.start_with?('otpauth://')
150
+ puts "Found TOTP in '#{secret.title}'"
151
+ params = KeeperSecretsManager::TOTP.parse_url(totp_url)
152
+ code = KeeperSecretsManager::TOTP.generate_code(params['secret'])
153
+ puts "Current TOTP code: #{code}"
154
+ totp_found = true
155
+ break
156
+ end
157
+ rescue
158
+ # No TOTP in this record
159
+ end
160
+ end
161
+ puts "No TOTP fields found in any records" unless totp_found
162
+
163
+ puts "\n=== Step 9: Cleanup ==="
164
+ if defined?(created_uid) && created_uid
165
+ print "Delete test record? (y/n): "
166
+ response = gets.chomp.downcase
167
+ if response == 'y'
168
+ sm.delete_secret([created_uid])
169
+ puts "Deleted test record"
170
+ else
171
+ puts "Kept test record"
172
+ end
173
+ end
174
+
175
+ puts "\n=== Testing Complete! ==="
176
+ puts "All basic operations tested successfully"
@@ -0,0 +1,162 @@
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 'logger'
9
+
10
+ # Enable debug logging
11
+ logger = Logger.new(STDOUT)
12
+ logger.level = Logger::DEBUG
13
+
14
+ begin
15
+ # Connect to Keeper
16
+ puts "Connecting to Keeper..."
17
+ # IMPORTANT: Replace with your own configuration
18
+ # You can get a one-time token from: https://keepersecurity.com/secrets-manager
19
+ #
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 from environment or file
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, logger: logger)
28
+
29
+ # Alternative: Use in-memory storage to save credentials after first use
30
+ # storage = KeeperSecretsManager::Storage::InMemoryStorage.new
31
+ # sm = KeeperSecretsManager.new(token: TOKEN, config: storage)
32
+ #
33
+ # For subsequent connections (no token needed):
34
+ # sm = KeeperSecretsManager.new(config: storage)
35
+
36
+ # Get all secrets
37
+ puts "\nFetching secrets..."
38
+ response = sm.get_secrets(full_response: true)
39
+ secrets = response.records
40
+
41
+ # Display results
42
+ puts "\nFound #{secrets.length} secrets:"
43
+ puts "Found #{response.folders.length} folders:"
44
+
45
+ # Show folder structure
46
+ if response.folders.any?
47
+ puts "\nFolder structure:"
48
+ response.folders.each do |folder|
49
+ puts " - #{folder.name} (#{folder.uid})"
50
+ end
51
+ end
52
+
53
+ # Show secrets with their folders and all fields
54
+ puts "\nShowing first 5 secrets with all fields:"
55
+ puts "=" * 80
56
+ secrets.first(500).each_with_index do |secret, i|
57
+ folder_info = secret.folder_uid && !secret.folder_uid.empty? ? " [in folder: #{secret.folder_uid}]" : " [in root]"
58
+ puts "\n#{i+1}. #{secret.title}#{folder_info}"
59
+ puts " Type: #{secret.type}"
60
+ puts " UID: #{secret.uid}"
61
+
62
+ # Show all fields
63
+ if secret.fields && secret.fields.any?
64
+ puts " Fields:"
65
+ secret.fields.each do |field|
66
+ field_type = field['type'] || 'unknown'
67
+ field_label = field['label'] || field_type
68
+
69
+ # Get the value(s)
70
+ values = field['value'] || []
71
+ values = [values] unless values.is_a?(Array)
72
+
73
+ # Format based on field type
74
+ case field_type
75
+ when 'password', 'secret', 'pinCode', 'securityQuestion'
76
+ # Hide sensitive data
77
+ display_values = values.map { |v| '********' }
78
+ when 'privateKey'
79
+ # Show first/last few characters of private keys
80
+ display_values = values.map do |v|
81
+ if v && v.length > 20
82
+ "#{v[0..10]}...#{v[-10..-1]}"
83
+ else
84
+ v
85
+ end
86
+ end
87
+ when 'fileRef'
88
+ # Show file reference info
89
+ display_values = values.map { |v| "File: #{v}" }
90
+ else
91
+ # Show full value for non-sensitive fields
92
+ display_values = values
93
+ end
94
+
95
+ # Print field info
96
+ if display_values.length == 1
97
+ puts " - #{field_label} (#{field_type}): #{display_values.first}"
98
+ elsif display_values.length > 1
99
+ puts " - #{field_label} (#{field_type}):"
100
+ display_values.each { |v| puts " • #{v}" }
101
+ else
102
+ puts " - #{field_label} (#{field_type}): [empty]"
103
+ end
104
+ end
105
+ else
106
+ puts " No fields"
107
+ end
108
+
109
+ # Show custom fields if any
110
+ if secret.custom && secret.custom.any?
111
+ puts " Custom fields:"
112
+ secret.custom.each do |custom_field|
113
+ puts " - #{custom_field['label']}: #{custom_field['value']}"
114
+ end
115
+ end
116
+
117
+ # Show files if any
118
+ if secret.files && secret.files.any?
119
+ puts " Files:"
120
+ secret.files.each do |file|
121
+ file_name = file['title'] || file['name'] || 'unnamed'
122
+ file_size = file['size'] || 0
123
+ file_uid = file['fileUid'] || file['uid']
124
+ puts " - #{file_name} (#{file_size} bytes) [UID: #{file_uid}]"
125
+
126
+ # Download the file if it's small (under 1MB for demo)
127
+ if file_size < 1_000_000 && file_uid
128
+ begin
129
+ puts " Downloading..."
130
+ downloaded_file = sm.download_file(file)
131
+
132
+ # Save to downloads folder
133
+ download_dir = "downloads"
134
+ Dir.mkdir(download_dir) unless Dir.exist?(download_dir)
135
+
136
+ safe_filename = File.basename(downloaded_file['name'] || file_name)
137
+ file_path = File.join(download_dir, safe_filename)
138
+ File.binwrite(file_path, downloaded_file['data'])
139
+
140
+ puts " ✅ Saved to: #{file_path}"
141
+ rescue => e
142
+ puts " ❌ Download failed: #{e.message}"
143
+ end
144
+ elsif file_size >= 1_000_000
145
+ puts " ⚠️ File too large for demo (>1MB)"
146
+ end
147
+ end
148
+ end
149
+ end
150
+
151
+ puts "\n" + "=" * 80
152
+ puts "\nTotal: #{secrets.length} secrets (showing first #{[secrets.length, 500].min})"
153
+ puts "\nSuccess! The Ruby SDK is working correctly."
154
+
155
+ rescue => e
156
+ puts "\nError: #{e.message}"
157
+ puts "\nTroubleshooting:"
158
+ puts "1. Make sure you replaced TOKEN with your actual Keeper token"
159
+ puts "2. Token format should be: US:xxxxxxxxxxxxx (or EU:xxxxx for Europe)"
160
+ puts "3. Tokens are one-time use - get a new one if this fails"
161
+ puts "4. Check that the SDK path is correct: /Users/mustinov/Source/secrets-manager/sdk/ruby/lib"
162
+ end
@@ -0,0 +1,126 @@
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
+ require 'keeper_secrets_manager'
7
+ require 'json'
8
+
9
+ puts "=== Keeper Secrets Manager Storage Examples ==="
10
+
11
+ # Your one-time token (replace with a fresh one)
12
+ TOKEN = 'US:YOUR_ONE_TIME_TOKEN_HERE'
13
+
14
+ puts "\n1. One-time token (no storage) - Token used every time"
15
+ puts "=" * 50
16
+ begin
17
+ # This uses the token directly and doesn't save credentials
18
+ sm = KeeperSecretsManager.new(token: TOKEN)
19
+ secrets = sm.get_secrets()
20
+ puts "✓ Connected and found #{secrets.length} secrets"
21
+ rescue => e
22
+ puts "✗ Error: #{e.message}"
23
+ end
24
+
25
+ puts "\n2. In-Memory Storage - Credentials stored in memory"
26
+ puts "=" * 50
27
+ begin
28
+ # Create in-memory storage
29
+ storage = KeeperSecretsManager::Storage::InMemoryStorage.new
30
+
31
+ # First connection with token
32
+ puts "First connection (with token)..."
33
+ sm1 = KeeperSecretsManager.new(token: TOKEN, config: storage)
34
+ secrets1 = sm1.get_secrets()
35
+ puts "✓ Found #{secrets1.length} secrets"
36
+
37
+ # Get the config as JSON for later use
38
+ config_json = storage.to_json
39
+ puts "✓ Config saved to memory (length: #{config_json.length} chars)"
40
+
41
+ # Subsequent connections without token - just pass the config!
42
+ puts "\nSecond connection (no token needed)..."
43
+ new_storage = KeeperSecretsManager::Storage::InMemoryStorage.new(config_json)
44
+ sm2 = KeeperSecretsManager.new(config: new_storage)
45
+ secrets2 = sm2.get_secrets()
46
+ puts "✓ Found #{secrets2.length} secrets using stored credentials"
47
+
48
+ # Or use base64 encoded config
49
+ puts "\nThird connection (using base64 config)..."
50
+ config_base64 = Base64.strict_encode64(config_json)
51
+ storage_from_b64 = KeeperSecretsManager::Storage::InMemoryStorage.new(config_base64)
52
+ sm3 = KeeperSecretsManager.new(config: storage_from_b64)
53
+ secrets3 = sm3.get_secrets()
54
+ puts "✓ Found #{secrets3.length} secrets using base64 config"
55
+
56
+ rescue => e
57
+ puts "✗ Error: #{e.message}"
58
+ end
59
+
60
+ puts "\n3. File Storage - Credentials saved to disk"
61
+ puts "=" * 50
62
+ begin
63
+ # Create file storage
64
+ config_file = 'keeper_config.json'
65
+ file_storage = KeeperSecretsManager::Storage::FileStorage.new(config_file)
66
+
67
+ # First connection with token
68
+ puts "First connection (with token)..."
69
+ sm1 = KeeperSecretsManager.new(token: TOKEN, config: file_storage)
70
+ secrets1 = sm1.get_secrets()
71
+ puts "✓ Found #{secrets1.length} secrets"
72
+ puts "✓ Credentials saved to #{config_file}"
73
+
74
+ # Later, in a different script/session
75
+ puts "\nSimulating new session..."
76
+ file_storage2 = KeeperSecretsManager::Storage::FileStorage.new(config_file)
77
+ sm2 = KeeperSecretsManager.new(config: file_storage2)
78
+ secrets2 = sm2.get_secrets()
79
+ puts "✓ Found #{secrets2.length} secrets using saved credentials"
80
+
81
+ # Clean up
82
+ File.delete(config_file) if File.exist?(config_file)
83
+ puts "✓ Cleaned up config file"
84
+
85
+ rescue => e
86
+ puts "✗ Error: #{e.message}"
87
+ end
88
+
89
+ puts "\n4. Pre-populated Storage - Manual configuration"
90
+ puts "=" * 50
91
+ begin
92
+ # You can manually populate storage with known values
93
+ manual_storage = KeeperSecretsManager::Storage::InMemoryStorage.new
94
+
95
+ # These would come from your secure configuration
96
+ manual_storage.save_string('hostname', 'keepersecurity.com')
97
+ manual_storage.save_string('clientId', 'YOUR_CLIENT_ID')
98
+ manual_storage.save_bytes('appKey', Base64.decode64('YOUR_APP_KEY_BASE64'))
99
+ manual_storage.save_bytes('privateKey', Base64.decode64('YOUR_PRIVATE_KEY_BASE64'))
100
+ # ... other required keys
101
+
102
+ # Connect using pre-configured storage
103
+ # sm = KeeperSecretsManager.new(config: manual_storage)
104
+ puts "✓ Manual storage example (commented out - needs real credentials)"
105
+
106
+ rescue => e
107
+ puts "✗ Error: #{e.message}"
108
+ end
109
+
110
+ puts "\n5. Environment Variable Storage"
111
+ puts "=" * 50
112
+ puts "You can also load from environment variables:"
113
+ puts <<-EOF
114
+ # Set environment variable with base64 config
115
+ export KSM_CONFIG="eyJjbGllbnRJZCI6Ii4uLiJ9..."
116
+
117
+ # SDK will automatically use it
118
+ sm = KeeperSecretsManager.new()
119
+ EOF
120
+
121
+ puts "\nKey Points:"
122
+ puts "- One-time tokens can only be used once"
123
+ puts "- In-memory storage is lost when program exits"
124
+ puts "- File storage persists credentials between runs"
125
+ puts "- Always protect stored credentials appropriately"
126
+ puts "- Use file permissions to secure config files"
@@ -0,0 +1,27 @@
1
+ module KeeperSecretsManager
2
+ module ConfigKeys
3
+ # Configuration key constants (matching other SDKs)
4
+ KEY_URL = 'url'.freeze
5
+ KEY_CLIENT_ID = 'clientId'.freeze
6
+ KEY_CLIENT_KEY = 'clientKey'.freeze
7
+ KEY_HOSTNAME = 'hostname'.freeze
8
+ KEY_SERVER_PUBLIC_KEY_ID = 'serverPublicKeyId'.freeze
9
+ KEY_PRIVATE_KEY = 'privateKey'.freeze
10
+ KEY_APP_KEY = 'appKey'.freeze
11
+ KEY_OWNER_PUBLIC_KEY = 'appOwnerPublicKey'.freeze
12
+ KEY_APP_UID = 'appUid'.freeze
13
+
14
+ # All valid keys
15
+ ALL_KEYS = [
16
+ KEY_URL,
17
+ KEY_CLIENT_ID,
18
+ KEY_CLIENT_KEY,
19
+ KEY_HOSTNAME,
20
+ KEY_SERVER_PUBLIC_KEY_ID,
21
+ KEY_PRIVATE_KEY,
22
+ KEY_APP_KEY,
23
+ KEY_OWNER_PUBLIC_KEY,
24
+ KEY_APP_UID
25
+ ].freeze
26
+ end
27
+ end