Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
fa67f56
KSM-685: Fix Ruby SDK to send subFolderUid parameter when creating re…
stas-schaller Nov 7, 2025
09f1a6c
KSM-686: Implement disaster recovery caching with CachingPostFunction…
stas-schaller Nov 7, 2025
2343e1c
Updated version and changelog
stas-schaller Nov 7, 2025
6738345
Fix Ruby SDK SBOM generation to scan built gem instead of directory w…
stas-schaller Nov 11, 2025
f317577
KSM-685: added missing inner_folder_uid field in DTO for record
stas-schaller Nov 11, 2025
ac35129
Added pry console script for development tools
stas-schaller Nov 11, 2025
d8620d1
KSM-687: Add missing DTO fields and methods for SDK parity with other…
stas-schaller Nov 11, 2025
83135e8
Expand Ruby SDK test coverage with TOTP, file operations, and folder …
stas-schaller Nov 11, 2025
e664a33
Fixed badly anchored regular expression in test
stas-schaller Nov 11, 2025
a9c2c83
Revert "Fix Ruby SDK SBOM generation to scan built gem instead of dir…
stas-schaller Nov 12, 2025
07f21ab
Add Ruby SDK to SDKs table in root README
stas-schaller Nov 12, 2025
1946981
Fix Ruby gem name in README (use underscores, not hyphens)
stas-schaller Nov 12, 2025
7d3034e
Add from_config() convenience method for base64 config initialization
stas-schaller Nov 13, 2025
1c7c5c0
Fix example files to use correct SDK APIs
stas-schaller Nov 13, 2025
df884e5
Update CHANGELOG for v17.1.1 release
stas-schaller Nov 13, 2025
56dd7cf
Update CHANGELOG.md for Keep a Changelog 1.1.0 compliance
stas-schaller Nov 13, 2025
c650c79
Simplify README to match other SDK patterns (docs.keeper.io is source…
stas-schaller Nov 13, 2025
8148723
Bump version to 17.2.0 (minor release for new API methods and features)
stas-schaller Nov 14, 2025
87cd66e
Add PAM linked records example and update all examples with new featu…
stas-schaller Nov 14, 2025
ad1a983
KSM-687: Add complete_transaction method for PAM rotation workflows
stas-schaller Nov 17, 2025
4c495a0
KSM-692: Add HTTP proxy support with environment variable fallback
stas-schaller Nov 17, 2025
2a610ae
KSM-694: Add file upload and notation convenience methods
stas-schaller Nov 17, 2025
3b19607
Add integration tests for thumbnail download workflow
stas-schaller Nov 17, 2025
827dda0
Add integration tests for UpdateOptions.links_to_remove
stas-schaller Nov 17, 2025
2367197
Add unit tests for QueryOptions filtering
stas-schaller Nov 17, 2025
be55ec6
Add integration tests for PAM linked records GraphSync
stas-schaller Nov 17, 2025
aeceb03
Add disaster recovery caching integration tests
stas-schaller Nov 17, 2025
c60544b
Update CHANGELOG for 17.2.0 release with new features
stas-schaller Nov 17, 2025
f75ae11
Fix file permissions for Ruby SDK config files
stas-schaller Nov 18, 2025
69e8781
Add comprehensive unit tests for errors, field_types, and utils modules
stas-schaller Nov 20, 2025
8a8d8ef
Add comprehensive unit tests for cache and TOTP modules
stas-schaller Nov 20, 2025
f69e06c
Add unit tests for core SecretsManager initialization and token proce…
stas-schaller Nov 20, 2025
1191878
Fix core_spec token test with valid base64-encoded mock data
stas-schaller Nov 20, 2025
8a35a58
Fix mock token key length in core_spec.rb
stas-schaller Nov 20, 2025
0d0883e
Fix AES-GCM encrypted data format in core_spec.rb
stas-schaller Nov 20, 2025
3af976f
Mock bind_one_time_token directly instead of encrypted HTTP responses
stas-schaller Nov 20, 2025
826812e
KSM-743: add transmission public key #18 for Gov Cloud Dev support
stas-schaller Jan 8, 2026
33d11e8
docs: update changelog and README with KSM-743 (Gov Cloud Dev key #18)
stas-schaller Jan 8, 2026
3087a10
KSM-734: fix notation lookup with record shortcuts
stas-schaller Dec 18, 2025
c572cea
docs: add KSM-734 fix to changelog and README
stas-schaller Jan 9, 2026
e9fd42b
chore: remove PAM features and comprehensive tests for v17.3.0 release
stas-schaller Jan 13, 2026
fa60344
chore: bump version to 17.3.0 and update CHANGELOG for PAM features
stas-schaller Jan 13, 2026
1d68b12
Revert "chore: remove PAM features and comprehensive tests for v17.3.…
stas-schaller Apr 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/test.ruby.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,12 @@ jobs:
run: |
bundle exec rubocop || true # Don't fail on linting for now

- name: Run unit tests
- name: Run RSpec tests (unit + integration)
run: |
bundle exec rspec spec/

- name: Run integration tests (mock mode)
- name: Run offline mock test
env:
KEEPER_MOCK_MODE: 'true'
run: |
ruby -I lib test/integration/test_offline_mock.rb || true # Don't fail if file doesn't exist yet
ruby -I lib test/integration/test_offline_mock.rb
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ Common use cases for Secrets Manager include:
**.NET** | [Docs](https://docs.keeper.io/secrets-manager/secrets-manager/developer-sdk-library/.net-sdk) <br /> [Source](https://github.com/Keeper-Security/secrets-manager/tree/master/sdk/dotNet) | ![.NET](https://github.com/Keeper-Security/secrets-manager/actions/workflows/test.dotnet.yml/badge.svg) | [![Nuget](https://img.shields.io/nuget/v/Keeper.SecretsManager?style=for-the-badge&logo=nuget&logoColor=white)](https://www.nuget.org/packages/Keeper.SecretsManager) |
**Go** | [Docs](https://docs.keeper.io/secrets-manager/secrets-manager/developer-sdk-library/golang-sdk) <br /> [Source](https://github.com/Keeper-Security/secrets-manager-go) | ![GoLang](https://github.com/keeper-security/secrets-manager-go/actions/workflows/test.go.yml/badge.svg) | [![Go](https://img.shields.io/github/v/tag/Keeper-Security/secrets-manager-go?label=Go&logo=go&logoColor=white&style=for-the-badge)](https://github.com/Keeper-Security/secrets-manager-go) |
**Rust** | [Docs](https://docs.keeper.io/secrets-manager/secrets-manager/developer-sdk-library/rust-sdk) <br /> [Source](https://github.com/Keeper-Security/secrets-manager/tree/master/sdk/rust) | ![Rust](https://github.com/Keeper-Security/secrets-manager/actions/workflows/test.rust.yml/badge.svg?branch=master) | [![Crates.io](https://img.shields.io/crates/v/keeper-secrets-manager-core?style=for-the-badge&logo=rust&logoColor=white)](https://crates.io/crates/keeper-secrets-manager-core) |
**Ruby** | [Docs](https://docs.keeper.io/secrets-manager/secrets-manager/developer-sdk-library/ruby-sdk) <br /> [Source](https://github.com/Keeper-Security/secrets-manager/tree/master/sdk/ruby) | ![Ruby](https://github.com/Keeper-Security/secrets-manager/actions/workflows/test.ruby.yml/badge.svg) | [![RubyGems](https://img.shields.io/gem/v/keeper_secrets_manager?style=for-the-badge&logo=rubygems&logoColor=white)](https://rubygems.org/gems/keeper_secrets_manager) |

More information about Keeper Secrets Manager, SDKs, tools, and integrations can be found in our [official documentation
portal](https://docs.keeper.io/secrets-manager/secrets-manager/overview)
Expand Down
23 changes: 14 additions & 9 deletions examples/ruby/01_quick_start.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,24 @@
puts 'Make sure to set KSM_TOKEN environment variable or replace with your token'
end

# Method 2: Using base64 configuration (for repeated use)
# After first connection, save your config for reuse
# Method 2: Using saved configuration file (recommended for repeated use)
# After first connection with token, config is saved to keeper_config.json
begin
config_base64 = ENV['KSM_CONFIG'] || 'YOUR_BASE64_CONFIG_STRING'
# Initialize from saved configuration file
secrets_manager = KeeperSecretsManager.from_file('keeper_config.json')

# Initialize with saved configuration
secrets_manager = KeeperSecretsManager.from_config(config_base64)
# Get all secrets
secrets = secrets_manager.get_secrets
puts "\nRetrieved #{secrets.length} secrets from saved config"

# Get specific secret by UID
secret = secrets_manager.get_secret_by_uid('RECORD_UID')
puts "\nSecret details:"
puts " Title: #{secret.title}"
puts " Login: #{secret.fields['login']}"
if secrets.any?
secret = secrets.first
puts "\nSecret details:"
puts " Title: #{secret.title}"
puts " Login: #{secret.login}" if secret.login
end
rescue StandardError => e
puts "Error: #{e.message}"
puts 'Make sure keeper_config.json exists (run with token first)'
end
11 changes: 6 additions & 5 deletions examples/ruby/02_authentication.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,16 +45,17 @@
puts "✗ Error: #{e.message}"
end

# Method 3: Using configuration file
puts "\n3. Using Configuration File:"
# Method 3: Using configuration file (RECOMMENDED)
puts "\n3. Using Configuration File (Recommended):"
begin
# Save configuration to a file
config_file = 'keeper-config.json'
# This is the recommended approach for most applications
config_file = 'keeper_config.json'

# Initialize with file storage
# Initialize from file storage
sm = KeeperSecretsManager.from_file(config_file)

puts "✓ Connected using config file: #{config_file}"
puts " (This is the recommended method after initial token binding)"
rescue StandardError => e
puts "✗ Error: #{e.message}"
end
Expand Down
42 changes: 38 additions & 4 deletions examples/ruby/03_retrieve_secrets.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@

require 'keeper_secrets_manager'

# Initialize (use your preferred method)
config = ENV['KSM_CONFIG'] || 'YOUR_BASE64_CONFIG'
secrets_manager = KeeperSecretsManager.from_config(config)
# Initialize from saved configuration file
secrets_manager = KeeperSecretsManager.from_file('keeper_config.json')

puts "=== Retrieving Secrets ==="

Expand Down Expand Up @@ -81,4 +80,39 @@
end
rescue => e
puts " Notation error: #{e.message}"
end
end

# 7. New DTO Fields (v17.2.0)
puts "\n7. New DTO Fields:"
puts " Access new metadata fields on records"

begin
query_options = KeeperSecretsManager::Dto::QueryOptions.new(request_links: true)
records_with_metadata = secrets_manager.get_secrets([], query_options)

records_with_metadata.first(3).each do |record|
puts "\n #{record.title}"
puts " Editable: #{record.is_editable ? 'Yes' : 'No'}"
puts " Folder UID: #{record.inner_folder_uid}" if record.inner_folder_uid
puts " Has links: #{record.links && record.links.any? ? 'Yes' : 'No'}"

if record.files && record.files.any?
file = record.files.first
puts " File metadata:"
puts " Last modified: #{Time.at(file['lastModified'])}" if file['lastModified']
puts " Has thumbnail: #{file['thumbnailUrl'] ? 'Yes' : 'No'}"
end
end
rescue => e
puts " Error: #{e.message}"
end

# Tips
puts "\n=== Tips ==="
puts '- Use get_secrets() without parameters to retrieve all secrets'
puts '- Use get_secrets([uid]) to retrieve specific secrets by UID'
puts '- Use get_secret_by_title() for quick lookups by name'
puts '- Use notation for quick field access'
puts '- Dynamic field access (record.password) is convenient for standard fields'
puts '- Enable request_links: true to retrieve PAM linked credentials'
puts '- Check is_editable before attempting to modify records'
59 changes: 56 additions & 3 deletions examples/ruby/04_create_update_delete.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@

require 'keeper_secrets_manager'

# Initialize
config = ENV['KSM_CONFIG'] || 'YOUR_BASE64_CONFIG'
secrets_manager = KeeperSecretsManager.from_config(config)
# Initialize from saved configuration file
secrets_manager = KeeperSecretsManager.from_file('keeper_config.json')

puts '=== CRUD Operations Example ==='

Expand Down Expand Up @@ -73,6 +72,60 @@
puts " New URL: #{updated.url}"
puts " Notes: #{updated.notes}"

# 3.5. Advanced Update - Password Rotation with Transaction Type
puts "\n3.5. Password rotation with transaction type..."
begin
# Get a fresh copy of the record
secret = secrets_manager.get_secrets([record_uid]).first

# Generate new password
new_password = KeeperSecretsManager::Utils.generate_password(length: 32)
secret.password = new_password

# Update with rotation transaction type
update_options = KeeperSecretsManager::Dto::UpdateOptions.new(
transaction_type: 'rotation'
)

secrets_manager.update_secret_with_options(secret, update_options)
puts '✓ Password rotated with transaction tracking'
puts " New password: #{new_password[0..5]}..." # Show first 6 chars only

rescue StandardError => e
puts "✗ Error: #{e.message}"
end

# 3.6. Advanced Update - Remove File Links
puts "\n3.6. Removing file attachments (if any)..."
begin
# Refresh the record
secret = secrets_manager.get_secrets([record_uid]).first

if secret.files && secret.files.any?
# Find files to remove (e.g., files starting with "old_")
file_uids_to_remove = secret.files
.select { |f| f['name'] =~ /^old_/ }
.map { |f| f['fileUid'] }

if file_uids_to_remove.any?
update_options = KeeperSecretsManager::Dto::UpdateOptions.new(
transaction_type: 'general',
links_to_remove: file_uids_to_remove
)

secrets_manager.update_secret_with_options(secret, update_options)
puts "✓ Removed #{file_uids_to_remove.length} file link(s)"
else
puts " (No old files to remove)"
end
else
puts " (No files attached to record)"
end

rescue StandardError => e
puts "✗ Error: #{e.message}"
end

# 4. DELETE - Remove the secret
puts "\n4. Deleting the secret..."
puts 'Press Enter to delete the test record...'
Expand Down
5 changes: 2 additions & 3 deletions examples/ruby/05_field_types.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@

require 'keeper_secrets_manager'

# Initialize
config = ENV['KSM_CONFIG'] || 'YOUR_BASE64_CONFIG'
secrets_manager = KeeperSecretsManager.from_config(config)
# Initialize from saved configuration file
secrets_manager = KeeperSecretsManager.from_file('keeper_config.json')

puts '=== Field Types Example ==='

Expand Down
131 changes: 84 additions & 47 deletions examples/ruby/06_files.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@
require 'keeper_secrets_manager'
require 'tempfile'

# Initialize
config = ENV['KSM_CONFIG'] || 'YOUR_BASE64_CONFIG'
secrets_manager = KeeperSecretsManager.from_config(config)
# Initialize from saved configuration file
secrets_manager = KeeperSecretsManager.from_file('keeper_config.json')

puts '=== File Operations Example ==='

Expand Down Expand Up @@ -38,68 +37,106 @@
filename = downloaded['name'] || 'downloaded_file'
File.write(filename, downloaded['data'])

puts " Downloaded: #{filename} (#{downloaded['size']} bytes)"
puts "[OK] Downloaded: #{filename} (#{downloaded['size']} bytes)"
puts " Type: #{downloaded['type']}"

# Clean up
File.delete(filename) if File.exist?(filename)

rescue StandardError => e
puts " Download failed: #{e.message}"
puts "[FAIL] Download failed: #{e.message}"
end
end

# 3. Upload a file
puts "\n3. Uploading a file..."
# 2.5. Download file thumbnails (new in v17.2.0)
if records_with_files.any?
puts "\n2.5. Downloading file thumbnails..."
record = records_with_files.first

record.files.each do |file|
# Check if thumbnail is available
if file['thumbnailUrl'] || file['thumbnail_url']
puts " Downloading thumbnail for: #{file['name']}"

begin
thumbnail = secrets_manager.download_thumbnail(file)

# Save thumbnail to disk
thumb_filename = "thumb_#{file['name']}"
File.write(thumb_filename, thumbnail['data'])

puts " [OK] Saved: #{thumb_filename} (#{thumbnail['size']} bytes, #{thumbnail['type']})"

# Clean up
File.delete(thumb_filename) if File.exist?(thumb_filename)
rescue StandardError => e
puts " [FAIL] Thumbnail download failed: #{e.message}"
end
else
puts " No thumbnail available for: #{file['name']}"
end
end
end

# 3. Upload a file (traditional method)
puts "\n3. Uploading a file (traditional method)..."
begin
# Create a test file
test_content = "This is a test file created at #{Time.now}\n"
test_content += "It contains some sample data for demonstration.\n"

# Create or find a record to attach the file to
record = secrets.first || begin
# Create a new record if none exist
# Note: You need to specify a folder_uid where the record will be created
# Get the first available folder
folders = secrets_manager.get_folders
folder_uid = folders.first&.uid
raise 'No folders available. Please create a folder in your vault first.' unless folder_uid

options = KeeperSecretsManager::Dto::CreateOptions.new(folder_uid: folder_uid)
uid = secrets_manager.create_secret({
type: 'login',
title: 'File Upload Test',
fields: [
{ type: 'login', value: ['test@example.com'] },
{ type: 'password', value: ['test123'] }
]
}, options)
secrets_manager.get_secret_by_uid(uid)
# Get a record to attach the file to
record = secrets.first
if record
puts "Uploading to record: #{record.title}"

# Upload the file (traditional method with file data)
file_uid = secrets_manager.upload_file(
record.uid,
test_content,
'test_document.txt',
'Test Document'
)

puts "[OK] Uploaded file with UID: #{file_uid}"
else
puts '[WARN] No records available for file upload test'
end
rescue StandardError => e
puts "[FAIL] Upload failed: #{e.message}"
puts ' Note: File upload requires write permissions'
end

puts "Uploading to record: #{record.title}"

# Upload the file
file_uid = secrets_manager.upload_file(
owner_record_uid: record.uid,
file_name: 'test_document.txt',
file_data: test_content,
mime_type: 'text/plain'
)

puts "✓ Uploaded file with UID: #{file_uid}"

# Verify by downloading
updated_record = secrets_manager.get_secret_by_uid(record.uid)
new_file = updated_record.files.find { |f| f['fileUid'] == file_uid }

if new_file
downloaded = secrets_manager.download_file(new_file)
puts "✓ Verified: #{downloaded['name']}"
# 3.5. Upload file from path (convenience method - NEW in v17.2.0)
puts "\n3.5. Uploading file from disk path (convenience method)..."
begin
# Create a temporary file on disk
temp_file = Tempfile.new(['keeper_test', '.txt'])
temp_file.write("Test file content from disk\nCreated: #{Time.now}")
temp_file.close

record = secrets.first
if record
puts "Uploading from path: #{temp_file.path}"

# Convenience method - reads file automatically
file_uid = secrets_manager.upload_file_from_path(
record.uid,
temp_file.path,
file_title: 'Uploaded from Disk'
)

puts "[OK] Uploaded file with UID: #{file_uid}"
puts " Filename auto-detected: #{File.basename(temp_file.path)}"
else
puts '[WARN] No records available for file upload test'
end

# Clean up
temp_file.unlink
rescue StandardError => e
puts " Upload failed: #{e.message}"
puts ' Note: File upload requires write permissions'
puts "[FAIL] Upload from path failed: #{e.message}"
temp_file&.unlink
end

# 4. Working with different file types
Expand Down
5 changes: 2 additions & 3 deletions examples/ruby/07_folders.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@

require 'keeper_secrets_manager'

# Initialize
config = ENV['KSM_CONFIG'] || 'YOUR_BASE64_CONFIG'
secrets_manager = KeeperSecretsManager.from_config(config)
# Initialize from saved configuration file
secrets_manager = KeeperSecretsManager.from_file('keeper_config.json')

puts '=== Folder Operations Example ==='

Expand Down
Loading
Loading