diff --git a/README.md b/README.md index ceae0d4..9b563e2 100644 --- a/README.md +++ b/README.md @@ -253,6 +253,37 @@ puts "Card Template ID: #{response.card_template_identifier}" puts "Environment ID: #{response.environment_identifier}" ``` +### Webhooks + +#### Create a webhook + +```ruby +webhook = client.console.webhooks.create( + name: 'Production', + url: 'https://example.com/webhooks', + subscribed_events: ['ag.access_pass.issued'] +) + +puts "Webhook created: #{webhook.id}" +puts "Private key: #{webhook.private_key}" +``` + +#### List webhooks + +```ruby +webhooks = client.console.webhooks.list + +webhooks.each do |webhook| + puts "ID: #{webhook.id}, Name: #{webhook.name}" +end +``` + +#### Delete a webhook + +```ruby +client.console.webhooks.delete('abc123') +``` + ## Configuration The SDK can be configured with a custom API endpoint: @@ -329,9 +360,9 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/access | GET /v1/console/pass-template-pairs | `console.list_pass_template_pairs()` | Y | | POST /v1/console/card-templates/{id}/ios_preflight | `console.ios_preflight()` | Y | | GET /v1/console/ledger-items | `console.list_ledger_items()` / `console.ledger_items()` | Y | -| GET /v1/console/webhooks | `console.webhooks.list()` | - | -| POST /v1/console/webhooks | `console.webhooks.create()` | - | -| DELETE /v1/console/webhooks/{id} | `console.webhooks.delete()` | - | +| GET /v1/console/webhooks | `console.webhooks.list()` | Y | +| POST /v1/console/webhooks | `console.webhooks.create()` | Y | +| DELETE /v1/console/webhooks/{id} | `console.webhooks.delete()` | Y | | POST /v1/console/hid/orgs | `console.hid.orgs.create()` | - | | POST /v1/console/hid/orgs/activate | `console.hid.orgs.activate()` | - | | GET /v1/console/hid/orgs | `console.hid.orgs.list()` | - | diff --git a/lib/accessgrid.rb b/lib/accessgrid.rb index 81d0a9d..8aabf39 100644 --- a/lib/accessgrid.rb +++ b/lib/accessgrid.rb @@ -63,6 +63,7 @@ def execute_and_process_request!(request) def handle_response(response) case response.code.to_i + when 204 then {} when 200, 201, 202 then JSON.parse(response.body) when 401 then raise AuthenticationError, 'Invalid credentials' when 402 then raise Error, 'Insufficient account balance' diff --git a/lib/accessgrid/console.rb b/lib/accessgrid/console.rb index 88f8139..8cf07d1 100644 --- a/lib/accessgrid/console.rb +++ b/lib/accessgrid/console.rb @@ -4,8 +4,11 @@ module AccessGrid # Manages enterprise template and logging operations. class Console + attr_reader :webhooks + def initialize(client) @client = client + @webhooks = Webhooks.new(client) end def create_template(params) @@ -200,4 +203,49 @@ def initialize(data) @use_case = data['use_case'] end end + + # Manages webhook operations. + class Webhooks + def initialize(client) + @client = client + end + + def create(name:, url:, subscribed_events:, auth_method: 'bearer_token') + data = { + name: name, + url: url, + subscribed_events: subscribed_events, + auth_method: auth_method + } + response = @client.make_request(:post, '/v1/console/webhooks', data) + Webhook.new(response) + end + + def list(**params) + response = @client.make_request(:get, '/v1/console/webhooks', nil, params) + (response['webhooks'] || []).map { |wh| Webhook.new(wh) } + end + + def delete(webhook_id) + @client.make_request(:delete, "/v1/console/webhooks/#{webhook_id}") + end + end + + # Represents a webhook configuration. + class Webhook + attr_reader :id, :name, :url, :auth_method, :subscribed_events, + :created_at, :private_key, :client_cert, :cert_expires_at + + def initialize(data) + @id = data['id'] + @name = data['name'] + @url = data['url'] + @auth_method = data['auth_method'] + @subscribed_events = data['subscribed_events'] + @created_at = data['created_at'] + @private_key = data['private_key'] + @client_cert = data['client_cert'] + @cert_expires_at = data['cert_expires_at'] + end + end end diff --git a/lib/accessgrid/request.rb b/lib/accessgrid/request.rb index e863476..a890e57 100644 --- a/lib/accessgrid/request.rb +++ b/lib/accessgrid/request.rb @@ -6,6 +6,13 @@ module AccessGrid # Builds and configures HTTP requests for the AccessGrid API. class Request PAYLOAD_SIGNATURE_PARAM = :sig_payload + HTTP_METHODS = { + get: Net::HTTP::Get, + post: Net::HTTP::Post, + put: Net::HTTP::Put, + patch: Net::HTTP::Patch, + delete: Net::HTTP::Delete + }.freeze attr_reader :account_id, :body, :http_method, :params, :payload, :uri @@ -40,16 +47,9 @@ def net_http_request private def generate_net_http_request! - klass = - case http_method - when :get then Net::HTTP::Get - when :post then Net::HTTP::Post - when :put then Net::HTTP::Put - when :patch then Net::HTTP::Patch - else - raise ArgumentError, "Unsupported HTTP method: #{http_method}" - end - + klass = HTTP_METHODS.fetch(http_method) do + raise ArgumentError, "Unsupported HTTP method: #{http_method}" + end klass.new(uri.request_uri) end @@ -89,6 +89,10 @@ def get? http_method == :get end + def delete? + http_method == :delete + end + def post? http_method == :post end @@ -98,7 +102,7 @@ def empty_body? end def post_without_body_or_get? - get? || (post? && empty_body?) + get? || delete? || (post? && empty_body?) end def default_payload diff --git a/spec/console_spec.rb b/spec/console_spec.rb index 2d79017..1b685b1 100644 --- a/spec/console_spec.rb +++ b/spec/console_spec.rb @@ -658,4 +658,90 @@ expect(result.environment_identifier).to eq('env_abc') end end + + describe '#webhooks' do + let(:webhooks_service) { console.webhooks } + + describe '#create' do + let(:create_response) do + { + id: 'wh_123', + name: 'Production', + url: 'https://example.com/webhooks', + auth_method: 'bearer_token', + subscribed_events: ['ag.access_pass.issued'], + created_at: '2025-01-01T00:00:00Z', + private_key: 'pk_secret_123' + } + end + + it 'creates a webhook' do + stub_api_request( + :post, + '/v1/console/webhooks', + body: create_response, + request_body: { + name: 'Production', + url: 'https://example.com/webhooks', + subscribed_events: ['ag.access_pass.issued'], + auth_method: 'bearer_token' + } + ) + + webhook = webhooks_service.create( + name: 'Production', + url: 'https://example.com/webhooks', + subscribed_events: ['ag.access_pass.issued'] + ) + + expect(webhook).to be_a(AccessGrid::Webhook) + expect(webhook.id).to eq('wh_123') + expect(webhook.name).to eq('Production') + expect(webhook.private_key).to eq('pk_secret_123') + end + end + + describe '#list' do + let(:list_response) do + { + webhooks: [ + { id: 'wh_1', name: 'Prod', url: 'https://example.com/wh1', auth_method: 'bearer_token' }, + { id: 'wh_2', name: 'Staging', url: 'https://example.com/wh2', auth_method: 'bearer_token' } + ], + pagination: { current_page: 1, total_pages: 1 } + } + end + + it 'lists webhooks' do + stub_api_request( + :get, + '/v1/console/webhooks', + body: list_response, + query: generate_sig_payload(id: :webhooks) + ) + + webhooks = webhooks_service.list + + expect(webhooks).to be_an(Array) + expect(webhooks.length).to eq(2) + expect(webhooks.first).to be_a(AccessGrid::Webhook) + expect(webhooks.first.id).to eq('wh_1') + end + end + + describe '#delete' do + it 'deletes a webhook' do + stub_api_request( + :delete, + '/v1/console/webhooks/wh_123', + status: 204, + body: {}, + query: generate_sig_payload(id: :wh_123) + ) + + result = webhooks_service.delete('wh_123') + expect(result).to eq({}) + end + end + end end diff --git a/spec/request_spec.rb b/spec/request_spec.rb index f88eb45..46efb4c 100644 --- a/spec/request_spec.rb +++ b/spec/request_spec.rb @@ -258,8 +258,13 @@ def build_request(overrides = {}) expect(request.net_http_request).to be_a(Net::HTTP::Patch) end - it 'raises ArgumentError for unsupported HTTP method' do + it 'returns Net::HTTP::Delete for :delete method' do request = build_request(http_method: :delete) + expect(request.net_http_request).to be_a(Net::HTTP::Delete) + end + + it 'raises ArgumentError for unsupported HTTP method' do + request = build_request(http_method: :options) expect { request.net_http_request }.to raise_error(ArgumentError, /Unsupported HTTP method/) end