btps_client 1.0.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.
Files changed (38) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +444 -0
  3. data/lib/btps_client/api_operations/actionable.rb +22 -0
  4. data/lib/btps_client/api_operations/creatable.rb +16 -0
  5. data/lib/btps_client/api_operations/deletable.rb +15 -0
  6. data/lib/btps_client/api_operations/listable.rb +16 -0
  7. data/lib/btps_client/api_operations/movable.rb +17 -0
  8. data/lib/btps_client/api_operations/nested_listable.rb +23 -0
  9. data/lib/btps_client/api_operations/retrievable.rb +15 -0
  10. data/lib/btps_client/api_operations/scalar_creatable.rb +31 -0
  11. data/lib/btps_client/api_operations/searchable.rb +16 -0
  12. data/lib/btps_client/api_operations/updatable.rb +16 -0
  13. data/lib/btps_client/api_operations/uploadable.rb +29 -0
  14. data/lib/btps_client/api_requestor.rb +143 -0
  15. data/lib/btps_client/api_resource.rb +45 -0
  16. data/lib/btps_client/btps_object.rb +58 -0
  17. data/lib/btps_client/client.rb +37 -0
  18. data/lib/btps_client/configuration.rb +71 -0
  19. data/lib/btps_client/errors.rb +39 -0
  20. data/lib/btps_client/resources/credential.rb +98 -0
  21. data/lib/btps_client/resources/folder.rb +36 -0
  22. data/lib/btps_client/resources/managed_account.rb +97 -0
  23. data/lib/btps_client/resources/request.rb +107 -0
  24. data/lib/btps_client/resources/safe.rb +77 -0
  25. data/lib/btps_client/resources/secret.rb +179 -0
  26. data/lib/btps_client/schema_builder.rb +50 -0
  27. data/lib/btps_client/services/auth_service.rb +57 -0
  28. data/lib/btps_client/services/credentials_service.rb +36 -0
  29. data/lib/btps_client/services/folders_service.rb +37 -0
  30. data/lib/btps_client/services/managed_accounts_service.rb +48 -0
  31. data/lib/btps_client/services/requests_service.rb +44 -0
  32. data/lib/btps_client/services/safes_service.rb +48 -0
  33. data/lib/btps_client/services/secrets_safe_service.rb +25 -0
  34. data/lib/btps_client/services/secrets_service.rb +96 -0
  35. data/lib/btps_client/validator.rb +129 -0
  36. data/lib/btps_client/version.rb +5 -0
  37. data/lib/btps_client.rb +64 -0
  38. metadata +183 -0
