bitwarden-sdk-secrets 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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: a4a8efa2366ad5c6568ce9ec284857674c1b1730263bd538096a2861031ae833
4
+ data.tar.gz: 7e15ebee50e29c57357f958f1273fdda182035a490b555510cd95de0f49e14b0
5
+ SHA512:
6
+ metadata.gz: ac31e0a25f1ee16b2db1e2949cca718cd268a4ad0b2f18c28ffee48b1cf3ab5d3855bb771869037f00161255529f8d0715f628197e5309391162bca94e799c79
7
+ data.tar.gz: 52e926afe59c3d545d0d5441dc2cb356491291a7ffffa6af2ef7a61006dbf6ab3eef8d6662faf87eb1811d4c5eaf17a5a8756627252304868fb3b589ab0070ea
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rubocop/rake_task"
5
+ require 'rspec/core/rake_task'
6
+
7
+ RSpec::Core::RakeTask.new
8
+
9
+ RuboCop::RakeTask.new
10
+
11
+ task default: :rubocop
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'bitwarden-sdk-secrets'
7
+ spec.version = BitwardenSDKSecrets::VERSION
8
+ spec.authors = ['Bitwarden Inc.']
9
+ spec.email = ['hello@bitwarden_sdk.com']
10
+
11
+ spec.summary = 'Bitwarden Secrets Manager SDK.'
12
+ spec.description = 'Ruby wrapper for Bitwarden secrets manager SDK.'
13
+ spec.homepage = 'https://bitwarden.com/products/secrets-manager/'
14
+ spec.required_ruby_version = '>= 3.0.0'
15
+
16
+ spec.metadata['homepage_uri'] = spec.homepage
17
+ spec.metadata['source_code_uri'] = 'https://github.com/bitwarden/sdk'
18
+ spec.metadata['changelog_uri'] = 'https://github.com/bitwarden/sdk/blob/main/languages/ruby/CHANGELOG.md'
19
+
20
+ # Specify which files should be added to the gem when it is released.
21
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
22
+ spec.files = Dir.chdir(__dir__) do
23
+ `git ls-files -z`.split("\x0").reject do |f|
24
+ (File.expand_path(f) == __FILE__) || f.start_with?(*%w[bin/ test/ spec/ features/ .git Gemfile])
25
+ end
26
+ end
27
+
28
+ spec.files += Dir.glob('lib/linux-x64/**/*')
29
+ spec.files += Dir.glob('lib/macos-x64/**/*')
30
+ spec.files += Dir.glob('lib/windows-x64/**/*')
31
+ spec.files += Dir.glob('lib/macos-arm64/**/*')
32
+ spec.files += Dir.glob('lib/schemas.rb')
33
+
34
+ spec.bindir = 'exe'
35
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
36
+ spec.require_paths = ['lib']
37
+
38
+ # Uncomment to register a new dependency of your gem
39
+ # spec.add_dependency "example-gem", "~> 1.0"
40
+ spec.add_dependency 'dry-struct', '~> 1.6'
41
+ spec.add_dependency 'dry-types', '~> 1.7'
42
+ spec.add_dependency 'ffi', '~> 1.15'
43
+ spec.add_dependency 'json', '~> 2.6'
44
+ spec.add_dependency 'rake', '~> 13.0'
45
+ spec.add_dependency 'rubocop', '~> 1.21'
46
+
47
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'dry-types'
5
+
6
+ require_relative 'schemas'
7
+ require_relative 'extended_schemas/schemas'
8
+ require_relative 'command_runner'
9
+ require_relative 'bitwarden_lib'
10
+ require_relative 'bitwarden_error'
11
+ require_relative 'projects'
12
+ require_relative 'secrets'
13
+
14
+ module BitwardenSDKSecrets
15
+ class BitwardenSettings
16
+ attr_accessor :api_url, :identity_url
17
+
18
+ def initialize(api_url, identity_url)
19
+ # if api_url.nil? || identity_url.nil?
20
+ # raise ArgumentError, "api_url and identity_url cannot be nil"
21
+ # end
22
+
23
+ @api_url = api_url
24
+ @identity_url = identity_url
25
+ end
26
+ end
27
+
28
+ class BitwardenClient
29
+ attr_reader :bitwarden, :project_client, :secrets_client
30
+
31
+ def initialize(bitwarden_settings)
32
+ client_settings = ClientSettings.new(
33
+ api_url: bitwarden_settings.api_url,
34
+ identity_url: bitwarden_settings.identity_url,
35
+ user_agent: 'Bitwarden RUBY-SDK',
36
+ device_type: nil
37
+ )
38
+
39
+ @bitwarden = BitwardenLib
40
+ @handle = @bitwarden.init(client_settings.to_dynamic.compact.to_json)
41
+ @command_runner = CommandRunner.new(@bitwarden, @handle)
42
+ @project_client = ProjectsClient.new(@command_runner)
43
+ @secrets_client = SecretsClient.new(@command_runner)
44
+ end
45
+
46
+ def access_token_login(access_token, state_file = nil)
47
+ access_token_request = AccessTokenLoginRequest.new(access_token: access_token, state_file: state_file)
48
+ @command_runner.run(SelectiveCommand.new(access_token_login: access_token_request))
49
+ nil
50
+ end
51
+
52
+ def free_mem
53
+ @bitwarden.free_mem(@handle)
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BitwardenSDKSecrets
4
+ class BitwardenError < StandardError
5
+ def initialize(message = 'Error getting response')
6
+ super(message)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ffi'
4
+
5
+ module BitwardenSDKSecrets
6
+ module BitwardenLib
7
+ extend FFI::Library
8
+
9
+ def self.mac_with_intel?
10
+ `uname -m`.strip == 'x86_64'
11
+ end
12
+
13
+ ffi_lib case RUBY_PLATFORM
14
+ when /darwin/
15
+ local_file = if mac_with_intel?
16
+ File.expand_path('macos-x64/libbitwarden_c.dylib', __dir__)
17
+ else
18
+ File.expand_path('macos-arm64/libbitwarden_c.dylib', __dir__)
19
+ end
20
+ File.exist?(local_file) ? local_file : File.expand_path('../../../../target/debug/libbitwarden_c.dylib', __dir__)
21
+ when /linux/
22
+ local_file = File.expand_path('linux-x64/libbitwarden_c.so', __dir__)
23
+ File.exist?(local_file) ? local_file : File.expand_path('../../../../target/debug/libbitwarden_c.so', __dir__)
24
+ when /mswin|mingw/
25
+ local_file = File.expand_path('windows-x64/bitwarden_c.dll', __dir__)
26
+ File.exist?(local_file) ? local_file : File.expand_path('../../../../target/debug/bitwarden_c.dll', __dir__)
27
+ else
28
+ raise "Unsupported platform: #{RUBY_PLATFORM}"
29
+ end
30
+
31
+ attach_function :init, [:string], :pointer
32
+ attach_function :run_command, %i[string pointer], :string
33
+ attach_function :free_mem, [:pointer], :void
34
+ end
35
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BitwardenSDKSecrets
4
+ class CommandRunner
5
+ def initialize(bitwarden_sdk, handle)
6
+ @bitwarden_sdk = bitwarden_sdk
7
+ @handle = handle
8
+ end
9
+
10
+ # @param [Dry-Struct] cmd
11
+ def run(cmd)
12
+ @bitwarden_sdk.run_command(cmd.to_json, @handle)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,64 @@
1
+
2
+ module BitwardenSDKSecrets
3
+ class SelectiveCommand < Command
4
+ attribute :password_login, PasswordLoginRequest.optional.default(nil)
5
+ attribute :api_key_login, APIKeyLoginRequest.optional.default(nil)
6
+ attribute :access_token_login, AccessTokenLoginRequest.optional.default(nil)
7
+ attribute :get_user_api_key, SecretVerificationRequest.optional.default(nil)
8
+ attribute :fingerprint, FingerprintRequest.optional.default(nil)
9
+ attribute :sync, SyncRequest.optional.default(nil)
10
+ attribute :secrets, SecretsCommand.optional.default(nil)
11
+ attribute :projects, ProjectsCommand.optional.default(nil)
12
+
13
+ def to_dynamic
14
+ {
15
+ "passwordLogin" => password_login&.to_dynamic,
16
+ "apiKeyLogin" => api_key_login&.to_dynamic,
17
+ "accessTokenLogin" => access_token_login&.to_dynamic,
18
+ "getUserApiKey" => get_user_api_key&.to_dynamic,
19
+ "fingerprint" => fingerprint&.to_dynamic,
20
+ "sync" => sync&.to_dynamic,
21
+ "secrets" => secrets&.to_dynamic,
22
+ "projects" => projects&.to_dynamic,
23
+ }.compact
24
+ end
25
+ end
26
+
27
+ class SelectiveProjectsCommand < ProjectsCommand
28
+ attribute :get, ProjectGetRequest.optional.default(nil)
29
+ attribute :create, ProjectCreateRequest.optional.default(nil)
30
+ attribute :list, ProjectsListRequest.optional.default(nil)
31
+ attribute :update, ProjectPutRequest.optional.default(nil)
32
+ attribute :delete, ProjectsDeleteRequest.optional.default(nil)
33
+
34
+ def to_dynamic
35
+ {
36
+ "get" => get&.to_dynamic,
37
+ "create" => create&.to_dynamic,
38
+ "list" => list&.to_dynamic,
39
+ "update" => update&.to_dynamic,
40
+ "delete" => delete&.to_dynamic,
41
+ }.compact
42
+ end
43
+ end
44
+
45
+ class SelectiveSecretsCommand < SecretsCommand
46
+ attribute :get, SecretGetRequest.optional.default(nil)
47
+ attribute :get_by_ids, SecretsGetRequest.optional.default(nil)
48
+ attribute :create, SecretCreateRequest.optional.default(nil)
49
+ attribute :list, SecretIdentifiersRequest.optional.default(nil)
50
+ attribute :update, SecretPutRequest.optional.default(nil)
51
+ attribute :delete, SecretsDeleteRequest.optional.default(nil)
52
+
53
+ def to_dynamic
54
+ {
55
+ "get" => get&.to_dynamic,
56
+ "getByIds" => get_by_ids&.to_dynamic,
57
+ "create" => create&.to_dynamic,
58
+ "list" => list&.to_dynamic,
59
+ "update" => update&.to_dynamic,
60
+ "delete" => delete&.to_dynamic,
61
+ }.compact
62
+ end
63
+ end
64
+ end
Binary file
Binary file
data/lib/projects.rb ADDED
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'bitwarden_error'
4
+
5
+ module BitwardenSDKSecrets
6
+ class ProjectsClient
7
+ def initialize(command_runner)
8
+ @command_runner = command_runner
9
+ end
10
+
11
+ def create_project(project_name, organization_id)
12
+ project_create_request = ProjectCreateRequest.new(
13
+ project_create_request_name: project_name,
14
+ organization_id: organization_id
15
+ )
16
+ command = create_command(
17
+ create: project_create_request
18
+ )
19
+ response = parse_response(command)
20
+
21
+ projects_response = ResponseForProjectResponse.from_json!(response).to_dynamic
22
+
23
+ if projects_response.key?('success') && projects_response['success'] == true &&
24
+ projects_response.key?('data')
25
+ return projects_response['data']
26
+ end
27
+
28
+ error_response(projects_response)
29
+ end
30
+
31
+ def get(project_id)
32
+ project_get_request = ProjectGetRequest.new(id: project_id)
33
+ command = create_command(get: project_get_request)
34
+ response = parse_response(command)
35
+
36
+ projects_response = ResponseForProjectResponse.from_json!(response).to_dynamic
37
+
38
+ if projects_response.key?('success') && projects_response['success'] == true &&
39
+ projects_response.key?('data')
40
+ return projects_response['data']
41
+ end
42
+
43
+ error_response(projects_response)
44
+ end
45
+
46
+ def list_projects(organization_id)
47
+ project_list_request = ProjectsListRequest.new(organization_id: organization_id)
48
+ command = create_command(list: project_list_request)
49
+ response = parse_response(command)
50
+
51
+ projects_response = ResponseForProjectsResponse.from_json!(response).to_dynamic
52
+
53
+ if projects_response.key?('success') && projects_response['success'] == true &&
54
+ projects_response.key?('data') && projects_response['data'].key?('data')
55
+ return projects_response['data']['data']
56
+ end
57
+
58
+ error_response(projects_response)
59
+ end
60
+
61
+ def update_project(id, project_put_request_name, organization_id)
62
+ project_put_request = ProjectPutRequest.new(
63
+ id: id,
64
+ project_put_request_name: project_put_request_name,
65
+ organization_id: organization_id
66
+ )
67
+ command = create_command(
68
+ update: project_put_request
69
+ )
70
+ response = parse_response(command)
71
+
72
+ projects_response = ResponseForProjectResponse.from_json!(response).to_dynamic
73
+
74
+ if projects_response.key?('success') && projects_response['success'] == true &&
75
+ projects_response.key?('data')
76
+ return projects_response['data']
77
+ end
78
+
79
+ error_response(projects_response)
80
+ end
81
+
82
+ def delete_projects(ids)
83
+ project_delete_request = ProjectsDeleteRequest.new(ids: ids)
84
+ command = create_command(delete: project_delete_request)
85
+ response = parse_response(command)
86
+
87
+ projects_response = ResponseForProjectsDeleteResponse.from_json!(response).to_dynamic
88
+
89
+ if projects_response.key?('success') && projects_response['success'] == true &&
90
+ projects_response.key?('data') && projects_response['data'].key?('data')
91
+ return projects_response['data']['data']
92
+ end
93
+
94
+ error_response(projects_response)
95
+ end
96
+
97
+ private
98
+
99
+ def error_response(response)
100
+ raise BitwardenError, response['errorMessage'] if response.key?('errorMessage')
101
+
102
+ raise BitwardenError, 'Error while getting response'
103
+ end
104
+
105
+ def create_command(commands)
106
+ SelectiveCommand.new(projects: SelectiveProjectsCommand.new(commands))
107
+ end
108
+
109
+ def parse_response(command)
110
+ response = @command_runner.run(command)
111
+ raise BitwardenError, 'Error getting response' if response.nil?
112
+
113
+ response
114
+ end
115
+ end
116
+ end