keeper_secrets_manager 17.2.1

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.
@@ -0,0 +1,152 @@
1
+ module KeeperSecretsManager
2
+ module FieldTypes
3
+ # Base field helper
4
+ class Field
5
+ attr_accessor :type, :label, :value, :required, :privacy_screen
6
+
7
+ def initialize(type:, value:, label: nil, required: false, privacy_screen: false)
8
+ @type = type
9
+ @label = label
10
+ @required = required
11
+ @privacy_screen = privacy_screen
12
+
13
+ # Ensure value is always an array
14
+ @value = value.is_a?(Array) ? value : [value]
15
+ end
16
+
17
+ def to_h
18
+ h = { 'type' => type, 'value' => value }
19
+ h['label'] = label if label
20
+ h['required'] = required if required
21
+ h['privacyScreen'] = privacy_screen if privacy_screen
22
+ h
23
+ end
24
+ end
25
+
26
+ # Helper methods for creating common fields
27
+ module Helpers
28
+ def self.login(value, label: nil)
29
+ Field.new(type: 'login', value: value, label: label)
30
+ end
31
+
32
+ def self.password(value, label: nil)
33
+ Field.new(type: 'password', value: value, label: label)
34
+ end
35
+
36
+ def self.url(value, label: nil)
37
+ Field.new(type: 'url', value: value, label: label)
38
+ end
39
+
40
+ def self.file_ref(value, label: nil)
41
+ Field.new(type: 'fileRef', value: value, label: label)
42
+ end
43
+
44
+ def self.one_time_code(value, label: nil)
45
+ Field.new(type: 'oneTimeCode', value: value, label: label)
46
+ end
47
+
48
+ def self.name(first:, last:, middle: nil, label: nil)
49
+ value = { 'first' => first, 'last' => last }
50
+ value['middle'] = middle if middle
51
+ Field.new(type: 'name', value: value, label: label)
52
+ end
53
+
54
+ def self.phone(number:, region: 'US', type: nil, ext: nil, label: nil)
55
+ value = { 'region' => region, 'number' => number }
56
+ value['type'] = type if type
57
+ value['ext'] = ext if ext
58
+ Field.new(type: 'phone', value: value, label: label)
59
+ end
60
+
61
+ def self.email(email, label: nil)
62
+ Field.new(type: 'email', value: email, label: label)
63
+ end
64
+
65
+ def self.address(street1:, city:, state:, zip:, country: 'US', street2: nil, label: nil)
66
+ value = {
67
+ 'street1' => street1,
68
+ 'city' => city,
69
+ 'state' => state,
70
+ 'zip' => zip,
71
+ 'country' => country
72
+ }
73
+ value['street2'] = street2 if street2
74
+ Field.new(type: 'address', value: value, label: label)
75
+ end
76
+
77
+ def self.payment_card(number:, expiration_date:, security_code:, cardholder_name: nil, label: nil)
78
+ value = {
79
+ 'cardNumber' => number,
80
+ 'cardExpirationDate' => expiration_date,
81
+ 'cardSecurityCode' => security_code
82
+ }
83
+ value['cardholderName'] = cardholder_name if cardholder_name
84
+ Field.new(type: 'paymentCard', value: value, label: label)
85
+ end
86
+
87
+ def self.bank_account(account_type:, routing_number:, account_number:, label: nil)
88
+ value = {
89
+ 'accountType' => account_type,
90
+ 'routingNumber' => routing_number,
91
+ 'accountNumber' => account_number
92
+ }
93
+ Field.new(type: 'bankAccount', value: value, label: label)
94
+ end
95
+
96
+ def self.birth_date(date, label: nil)
97
+ # Date should be in unix timestamp (milliseconds)
98
+ timestamp = case date
99
+ when Date, Time, DateTime
100
+ (date.to_time.to_f * 1000).to_i
101
+ when Integer
102
+ date
103
+ when String
104
+ (Date.parse(date).to_time.to_f * 1000).to_i
105
+ else
106
+ raise ArgumentError, 'Invalid date format'
107
+ end
108
+ Field.new(type: 'birthDate', value: timestamp, label: label)
109
+ end
110
+
111
+ def self.secure_note(note, label: nil)
112
+ Field.new(type: 'secureNote', value: note, label: label)
113
+ end
114
+
115
+ def self.ssh_key(private_key:, public_key: nil, label: nil)
116
+ value = { 'privateKey' => private_key }
117
+ value['publicKey'] = public_key if public_key
118
+ Field.new(type: 'sshKey', value: value, label: label)
119
+ end
120
+
121
+ def self.host(hostname:, port: nil, label: nil)
122
+ value = { 'hostName' => hostname }
123
+ value['port'] = port.to_s if port
124
+ Field.new(type: 'host', value: value, label: label)
125
+ end
126
+
127
+ def self.database_type(type, label: nil)
128
+ Field.new(type: 'databaseType', value: type, label: label)
129
+ end
130
+
131
+ def self.script(script, label: nil)
132
+ Field.new(type: 'script', value: script, label: label)
133
+ end
134
+
135
+ def self.passkey(private_key:, credential_id:, rp_id:, user_id:, username:, label: nil)
136
+ value = {
137
+ 'privateKey' => private_key,
138
+ 'credentialId' => credential_id,
139
+ 'relyingParty' => rp_id,
140
+ 'userId' => user_id,
141
+ 'username' => username
142
+ }
143
+ Field.new(type: 'passkey', value: value, label: label)
144
+ end
145
+
146
+ # Generic field for any type
147
+ def self.custom(type:, value:, label: nil, required: false)
148
+ Field.new(type: type, value: value, label: label, required: required)
149
+ end
150
+ end
151
+ end
152
+ end
@@ -0,0 +1,110 @@
1
+ module KeeperSecretsManager
2
+ class FolderManager
3
+ def initialize(folders)
4
+ @folders = folders
5
+ end
6
+
7
+ # Build a hierarchical tree structure from flat folder list
8
+ def build_folder_tree
9
+ # Create a hash for quick lookup
10
+ folder_map = {}
11
+ @folders.each { |f| folder_map[f.uid] = f }
12
+
13
+ # Find root folders (no parent) and build tree
14
+ root_folders = []
15
+ @folders.each do |folder|
16
+ root_folders << build_node(folder, folder_map) if folder.parent_uid.nil? || folder.parent_uid.empty?
17
+ end
18
+
19
+ root_folders
20
+ end
21
+
22
+ # Get folder path from root to given folder
23
+ def get_folder_path(folder_uid)
24
+ folder = @folders.find { |f| f.uid == folder_uid }
25
+ return nil unless folder
26
+
27
+ path = []
28
+ current = folder
29
+
30
+ # Walk up the tree
31
+ while current
32
+ path.unshift(current.name)
33
+ current = @folders.find { |f| f.uid == current.parent_uid }
34
+ end
35
+
36
+ path.join('/')
37
+ end
38
+
39
+ # Get all ancestors of a folder (parent, grandparent, etc.)
40
+ def get_ancestors(folder_uid)
41
+ ancestors = []
42
+ folder = @folders.find { |f| f.uid == folder_uid }
43
+ return ancestors unless folder
44
+
45
+ current_parent_uid = folder.parent_uid
46
+ while current_parent_uid && !current_parent_uid.empty?
47
+ parent = @folders.find { |f| f.uid == current_parent_uid }
48
+ break unless parent
49
+
50
+ ancestors << parent
51
+ current_parent_uid = parent.parent_uid
52
+ end
53
+
54
+ ancestors
55
+ end
56
+
57
+ # Get all descendants of a folder (children, grandchildren, etc.)
58
+ def get_descendants(folder_uid)
59
+ descendants = []
60
+ children = @folders.select { |f| f.parent_uid == folder_uid }
61
+
62
+ children.each do |child|
63
+ descendants << child
64
+ descendants.concat(get_descendants(child.uid))
65
+ end
66
+
67
+ descendants
68
+ end
69
+
70
+ # Find folder by name (optionally within a parent)
71
+ def find_folder_by_name(name, parent_uid: nil)
72
+ if parent_uid
73
+ @folders.find { |f| f.name == name && f.parent_uid == parent_uid }
74
+ else
75
+ @folders.find { |f| f.name == name }
76
+ end
77
+ end
78
+
79
+ # Print folder tree to console
80
+ def print_tree(folders = nil, indent = 0)
81
+ folders ||= build_folder_tree
82
+
83
+ folders.each do |node|
84
+ puts "#{' ' * indent}├── #{node[:folder].name} (#{node[:folder].uid})"
85
+ if node[:folder].records && !node[:folder].records.empty?
86
+ node[:folder].records.each do |record|
87
+ puts "#{' ' * (indent + 4)}└─ #{record.title} (#{record.uid})"
88
+ end
89
+ end
90
+ print_tree(node[:children], indent + 4) if node[:children]
91
+ end
92
+ end
93
+
94
+ private
95
+
96
+ def build_node(folder, folder_map)
97
+ node = {
98
+ folder: folder,
99
+ children: []
100
+ }
101
+
102
+ # Find children
103
+ @folders.each do |f|
104
+ node[:children] << build_node(f, folder_map) if f.parent_uid == folder.uid
105
+ end
106
+
107
+ node
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,53 @@
1
+ require_relative 'version'
2
+
3
+ module KeeperSecretsManager
4
+ module KeeperGlobals
5
+ CLIENT_VERSION_PREFIX = 'mb'.freeze
6
+
7
+ def self.client_version
8
+ "#{CLIENT_VERSION_PREFIX}#{KeeperSecretsManager::VERSION}"
9
+ end
10
+
11
+ # Keeper public keys by ID
12
+ KEEPER_PUBLIC_KEYS = {
13
+ '1' => 'BK9w6TZFxE6nFNbMfIpULCup2a8xc6w2tUTABjxny7yFmxW0dAEojwC6j6zb5nTlmb1dAx8nwo3qF7RPYGmloRM',
14
+ '2' => 'BK9w6TZFxE6nFNbMfIpULCup2a8xc6w2tUTABjxny7yFmxW0dAEojwC6j6zb5nTlmb1dAx8nwo3qF7RPYGmloRM',
15
+ '3' => 'BK9w6TZFxE6nFNbMfIpULCup2a8xc6w2tUTABjxny7yFmxW0dAEojwC6j6zb5nTlmb1dAx8nwo3qF7RPYGmloRM',
16
+ '4' => 'BK9w6TZFxE6nFNbMfIpULCup2a8xc6w2tUTABjxny7yFmxW0dAEojwC6j6zb5nTlmb1dAx8nwo3qF7RPYGmloRM',
17
+ '5' => 'BK9w6TZFxE6nFNbMfIpULCup2a8xc6w2tUTABjxny7yFmxW0dAEojwC6j6zb5nTlmb1dAx8nwo3qF7RPYGmloRM',
18
+ '6' => 'BK9w6TZFxE6nFNbMfIpULCup2a8xc6w2tUTABjxny7yFmxW0dAEojwC6j6zb5nTlmb1dAx8nwo3qF7RPYGmloRM',
19
+ '7' => 'BK9w6TZFxE6nFNbMfIpULCup2a8xc6w2tUTABjxny7yFmxW0dAEojwC6j6zb5nTlmb1dAx8nwo3qF7RPYGmloRM',
20
+ '8' => 'BKnhy0obglZJK-igwthNLdknoSXRrGB-mvFRzyb_L-DKKefWjYdFD2888qN1ROczz4n3keYSfKz9Koj90Z6w_tQ',
21
+ '9' => 'BAsPQdCpLIGXdWNLdAwx-3J5lNqUtKbaOMV56hUj8VzxE2USLHuHHuKDeno0ymJt-acxWV1xPlBfNUShhRTR77g',
22
+ '10' => 'BNYIh_Sv03nRZUUJveE8d2mxKLIDXv654UbshaItHrCJhd6cT7pdZ_XwbdyxAOCWMkBb9AZ4t1XRCsM8-wkEBRg',
23
+ '11' => 'BA6uNfeYSvqagwu4TOY6wFK4JyU5C200vJna0lH4PJ-SzGVXej8l9dElyQ58_ljfPs5Rq6zVVXpdDe8A7Y3WRhk',
24
+ '12' => 'BMjTIlXfohI8TDymsHxo0DqYysCy7yZGJ80WhgOBR4QUd6LBDA6-_318a-jCGW96zxXKMm8clDTKpE8w75KG-FY',
25
+ '13' => 'BJBDU1P1H21IwIdT2brKkPqbQR0Zl0TIHf7Bz_OO9jaNgIwydMkxt4GpBmkYoprZ_DHUGOrno2faB7pmTR7HhuI',
26
+ '14' => 'BJFF8j-dH7pDEw_U347w2CBM6xYM8Dk5fPPAktjib-opOqzvvbsER-WDHM4ONCSBf9O_obAHzCyygxmtpktDuiE',
27
+ '15' => 'BDKyWBvLbyZ-jMueORl3JwJnnEpCiZdN7yUvT0vOyjwpPBCDf6zfL4RWzvSkhAAFnwOni_1tQSl8dfXHbXqXsQ8',
28
+ '16' => 'BDXyZZnrl0tc2jdC5I61JjwkjK2kr7uet9tZjt8StTiJTAQQmnVOYBgbtP08PWDbecxnHghx3kJ8QXq1XE68y8c',
29
+ '17' => 'BFX68cb97m9_sweGdOVavFM3j5ot6gveg6xT4BtGahfGhKib-zdZyO9pwvv1cBda9ahkSzo1BQ4NVXp9qRyqVGU',
30
+ '18' => 'BNhngQqTT1bPKxGuB6FhbPTAeNVFl8PKGGSGo5W06xWIReutm6ix6JPivqnbvkydY-1uDQTr-5e6t70G01Bb5JA'
31
+ }.freeze
32
+
33
+ # Keeper servers by region
34
+ KEEPER_SERVERS = {
35
+ 'US' => 'keepersecurity.com',
36
+ 'EU' => 'keepersecurity.eu',
37
+ 'AU' => 'keepersecurity.com.au',
38
+ 'GOV' => 'govcloud.keepersecurity.us',
39
+ 'JP' => 'keepersecurity.jp',
40
+ 'CA' => 'keepersecurity.ca',
41
+ 'IL5' => 'il5.keepersecurity.us'
42
+ }.freeze
43
+
44
+ # Default server (US)
45
+ DEFAULT_SERVER = KEEPER_SERVERS['US'].freeze
46
+
47
+ # Default public key ID
48
+ DEFAULT_KEY_ID = '7'.freeze
49
+
50
+ # Logger name
51
+ LOGGER_NAME = 'keeper_secrets_manager'.freeze
52
+ end
53
+ end