@@ -0,0 +1,143 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'typhoeus'
4
+ require 'json'
5
+
6
+ module BtpsClient
7
+ # Single-responsibility HTTP layer.
8
+ # Builds requests, executes via Typhoeus, retries on 429/5xx,
9
+ # and maps HTTP status codes to the SDK error hierarchy.
10
+ # Never logs request bodies or bearer tokens.
11
+ class ApiRequestor
12
+ ERROR_MAP = {
13
+ 400 => InvalidRequestError,
14
+ 401 => AuthenticationError,
15
+ 403 => PermissionError,
16
+ 404 => NotFoundError,
17
+ 409 => ConflictError,
18
+ 422 => ValidationError,
19
+ 429 => RateLimitError
20
+ }.freeze
21
+
22
+ def initialize(config)
23
+ @config = config
24
+ end
25
+
26
+ # Performs an HTTP request with automatic retry on retriable errors.
27
+ #
28
+ # @param method [Symbol] :get, :post, :put, :delete
29
+ # @param path [String] path relative to base_url (leading slash optional)
30
+ # @param params [Hash] query-string parameters
31
+ # @param body [Hash|String|nil] request body
32
+ # @param opts [Hash] { headers: {}, multipart: bool, form_encoded: bool }
33
+ # @return [Hash|Array|String|nil] parsed response body
34
+ def request(method, path, params: {}, body: nil, opts: {})
35
+ url = "#{@config.base_url}/#{path.to_s.sub(%r{^/}, '')}"
36
+ attempt = 0
37
+
38
+ begin
39
+ attempt += 1
40
+ log(:debug, "→ #{method.upcase} #{url}#{" params=#{params}" unless params.empty?}")
41
+ response = execute(method, url, build_headers(opts), params, body, opts)
42
+ log(:debug, "← #{response.code} (attempt #{attempt})")
43
+ if response.code.zero?
44
+ raise ApiError.new(
45
+ "Network error: #{response.return_message} (curl code #{response.return_code})",
46
+ http_status: 0, http_body: nil
47
+ )
48
+ end
49
+ handle_response(response)
50
+ rescue RateLimitError, ApiError => e
51
+ log(:warn, "Retriable error (attempt #{attempt}/#{@config.max_retries}): #{e.message}")
52
+ raise if attempt > @config.max_retries
53
+
54
+ sleep(backoff(attempt))
55
+ retry
56
+ end
57
+ end
58
+
59
+ private
60
+
61
+ def execute(method, url, headers, params, body, opts)
62
+ req_opts = {
63
+ method: method,
64
+ headers: headers,
65
+ ssl_verifypeer: @config.verify_ssl,
66
+ timeout: @config.timeout,
67
+ connecttimeout: @config.connect_timeout
68
+ }
69
+
70
+ req_opts[:params] = params unless params.nil? || params.empty?
71
+
72
+ if opts[:multipart]
73
+ req_opts[:body] = body
74
+ elsif opts[:form_encoded]
75
+ req_opts[:body] = body
76
+ req_opts[:headers] = req_opts[:headers].merge('Content-Type' => 'application/x-www-form-urlencoded')
77
+ elsif !body.nil?
78
+ encoded_body = body.is_a?(String) ? body : body.to_json
79
+ req_opts[:body] = encoded_body
80
+ req_opts[:headers] = req_opts[:headers].merge('Content-Type' => 'application/json')
81
+ end
82
+
83
+ Typhoeus::Request.new(url, req_opts).run
84
+ end
85
+
86
+ def handle_response(response)
87
+ status = response.code
88
+ body = response.body
89
+ request_id = response.headers&.[]('X-Request-Id')
90
+
91
+ return parse_success_body(body) if status >= 200 && status < 300
92
+
93
+ raise_for_status(status, body, request_id)
94
+ end
95
+
96
+ def parse_success_body(body)
97
+ return nil if body.nil? || body.strip.empty?
98
+
99
+ JSON.parse(body)
100
+ rescue JSON::ParserError
101
+ body
102
+ end
103
+
104
+ def raise_for_status(status, body, request_id)
105
+ error_class = ERROR_MAP[status] || (status >= 500 ? ApiError : BtpsError)
106
+ log(:error, "HTTP #{status} (request_id=#{request_id}): #{body}")
107
+ raise error_class.new(parse_error_message(body), http_status: status, http_body: body, request_id: request_id)
108
+ end
109
+
110
+ def parse_error_message(body)
111
+ return nil if body.nil? || body.strip.empty?
112
+
113
+ parsed = JSON.parse(body)
114
+ extract_error_message(parsed)
115
+ rescue JSON::ParserError
116
+ body
117
+ end
118
+
119
+ def extract_error_message(parsed)
120
+ return parsed.to_s unless parsed.is_a?(Hash)
121
+
122
+ parsed['message'] || parsed['Message'] || parsed['error'] || parsed.to_s
123
+ end
124
+
125
+ def build_headers(opts = {})
126
+ headers = {
127
+ 'Accept' => 'application/json',
128
+ 'User-Agent' => "btps_client/#{BtpsClient::VERSION} Ruby/#{RUBY_VERSION}"
129
+ }
130
+ headers['Authorization'] = "Bearer #{@config.access_token}" if @config.access_token
131
+ headers.merge(opts.fetch(:headers, {}))
132
+ end
133
+
134
+ def backoff(attempt)
135
+ base = [(2**(attempt - 1)) * 0.5, 32].min
136
+ base * (0.5 + (rand * 0.5)) # jitter: randomise between 50% and 100% of base
137
+ end
138
+
139
+ def log(level, message)
140
+ @config.logger&.public_send(level, "[BtpsClient] #{message}")
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BtpsClient
4
+ # Abstract marker for all API resource classes.
5
+ # Subclasses declare their base path with the `resource_path` class macro
6
+ # and extend only the operation mixins that correspond to real API endpoints.
7
+ class ApiResource < BtpsObject
8
+ # Class macro — call once per subclass to set the URL path segment.
9
+ # resource_path 'secrets-safe/folders'
10
+ def self.resource_path(path = nil)
11
+ return @resource_path = path if path
12
+
13
+ @resource_path || raise(NotImplementedError, "#{name} must declare resource_path")
14
+ end
15
+
16
+ # Returns the full collection URL, or the member URL when an id is given.
17
+ def self.resource_url(id = nil)
18
+ id ? "#{resource_path}/#{id}" : resource_path
19
+ end
20
+
21
+ # Class macro — declares validation constraints inline on each resource.
22
+ # The block is evaluated inside a SchemaBuilder instance:
23
+ #
24
+ # param_schema :create do
25
+ # required 'Name', type: :string, min_length: 1, max_length: 256
26
+ # optional 'Description', type: :string, max_length: 256
27
+ # end
28
+ #
29
+ # Calling param_schema with the same operation again overwrites the
30
+ # previous schema for that class.
31
+ def self.param_schema(operation, &block)
32
+ @param_schemas ||= {}
33
+ builder = SchemaBuilder.new
34
+ builder.instance_eval(&block)
35
+ @param_schemas[operation.to_sym] = builder.to_h
36
+ end
37
+
38
+ # Returns the schema Hash registered for +operation+, or nil when none
39
+ # has been declared (meaning validation is skipped for that operation).
40
+ def self.schema_for(operation)
41
+ @param_schemas ||= {}
42
+ @param_schemas[operation.to_sym]
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BtpsClient
4
+ # Immutable base class for all deserialized API responses.
5
+ # Converts PascalCase JSON keys into snake_case Ruby reader methods.
6
+ # Instances are frozen after initialization — no mutations allowed.
7
+ class BtpsObject
8
+ attr_reader :raw_response
9
+
10
+ # Ruby built-in method names that must never be overwritten.
11
+ # API keys that snake_case to any of these are accessible via #[] instead.
12
+ RESERVED_METHODS = (BasicObject.instance_methods | Object.instance_methods)
13
+ .map(&:to_s).freeze
14
+
15
+ def initialize(data = {})
16
+ data = {} if data.nil?
17
+ @raw_response = data.dup.freeze
18
+
19
+ data.each_key do |key|
20
+ attr_name = snake_case(key.to_s)
21
+ next if RESERVED_METHODS.include?(attr_name)
22
+
23
+ val = sanitize(data[key])
24
+ # Define per-instance readers via the singleton class so different
25
+ # instances with different keys do not pollute each other's class.
26
+ singleton_class.define_method(attr_name) { val }
27
+ end
28
+
29
+ freeze
30
+ end
31
+
32
+ # Fetch any field by its original API key name (string or symbol).
33
+ # Useful for fields whose snake_case name collides with a Ruby built-in
34
+ # (e.g. 'ObjectId' → 'object_id') and therefore have no reader method.
35
+ def [](key)
36
+ @raw_response[key.to_s] || @raw_response[key.to_sym]
37
+ end
38
+
39
+ private
40
+
41
+ # Converts PascalCase / camelCase key strings into snake_case.
42
+ def snake_case(str)
43
+ str
44
+ .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
45
+ .gsub(/([a-z\d])([A-Z])/, '\1_\2')
46
+ .downcase
47
+ end
48
+
49
+ # Recursively wraps nested hashes as BtpsObjects and freezes arrays.
50
+ def sanitize(value)
51
+ case value
52
+ when Hash then BtpsObject.new(value)
53
+ when Array then value.map { |v| v.is_a?(Hash) ? BtpsObject.new(v) : v }.freeze
54
+ else value
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BtpsClient
4
+ # User-facing entry point. Instantiate with per-client config overrides
5
+ # or rely on the global BtpsClient.configure block.
6
+ class Client
7
+ def initialize(**opts)
8
+ base = BtpsClient.configuration.to_h
9
+ @config = Configuration.new(base.merge(opts))
10
+ end
11
+
12
+ # Returns the SecretsSafeService aggregator (folders / safes / secrets).
13
+ def secrets_safe
14
+ @secrets_safe ||= Services::SecretsSafeService.new(@config)
15
+ end
16
+
17
+ # Returns the AuthService for sign-in, sign-out, and OAuth token acquisition.
18
+ def auth
19
+ @auth ||= Services::AuthService.new(@config)
20
+ end
21
+
22
+ # Returns the ManagedAccountsService.
23
+ def managed_accounts
24
+ @managed_accounts ||= Services::ManagedAccountsService.new(@config)
25
+ end
26
+
27
+ # Returns the RequestsService.
28
+ def requests
29
+ @requests ||= Services::RequestsService.new(@config)
30
+ end
31
+
32
+ # Returns the CredentialsService.
33
+ def credentials
34
+ @credentials ||= Services::CredentialsService.new(@config)
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BtpsClient
4
+ # Holds all SDK configuration.
5
+ # Build via BtpsClient.configure block or pass overrides to Configuration.new.
6
+ # Per-client overrides can be passed to Client.new — each Client gets its own
7
+ # Configuration copy so changes to one do not affect others.
8
+ class Configuration
9
+ DEFAULTS = {
10
+ scheme: 'https',
11
+ host: nil,
12
+ port: nil,
13
+ base_path: 'api/public/v3',
14
+ verify_ssl: true,
15
+ timeout: 30,
16
+ connect_timeout: 10,
17
+ max_retries: 3,
18
+ logger: nil
19
+ }.freeze
20
+
21
+ ALLOWED_ATTRS = DEFAULTS.keys.freeze
22
+
23
+ attr_accessor :access_token, :client_id, :client_secret
24
+ attr_reader(*ALLOWED_ATTRS)
25
+
26
+ def initialize(overrides = {})
27
+ overrides = overrides.transform_keys(&:to_sym)
28
+ invalid = overrides.keys - ALLOWED_ATTRS - %i[access_token client_id client_secret]
29
+ raise ArgumentError, "Unknown configuration keys: #{invalid.join(', ')}" unless invalid.empty?
30
+
31
+ ALLOWED_ATTRS.each do |key|
32
+ instance_variable_set(:"@#{key}", overrides.fetch(key, DEFAULTS[key]))
33
+ end
34
+
35
+ @access_token = overrides[:access_token] || ENV.fetch('BTPS_ACCESS_TOKEN', nil)
36
+ @client_id = overrides[:client_id] || ENV.fetch('BTPS_CLIENT_ID', nil)
37
+ @client_secret = overrides[:client_secret] || ENV.fetch('BTPS_CLIENT_SECRET', nil)
38
+ end
39
+
40
+ # Setters for use inside BtpsClient.configure { |c| c.host = '...' }
41
+ ALLOWED_ATTRS.each do |attr|
42
+ define_method(:"#{attr}=") { |val| instance_variable_set(:"@#{attr}", val) }
43
+ end
44
+
45
+ # Builds the full base URL used by ApiRequestor.
46
+ def base_url
47
+ port_segment = port ? ":#{port}" : ''
48
+ "#{scheme}://#{host}#{port_segment}/#{base_path}"
49
+ end
50
+
51
+ # Returns a safe string representation — credentials are redacted so this
52
+ # is safe to appear in logs, exception messages, and IRB sessions.
53
+ def inspect
54
+ attrs = ALLOWED_ATTRS.map { |k| "#{k}=#{public_send(k).inspect}" }
55
+ attrs << "access_token=#{@access_token ? '[REDACTED]' : 'nil'}"
56
+ attrs << "client_id=#{@client_id ? '[REDACTED]' : 'nil'}"
57
+ attrs << "client_secret=#{@client_secret ? '[REDACTED]' : 'nil'}"
58
+ "#<#{self.class} #{attrs.join(', ')}>"
59
+ end
60
+
61
+ # Returns all settings as a plain Hash — used by Client to merge global config
62
+ # with per-instance overrides.
63
+ def to_h
64
+ hash = ALLOWED_ATTRS.to_h { |key| [key, public_send(key)] }
65
+ hash[:access_token] = @access_token
66
+ hash[:client_id] = @client_id
67
+ hash[:client_secret] = @client_secret
68
+ hash
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BtpsClient
4
+ # Base class for all SDK errors. Carries HTTP context without leaking credentials.
5
+ class BtpsError < StandardError
6
+ attr_reader :http_status, :http_body, :request_id
7
+
8
+ def initialize(message = nil, http_status: nil, http_body: nil, request_id: nil)
9
+ super(message)
10
+ @http_status = http_status
11
+ @http_body = http_body
12
+ @request_id = request_id
13
+ end
14
+ end
15
+
16
+ # HTTP 400
17
+ class InvalidRequestError < BtpsError; end
18
+
19
+ # HTTP 401
20
+ class AuthenticationError < BtpsError; end
21
+
22
+ # HTTP 403
23
+ class PermissionError < BtpsError; end
24
+
25
+ # HTTP 404
26
+ class NotFoundError < BtpsError; end
27
+
28
+ # HTTP 409
29
+ class ConflictError < BtpsError; end
30
+
31
+ # HTTP 422
32
+ class ValidationError < BtpsError; end
33
+
34
+ # HTTP 429
35
+ class RateLimitError < BtpsError; end
36
+
37
+ # HTTP 5xx
38
+ class ApiError < BtpsError; end
39
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BtpsClient
4
+ module Resources
5
+ # Swagger tag: Credentials
6
+ # Endpoints:
7
+ # GET /credentials/{requestId} — retrieve password by request
8
+ # PUT /credentials — update credentials (bulk)
9
+ # GET /managedaccounts/{managedAccountID}/credentials — get current credentials
10
+ # PUT /managedaccounts/{managedAccountID}/credentials — set/update credentials
11
+ # POST /managedaccounts/{managedAccountID}/credentials/test — test credentials on system
12
+ # POST /managedaccounts/{managedAccountID}/credentials/change — trigger live rotation
13
+ class Credential < ApiResource
14
+ resource_path 'credentials'
15
+
16
+ # Update schema — maps to CredentialsUpdateModel (all optional)
17
+ param_schema :update do
18
+ optional 'Password', type: :string, nullable: true
19
+ optional 'Credentials', type: :string, nullable: true
20
+ optional 'PublicKey', type: :string, nullable: true
21
+ optional 'PrivateKey', type: :string, nullable: true
22
+ optional 'Passphrase', type: :string, nullable: true
23
+ optional 'UpdateSystem', type: :boolean, nullable: true
24
+ end
25
+
26
+ # Change schema — maps to CredentialsChangeModel (all optional)
27
+ param_schema :change do
28
+ optional 'Password', type: :string, nullable: true
29
+ optional 'Credentials', type: :string, nullable: true
30
+ optional 'PublicKey', type: :string, nullable: true
31
+ optional 'PrivateKey', type: :string, nullable: true
32
+ optional 'Passphrase', type: :string, nullable: true
33
+ optional 'UpdateSystem', type: :boolean, nullable: true
34
+ optional 'Queue', type: :boolean, nullable: true
35
+ end
36
+
37
+ # ── Request-scoped retrieve ────────────────────────────────────────────────
38
+
39
+ # GET /credentials/{requestId}
40
+ # Returns the password/credential for an active request as a BtpsObject with .value.
41
+ def self.retrieve_by_request(request_id, config: BtpsClient.config)
42
+ requestor = ApiRequestor.new(config)
43
+ data = requestor.request(:get, "#{resource_path}/#{request_id}")
44
+ BtpsObject.new('value' => data)
45
+ end
46
+
47
+ # ── Global (bulk) update ───────────────────────────────────────────────────
48
+
49
+ # PUT /credentials
50
+ # Updates credentials in bulk (not scoped to a specific managed account).
51
+ def self.update_global(params, config: BtpsClient.config)
52
+ Validator.validate!(schema_for(:update), params)
53
+ requestor = ApiRequestor.new(config)
54
+ requestor.request(:put, resource_path, body: params)
55
+ true
56
+ end
57
+
58
+ # ── Account-scoped methods ────────────────────────────────────────────────
59
+
60
+ # GET /managedaccounts/{managedAccountID}/credentials
61
+ # Retrieves current credentials for a managed account (returns BtpsObject with .value).
62
+ def self.retrieve_for_account(managed_account_id, params = {}, config: BtpsClient.config)
63
+ requestor = ApiRequestor.new(config)
64
+ data = requestor.request(:get, "managedaccounts/#{managed_account_id}/credentials",
65
+ params: params)
66
+ BtpsObject.new('value' => data)
67
+ end
68
+
69
+ # PUT /managedaccounts/{managedAccountID}/credentials
70
+ # Sets or updates the stored credentials for a managed account.
71
+ def self.update_for_account(managed_account_id, params, config: BtpsClient.config)
72
+ Validator.validate!(schema_for(:update), params)
73
+ requestor = ApiRequestor.new(config)
74
+ requestor.request(:put, "managedaccounts/#{managed_account_id}/credentials", body: params)
75
+ true
76
+ end
77
+
78
+ # POST /managedaccounts/{managedAccountID}/credentials/test
79
+ # Tests whether the stored credentials succeed on the target system.
80
+ def self.test_for_account(managed_account_id, config: BtpsClient.config)
81
+ requestor = ApiRequestor.new(config)
82
+ data = requestor.request(:post, "managedaccounts/#{managed_account_id}/credentials/test")
83
+ data ? BtpsObject.new(data) : true
84
+ end
85
+
86
+ # POST /managedaccounts/{managedAccountID}/credentials/change
87
+ # Triggers a live credential rotation on the target system.
88
+ def self.change_for_account(managed_account_id, params = {}, config: BtpsClient.config)
89
+ Validator.validate!(schema_for(:change), params)
90
+ requestor = ApiRequestor.new(config)
91
+ body = params.empty? ? nil : params
92
+ requestor.request(:post, "managedaccounts/#{managed_account_id}/credentials/change",
93
+ body: body)
94
+ true
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BtpsClient
4
+ module Resources
5
+ # Swagger tag: Secrets Safe Folders
6
+ # Endpoints: GET/POST /secrets-safe/folders
7
+ # GET/PUT/DELETE /secrets-safe/folders/{folderId}
8
+ # PUT /secrets-safe/folders/{folderId}/move
9
+ class Folder < ApiResource
10
+ resource_path 'secrets-safe/folders'
11
+
12
+ extend ApiOperations::Listable # GET /secrets-safe/folders
13
+ extend ApiOperations::Retrievable # GET /secrets-safe/folders/{folderId}
14
+ extend ApiOperations::Creatable # POST /secrets-safe/folders
15
+ extend ApiOperations::Updatable # PUT /secrets-safe/folders/{folderId}
16
+ extend ApiOperations::Deletable # DELETE /secrets-safe/folders/{folderId}
17
+ extend ApiOperations::Movable # PUT /secrets-safe/folders/{folderId}/move
18
+
19
+ param_schema :create do
20
+ required 'Name', type: :string, min_length: 1, max_length: 256
21
+ optional 'Description', type: :string, max_length: 256, nullable: true
22
+ optional 'ParentId', type: :string, format: :uuid, nullable: true
23
+ end
24
+
25
+ param_schema :update do
26
+ required 'Name', type: :string, min_length: 1
27
+ optional 'Description', type: :string, max_length: 256, nullable: true
28
+ end
29
+
30
+ param_schema :move do
31
+ required 'DestinationFolderId', type: :string, format: :uuid
32
+ required 'DuplicateNameAction', type: :string, values: %w[Rename Replace Abort]
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BtpsClient
4
+ module Resources
5
+ # Swagger tag: Managed Accounts
6
+ # Endpoints:
7
+ # GET /managedaccounts — list requestable accounts
8
+ # GET /managedaccounts/{id} — get by ID
9
+ # PUT /managedaccounts/{id} — update
10
+ # DELETE /managedaccounts/{id} — delete
11
+ # GET /managedsystems/{managedSystemID}/managedaccounts — list by managed system
12
+ # POST /managedsystems/{managedSystemID}/managedaccounts — create in managed system
13
+ # DELETE /managedsystems/{managedSystemID}/managedaccounts — delete all in system
14
+ # DELETE /managedsystems/{managedSystemID}/managedaccounts/{name} — delete by system + name
15
+ # GET /smartrules/{smartRuleID}/managedaccounts — list by smart rule
16
+ class ManagedAccount < ApiResource
17
+ resource_path 'managedaccounts'
18
+
19
+ extend ApiOperations::Listable # GET /managedaccounts
20
+ extend ApiOperations::Retrievable # GET /managedaccounts/{id}
21
+ extend ApiOperations::Updatable # PUT /managedaccounts/{id}
22
+ extend ApiOperations::Deletable # DELETE /managedaccounts/{id}
23
+ extend ApiOperations::NestedListable # list_for(parent_path, parent_id)
24
+
25
+ # Create schema — AccountName required, all others from ManagedAccountAddUpdateModel
26
+ param_schema :create do
27
+ required 'AccountName', type: :string, max_length: 245
28
+ optional 'Password', type: :string, max_length: 128, nullable: true
29
+ optional 'Description', type: :string, max_length: 1024, nullable: true
30
+ optional 'ApiEnabled', type: :boolean, nullable: true
31
+ optional 'PasswordRuleID', type: :integer, nullable: true
32
+ optional 'ReleaseDuration', type: :integer, minimum: 1, maximum: 525_600
33
+ optional 'MaxReleaseDuration', type: :integer, minimum: 1, maximum: 525_600
34
+ optional 'ISAReleaseDuration', type: :integer, minimum: 1, maximum: 525_600
35
+ optional 'AutoManagementFlag', type: :boolean, nullable: true
36
+ optional 'ChangeFrequencyType', type: :string, values: %w[first last xdays], nullable: true
37
+ optional 'ChangeFrequencyDays', type: :integer, minimum: 1, maximum: 999
38
+ end
39
+
40
+ # Update schema — all optional for PUT /managedaccounts/{id}
41
+ param_schema :update do
42
+ optional 'AccountName', type: :string, max_length: 245, nullable: true
43
+ optional 'Password', type: :string, max_length: 128, nullable: true
44
+ optional 'Description', type: :string, max_length: 1024, nullable: true
45
+ optional 'ApiEnabled', type: :boolean, nullable: true
46
+ optional 'PasswordRuleID', type: :integer, nullable: true
47
+ optional 'ReleaseDuration', type: :integer, minimum: 1, maximum: 525_600
48
+ optional 'MaxReleaseDuration', type: :integer, minimum: 1, maximum: 525_600
49
+ optional 'ISAReleaseDuration', type: :integer, minimum: 1, maximum: 525_600
50
+ optional 'AutoManagementFlag', type: :boolean, nullable: true
51
+ optional 'ChangeFrequencyType', type: :string, values: %w[first last xdays], nullable: true
52
+ optional 'ChangeFrequencyDays', type: :integer, minimum: 1, maximum: 999
53
+ end
54
+
55
+ # ── Managed System-scoped methods ──────────────────────────────────────────
56
+
57
+ # GET /managedsystems/{managedSystemID}/managedaccounts
58
+ # Lists all managed accounts for a specific managed system.
59
+ def self.list_by_system(system_id, params = {}, config: BtpsClient.config)
60
+ list_for('managedsystems', system_id, params: params, config: config)
61
+ end
62
+
63
+ # POST /managedsystems/{managedSystemID}/managedaccounts
64
+ # Creates a managed account within the given managed system.
65
+ def self.create_in_system(system_id, params, config: BtpsClient.config)
66
+ Validator.validate!(schema_for(:create), params)
67
+ requestor = ApiRequestor.new(config)
68
+ data = requestor.request(:post, "managedsystems/#{system_id}/managedaccounts", body: params)
69
+ new(data)
70
+ end
71
+
72
+ # DELETE /managedsystems/{managedSystemID}/managedaccounts
73
+ # Deletes ALL managed accounts within the given managed system.
74
+ def self.delete_all_in_system(system_id, config: BtpsClient.config)
75
+ requestor = ApiRequestor.new(config)
76
+ requestor.request(:delete, "managedsystems/#{system_id}/managedaccounts")
77
+ true
78
+ end
79
+
80
+ # DELETE /managedsystems/{managedSystemID}/managedaccounts/{accountName}
81
+ # Deletes a specific account by managed system ID and account name.
82
+ def self.delete_by_system_and_name(system_id, account_name, config: BtpsClient.config)
83
+ requestor = ApiRequestor.new(config)
84
+ requestor.request(:delete, "managedsystems/#{system_id}/managedaccounts/#{account_name}")
85
+ true
86
+ end
87
+
88
+ # ── Smart Rule-scoped method ────────────────────────────────────────────────
89
+
90
+ # GET /smartrules/{smartRuleID}/managedaccounts
91
+ # Lists all managed accounts matching a smart rule.
92
+ def self.list_by_smart_rule(smart_rule_id, params = {}, config: BtpsClient.config)
93
+ list_for('smartrules', smart_rule_id, params: params, config: config)
94
+ end
95
+ end
96
+ end
97
+ end