accessgrid 0.2.0 → 0.4.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 +4 -4
- data/README.md +255 -24
- data/lib/accessgrid/access_cards.rb +28 -17
- data/lib/accessgrid/console.rb +307 -23
- data/lib/accessgrid/error.rb +18 -0
- data/lib/accessgrid/request.rb +112 -0
- data/lib/accessgrid/version.rb +4 -2
- data/lib/accessgrid.rb +64 -136
- metadata +11 -51
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 05f4bc4f464d21efa501041653ae1252c64f6d5cca1a29b30394b51ea0cdd486
|
|
4
|
+
data.tar.gz: e26bba2fb9ccb37ecfaa5f32676453c9b5a5369b49d11f6cc2385a26345eead6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 181d498ca94e2514ecf4c5296acf2b4cb5d975efbd77cfa0915280dcc610b7860740cb58ad1d3909f5ca137a30ca610347e10de6d85608c72fbe022e31923eb4
|
|
7
|
+
data.tar.gz: d9ccd5c9bf41ba8f68415ea1ad9f72b68b48fa2799f76ce53726cd3fca93466a56e38fe6320b907c57cb67dbb7f8aac06674cd7cba7ac97a31ab44fb32d6ea11
|
data/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# AccessGrid
|
|
1
|
+
# 
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
AccessGrid is a Ruby SDK for interacting with the [AccessGrid.com](https://www.accessgrid.com) API. This SDK provides a simple interface for managing NFC key cards and enterprise templates. Full docs at https://www.accessgrid.com/docs
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -43,15 +43,25 @@ client = AccessGrid.new(account_id, secret_key)
|
|
|
43
43
|
card = client.access_cards.issue(
|
|
44
44
|
card_template_id: card_template_id,
|
|
45
45
|
employee_id: "123456789",
|
|
46
|
-
card_number: "16187",
|
|
47
46
|
tag_id: "DDEADB33FB00B5",
|
|
48
47
|
full_name: "Employee name",
|
|
49
48
|
email: "employee@yourwebsite.com",
|
|
50
49
|
phone_number: "+19547212241",
|
|
51
50
|
classification: "full_time",
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
51
|
+
department: "Engineering",
|
|
52
|
+
location: "San Francisco",
|
|
53
|
+
site_name: "HQ Building A",
|
|
54
|
+
workstation: "4F-207",
|
|
55
|
+
mail_stop: "MS-401",
|
|
56
|
+
company_address: "123 Main St, San Francisco, CA 94105",
|
|
57
|
+
start_date: Time.now.utc.iso8601(3),
|
|
58
|
+
expiration_date: 3.months.from_now.utc.iso8601(3),
|
|
59
|
+
employee_photo: "[image_in_base64_encoded_format]",
|
|
60
|
+
title: "Engineering Manager",
|
|
61
|
+
metadata: {
|
|
62
|
+
"department": "engineering",
|
|
63
|
+
"badge_type": "contractor"
|
|
64
|
+
}
|
|
55
65
|
)
|
|
56
66
|
|
|
57
67
|
# Provision is an alias for issue (for backwards compatibility)
|
|
@@ -127,27 +137,24 @@ client.access_cards.delete("0xc4rd1d")
|
|
|
127
137
|
|
|
128
138
|
```ruby
|
|
129
139
|
template = client.console.create_template(
|
|
130
|
-
name: "Employee
|
|
140
|
+
name: "Employee Access Pass",
|
|
131
141
|
platform: "apple",
|
|
132
142
|
use_case: "employee_badge",
|
|
133
143
|
protocol: "desfire",
|
|
134
144
|
allow_on_multiple_devices: true,
|
|
135
145
|
watch_count: 2,
|
|
136
146
|
iphone_count: 3,
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
support_email: "support@yourcompany.com",
|
|
149
|
-
privacy_policy_url: "https://yourcompany.com/privacy",
|
|
150
|
-
terms_and_conditions_url: "https://yourcompany.com/terms"
|
|
147
|
+
background_color: "#FFFFFF",
|
|
148
|
+
label_color: "#000000",
|
|
149
|
+
label_secondary_color: "#333333",
|
|
150
|
+
support_url: "https://help.yourcompany.com",
|
|
151
|
+
support_phone_number: "+1-555-123-4567",
|
|
152
|
+
support_email: "support@yourcompany.com",
|
|
153
|
+
privacy_policy_url: "https://yourcompany.com/privacy",
|
|
154
|
+
terms_and_conditions_url: "https://yourcompany.com/terms",
|
|
155
|
+
metadata: {
|
|
156
|
+
version: "2.1",
|
|
157
|
+
approval_status: "approved"
|
|
151
158
|
}
|
|
152
159
|
)
|
|
153
160
|
```
|
|
@@ -159,7 +166,6 @@ template = client.console.update_template(
|
|
|
159
166
|
"0xd3adb00b5",
|
|
160
167
|
{
|
|
161
168
|
name: "Updated Employee NFC key",
|
|
162
|
-
allow_on_multiple_devices: true,
|
|
163
169
|
watch_count: 2,
|
|
164
170
|
iphone_count: 3,
|
|
165
171
|
support_info: {
|
|
@@ -205,6 +211,199 @@ events = client.console.event_log(
|
|
|
205
211
|
)
|
|
206
212
|
```
|
|
207
213
|
|
|
214
|
+
#### List pass template pairs
|
|
215
|
+
|
|
216
|
+
```ruby
|
|
217
|
+
response = client.console.list_pass_template_pairs(page: 1, per_page: 20)
|
|
218
|
+
|
|
219
|
+
response['card_template_pairs'].each do |pair|
|
|
220
|
+
puts "#{pair.name} (#{pair.id})"
|
|
221
|
+
puts " iOS: #{pair.ios_template&.name}"
|
|
222
|
+
puts " Android: #{pair.android_template&.name}"
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
puts response['pagination'] # { "current_page" => 1, "total_pages" => 5, ... }
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
#### List ledger items
|
|
229
|
+
|
|
230
|
+
```ruby
|
|
231
|
+
response = client.console.list_ledger_items(
|
|
232
|
+
page: 1,
|
|
233
|
+
per_page: 50,
|
|
234
|
+
start_date: '2025-01-01T00:00:00Z',
|
|
235
|
+
end_date: '2025-06-30T23:59:59Z'
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
response['ledger_items'].each do |item|
|
|
239
|
+
puts "#{item.kind}: #{item.amount} (#{item.created_at})"
|
|
240
|
+
|
|
241
|
+
if item.access_pass
|
|
242
|
+
puts " Pass: #{item.access_pass.full_name} (#{item.access_pass.state})"
|
|
243
|
+
if item.access_pass.pass_template
|
|
244
|
+
puts " Template: #{item.access_pass.pass_template.name}"
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
puts response['pagination'] # { "current_page" => 1, "total_pages" => 3, ... }
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
#### iOS In-App Provisioning Preflight
|
|
253
|
+
|
|
254
|
+
```ruby
|
|
255
|
+
response = client.console.ios_preflight(
|
|
256
|
+
card_template_id: "0xt3mp14t3-3x1d",
|
|
257
|
+
access_pass_ex_id: "0xp455-3x1d"
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
puts "Provisioning Credential ID: #{response.provisioning_credential_identifier}"
|
|
261
|
+
puts "Sharing Instance ID: #{response.sharing_instance_identifier}"
|
|
262
|
+
puts "Card Template ID: #{response.card_template_identifier}"
|
|
263
|
+
puts "Environment ID: #{response.environment_identifier}"
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### Webhooks
|
|
267
|
+
|
|
268
|
+
#### Create a webhook
|
|
269
|
+
|
|
270
|
+
```ruby
|
|
271
|
+
webhook = client.console.webhooks.create(
|
|
272
|
+
name: 'Production',
|
|
273
|
+
url: 'https://example.com/webhooks',
|
|
274
|
+
subscribed_events: ['ag.access_pass.issued']
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
puts "Webhook created: #{webhook.id}"
|
|
278
|
+
puts "Private key: #{webhook.private_key}"
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
#### List webhooks
|
|
282
|
+
|
|
283
|
+
```ruby
|
|
284
|
+
webhooks = client.console.webhooks.list
|
|
285
|
+
|
|
286
|
+
webhooks.each do |webhook|
|
|
287
|
+
puts "ID: #{webhook.id}, Name: #{webhook.name}"
|
|
288
|
+
end
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
#### Delete a webhook
|
|
292
|
+
|
|
293
|
+
```ruby
|
|
294
|
+
client.console.webhooks.delete('abc123')
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### HID Organizations
|
|
298
|
+
|
|
299
|
+
#### Create an HID org
|
|
300
|
+
|
|
301
|
+
```ruby
|
|
302
|
+
org = client.console.hid.orgs.create(
|
|
303
|
+
name: 'My Org',
|
|
304
|
+
full_address: '1 Main St, NY NY',
|
|
305
|
+
phone: '+1-555-0000',
|
|
306
|
+
first_name: 'Ada',
|
|
307
|
+
last_name: 'Lovelace'
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
puts "Created org: #{org.name} (ID: #{org.id})"
|
|
311
|
+
puts "Slug: #{org.slug}"
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
#### List HID orgs
|
|
315
|
+
|
|
316
|
+
```ruby
|
|
317
|
+
orgs = client.console.hid.orgs.list
|
|
318
|
+
|
|
319
|
+
orgs.each do |org|
|
|
320
|
+
puts "Org ID: #{org.id}, Name: #{org.name}, Slug: #{org.slug}"
|
|
321
|
+
end
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
#### Activate an HID org
|
|
325
|
+
|
|
326
|
+
```ruby
|
|
327
|
+
result = client.console.hid.orgs.activate(
|
|
328
|
+
email: 'admin@example.com',
|
|
329
|
+
password: 'hid-password-123'
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
puts "Completed registration for org: #{result.name}"
|
|
333
|
+
puts "Status: #{result.status}"
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
### Landing Pages
|
|
337
|
+
|
|
338
|
+
#### List landing pages
|
|
339
|
+
|
|
340
|
+
```ruby
|
|
341
|
+
landing_pages = client.console.list_landing_pages
|
|
342
|
+
|
|
343
|
+
landing_pages.each do |page|
|
|
344
|
+
puts "ID: #{page.id}, Name: #{page.name}, Kind: #{page.kind}"
|
|
345
|
+
puts " Password Protected: #{page.password_protected}"
|
|
346
|
+
puts " Logo URL: #{page.logo_url}" if page.logo_url
|
|
347
|
+
end
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
#### Create a landing page
|
|
351
|
+
|
|
352
|
+
```ruby
|
|
353
|
+
landing_page = client.console.create_landing_page(
|
|
354
|
+
name: "Miami Office Access Pass",
|
|
355
|
+
kind: "universal",
|
|
356
|
+
additional_text: "Welcome to the Miami Office",
|
|
357
|
+
bg_color: "#f1f5f9",
|
|
358
|
+
allow_immediate_download: true
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
puts "Landing page created: #{landing_page.id}"
|
|
362
|
+
puts "Name: #{landing_page.name}, Kind: #{landing_page.kind}"
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
#### Update a landing page
|
|
366
|
+
|
|
367
|
+
```ruby
|
|
368
|
+
landing_page = client.console.update_landing_page(
|
|
369
|
+
landing_page_id: "0xlandingpage1d",
|
|
370
|
+
name: "Updated Miami Office Access Pass",
|
|
371
|
+
additional_text: "Welcome! Tap below to get your access pass.",
|
|
372
|
+
bg_color: "#e2e8f0"
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
puts "Landing page updated: #{landing_page.id}"
|
|
376
|
+
puts "Name: #{landing_page.name}"
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
### Credential Profiles
|
|
380
|
+
|
|
381
|
+
#### List credential profiles
|
|
382
|
+
|
|
383
|
+
```ruby
|
|
384
|
+
profiles = client.console.credential_profiles.list
|
|
385
|
+
|
|
386
|
+
profiles.each do |profile|
|
|
387
|
+
puts "ID: #{profile.id}, Name: #{profile.name}, AID: #{profile.aid}"
|
|
388
|
+
end
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
#### Create a credential profile
|
|
392
|
+
|
|
393
|
+
```ruby
|
|
394
|
+
profile = client.console.credential_profiles.create(
|
|
395
|
+
name: 'Main Office Profile',
|
|
396
|
+
app_name: 'KEY-ID-main',
|
|
397
|
+
keys: [
|
|
398
|
+
{ value: 'your_32_char_hex_master_key_here' },
|
|
399
|
+
{ value: 'your_32_char_hex__read_key__here' }
|
|
400
|
+
]
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
puts "Profile created: #{profile.id}"
|
|
404
|
+
puts "AID: #{profile.aid}"
|
|
405
|
+
```
|
|
406
|
+
|
|
208
407
|
## Configuration
|
|
209
408
|
|
|
210
409
|
The SDK can be configured with a custom API endpoint:
|
|
@@ -242,7 +441,7 @@ end
|
|
|
242
441
|
|
|
243
442
|
## Requirements
|
|
244
443
|
|
|
245
|
-
- Ruby 2.
|
|
444
|
+
- Ruby 2.19 or higher
|
|
246
445
|
|
|
247
446
|
## Security
|
|
248
447
|
|
|
@@ -262,6 +461,38 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
|
|
262
461
|
|
|
263
462
|
Bug reports and pull requests are welcome on GitHub at https://github.com/access-grid/accessgrid-rb.
|
|
264
463
|
|
|
464
|
+
## Feature Matrix
|
|
465
|
+
|
|
466
|
+
| Endpoint | Method | Supported |
|
|
467
|
+
|---|---|:---:|
|
|
468
|
+
| POST /v1/key-cards | `access_cards.issue()` | Y |
|
|
469
|
+
| GET /v1/key-cards/{id} | `access_cards.get()` | Y |
|
|
470
|
+
| PATCH /v1/key-cards/{id} | `access_cards.update()` | Y |
|
|
471
|
+
| GET /v1/key-cards | `access_cards.list()` | Y |
|
|
472
|
+
| POST /v1/key-cards/{id}/suspend | `access_cards.suspend()` | Y |
|
|
473
|
+
| POST /v1/key-cards/{id}/resume | `access_cards.resume()` | Y |
|
|
474
|
+
| POST /v1/key-cards/{id}/unlink | `access_cards.unlink()` | Y |
|
|
475
|
+
| POST /v1/key-cards/{id}/delete | `access_cards.delete()` | Y |
|
|
476
|
+
| POST /v1/console/card-templates | `console.create_template()` | Y |
|
|
477
|
+
| PUT /v1/console/card-templates/{id} | `console.update_template()` | Y |
|
|
478
|
+
| GET /v1/console/card-templates/{id} | `console.read_template()` | Y |
|
|
479
|
+
| GET /v1/console/card-templates/{id}/logs | `console.get_logs()` / `console.event_log()` | Y |
|
|
480
|
+
| GET /v1/console/card-template-pairs | `console.list_pass_template_pairs()` | Y |
|
|
481
|
+
| POST /v1/console/card-template-pairs | `console.create_pass_template_pair()` | Y |
|
|
482
|
+
| POST /v1/console/card-templates/{id}/ios_preflight | `console.ios_preflight()` | Y |
|
|
483
|
+
| GET /v1/console/ledger-items | `console.list_ledger_items()` / `console.ledger_items()` | Y |
|
|
484
|
+
| GET /v1/console/webhooks | `console.webhooks.list()` | Y |
|
|
485
|
+
| POST /v1/console/webhooks | `console.webhooks.create()` | Y |
|
|
486
|
+
| DELETE /v1/console/webhooks/{id} | `console.webhooks.delete()` | Y |
|
|
487
|
+
| GET /v1/console/landing-pages | `console.list_landing_pages()` | Y |
|
|
488
|
+
| POST /v1/console/landing-pages | `console.create_landing_page()` | Y |
|
|
489
|
+
| PUT /v1/console/landing-pages/{id} | `console.update_landing_page()` | Y |
|
|
490
|
+
| GET /v1/console/credential-profiles | `console.credential_profiles.list()` | Y |
|
|
491
|
+
| POST /v1/console/credential-profiles | `console.credential_profiles.create()` | Y |
|
|
492
|
+
| POST /v1/console/hid/orgs | `console.hid.orgs.create()` | Y |
|
|
493
|
+
| POST /v1/console/hid/orgs/activate | `console.hid.orgs.activate()` | Y |
|
|
494
|
+
| GET /v1/console/hid/orgs | `console.hid.orgs.list()` | Y |
|
|
495
|
+
|
|
265
496
|
## License
|
|
266
497
|
|
|
267
|
-
The gem is available as open source under the terms of the MIT License.
|
|
498
|
+
The gem is available as open source under the terms of the MIT License.
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
# lib/accessgrid/access_cards.rb
|
|
2
4
|
module AccessGrid
|
|
5
|
+
# Manages NFC key card lifecycle operations.
|
|
3
6
|
class AccessCards
|
|
4
7
|
def initialize(client)
|
|
5
8
|
@client = client
|
|
@@ -9,7 +12,7 @@ module AccessGrid
|
|
|
9
12
|
response = @client.make_request(:post, '/v1/key-cards', params)
|
|
10
13
|
Card.new(response)
|
|
11
14
|
end
|
|
12
|
-
|
|
15
|
+
|
|
13
16
|
# Alias provision to issue for backward compatibility
|
|
14
17
|
alias provision issue
|
|
15
18
|
|
|
@@ -22,24 +25,15 @@ module AccessGrid
|
|
|
22
25
|
response = @client.make_request(:patch, "/v1/key-cards/#{card_id}", params)
|
|
23
26
|
Card.new(response)
|
|
24
27
|
end
|
|
25
|
-
|
|
28
|
+
|
|
26
29
|
def list(template_id, state = nil)
|
|
27
30
|
params = { template_id: template_id }
|
|
28
31
|
params[:state] = state if state
|
|
29
|
-
|
|
32
|
+
|
|
30
33
|
response = @client.make_request(:get, '/v1/key-cards', nil, params)
|
|
31
34
|
response.fetch('keys', []).map { |item| Card.new(item) }
|
|
32
35
|
end
|
|
33
36
|
|
|
34
|
-
private def manage_state(card_id, action)
|
|
35
|
-
response = @client.make_request(
|
|
36
|
-
:post,
|
|
37
|
-
"/v1/key-cards/#{card_id}/#{action}",
|
|
38
|
-
{}
|
|
39
|
-
)
|
|
40
|
-
Card.new(response)
|
|
41
|
-
end
|
|
42
|
-
|
|
43
37
|
def suspend(card_id)
|
|
44
38
|
manage_state(card_id, 'suspend')
|
|
45
39
|
end
|
|
@@ -51,23 +45,36 @@ module AccessGrid
|
|
|
51
45
|
def unlink(card_id)
|
|
52
46
|
manage_state(card_id, 'unlink')
|
|
53
47
|
end
|
|
54
|
-
|
|
48
|
+
|
|
55
49
|
def delete(card_id)
|
|
56
50
|
manage_state(card_id, 'delete')
|
|
57
51
|
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def manage_state(card_id, action)
|
|
56
|
+
response = @client.make_request(
|
|
57
|
+
:post,
|
|
58
|
+
"/v1/key-cards/#{card_id}/#{action}"
|
|
59
|
+
)
|
|
60
|
+
Card.new(response)
|
|
61
|
+
end
|
|
58
62
|
end
|
|
59
63
|
|
|
64
|
+
# Represents an NFC key card with its attributes and state.
|
|
60
65
|
class Card
|
|
61
66
|
attr_reader :id, :state, :url, :install_url, :details, :full_name,
|
|
62
67
|
:expiration_date, :card_template_id, :card_number, :site_code,
|
|
63
|
-
:file_data, :direct_install_url, :devices, :metadata
|
|
68
|
+
:file_data, :direct_install_url, :devices, :metadata, :temporary,
|
|
69
|
+
:employee_id, :organization_name, :created_at
|
|
64
70
|
|
|
65
71
|
def initialize(data)
|
|
66
72
|
data ||= {}
|
|
73
|
+
install_url = data.fetch('install_url', nil)
|
|
67
74
|
@id = data.fetch('id', nil)
|
|
68
75
|
@state = data.fetch('state', nil)
|
|
69
|
-
@url =
|
|
70
|
-
@install_url =
|
|
76
|
+
@url = install_url
|
|
77
|
+
@install_url = install_url
|
|
71
78
|
@details = data.fetch('details', nil)
|
|
72
79
|
@full_name = data.fetch('full_name', nil)
|
|
73
80
|
@expiration_date = data.fetch('expiration_date', nil)
|
|
@@ -78,6 +85,10 @@ module AccessGrid
|
|
|
78
85
|
@direct_install_url = data.fetch('direct_install_url', nil)
|
|
79
86
|
@devices = data.fetch('devices', [])
|
|
80
87
|
@metadata = data.fetch('metadata', {})
|
|
88
|
+
@temporary = data.fetch('temporary', nil)
|
|
89
|
+
@employee_id = data.fetch('employee_id', nil)
|
|
90
|
+
@organization_name = data.fetch('organization_name', nil)
|
|
91
|
+
@created_at = data.fetch('created_at', nil)
|
|
81
92
|
end
|
|
82
93
|
|
|
83
94
|
def to_s
|
|
@@ -86,4 +97,4 @@ module AccessGrid
|
|
|
86
97
|
|
|
87
98
|
alias inspect to_s
|
|
88
99
|
end
|
|
89
|
-
end
|
|
100
|
+
end
|
data/lib/accessgrid/console.rb
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
# lib/accessgrid/console.rb
|
|
2
4
|
module AccessGrid
|
|
5
|
+
# Manages enterprise template and logging operations.
|
|
3
6
|
class Console
|
|
7
|
+
attr_reader :webhooks, :hid, :credential_profiles
|
|
8
|
+
|
|
4
9
|
def initialize(client)
|
|
5
10
|
@client = client
|
|
11
|
+
@webhooks = Webhooks.new(client)
|
|
12
|
+
@hid = HID.new(client)
|
|
13
|
+
@credential_profiles = CredentialProfiles.new(client)
|
|
6
14
|
end
|
|
7
15
|
|
|
8
16
|
def create_template(params)
|
|
@@ -24,15 +32,13 @@ module AccessGrid
|
|
|
24
32
|
|
|
25
33
|
def get_logs(template_id, params = {})
|
|
26
34
|
response = @client.make_request(:get, "/v1/console/card-templates/#{template_id}/logs", nil, params)
|
|
27
|
-
|
|
35
|
+
|
|
28
36
|
# Return full response to match Python's behavior
|
|
29
|
-
if response['logs']
|
|
30
|
-
|
|
31
|
-
end
|
|
32
|
-
|
|
37
|
+
response['logs'] = response['logs'].map { |log| Event.new(log) } if response['logs']
|
|
38
|
+
|
|
33
39
|
response
|
|
34
40
|
end
|
|
35
|
-
|
|
41
|
+
|
|
36
42
|
# Keep event_log for backwards compatibility
|
|
37
43
|
def event_log(params)
|
|
38
44
|
template_id = params.delete(:card_template_id)
|
|
@@ -40,29 +46,75 @@ module AccessGrid
|
|
|
40
46
|
response['logs'] || []
|
|
41
47
|
end
|
|
42
48
|
|
|
49
|
+
def list_pass_template_pairs(params = {})
|
|
50
|
+
response = @client.make_request(:get, '/v1/console/card-template-pairs', nil, params)
|
|
51
|
+
|
|
52
|
+
if response['card_template_pairs']
|
|
53
|
+
response['card_template_pairs'] = response['card_template_pairs'].map { |pair| PassTemplatePair.new(pair) }
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
response
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def create_pass_template_pair(params)
|
|
60
|
+
response = @client.make_request(:post, '/v1/console/card-template-pairs', params)
|
|
61
|
+
PassTemplatePair.new(response)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def list_ledger_items(params = {})
|
|
65
|
+
response = @client.make_request(:get, '/v1/console/ledger-items', nil, params)
|
|
66
|
+
|
|
67
|
+
if response['ledger_items']
|
|
68
|
+
response['ledger_items'] = response['ledger_items'].map { |item| LedgerItem.new(item) }
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
response
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
alias ledger_items list_ledger_items
|
|
75
|
+
|
|
76
|
+
def ios_preflight(card_template_id:, access_pass_ex_id:)
|
|
77
|
+
data = { access_pass_ex_id: access_pass_ex_id }
|
|
78
|
+
response = @client.make_request(:post, "/v1/console/card-templates/#{card_template_id}/ios_preflight", data)
|
|
79
|
+
IosPreflight.new(response)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def list_landing_pages
|
|
83
|
+
response = @client.make_request(:get, '/v1/console/landing-pages')
|
|
84
|
+
pages = response.is_a?(Array) ? response : response.fetch('landing_pages', [])
|
|
85
|
+
pages.map { |page| LandingPage.new(page) }
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def create_landing_page(**params)
|
|
89
|
+
response = @client.make_request(:post, '/v1/console/landing-pages', params)
|
|
90
|
+
LandingPage.new(response)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def update_landing_page(landing_page_id:, **params)
|
|
94
|
+
response = @client.make_request(:put, "/v1/console/landing-pages/#{landing_page_id}", params)
|
|
95
|
+
LandingPage.new(response)
|
|
96
|
+
end
|
|
97
|
+
|
|
43
98
|
private
|
|
44
99
|
|
|
45
100
|
def transform_template_params(params)
|
|
46
|
-
design = params.delete(:design)
|
|
47
|
-
support_info = params.delete(:support_info)
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
support_phone_number: support_info[:support_phone_number],
|
|
55
|
-
support_email: support_info[:support_email],
|
|
56
|
-
privacy_policy_url: support_info[:privacy_policy_url],
|
|
57
|
-
terms_and_conditions_url: support_info[:terms_and_conditions_url]
|
|
58
|
-
)
|
|
101
|
+
design = params.delete(:design)
|
|
102
|
+
support_info = params.delete(:support_info)
|
|
103
|
+
|
|
104
|
+
# Only merge nested keys if they were provided (backward compat)
|
|
105
|
+
params.merge!(design) if design
|
|
106
|
+
params.merge!(support_info) if support_info
|
|
107
|
+
|
|
108
|
+
params
|
|
59
109
|
end
|
|
60
110
|
end
|
|
61
111
|
|
|
112
|
+
# Represents a card template configuration.
|
|
62
113
|
class Template
|
|
63
|
-
attr_reader :id, :name, :platform, :protocol, :use_case, :created_at,
|
|
114
|
+
attr_reader :id, :name, :platform, :protocol, :use_case, :created_at,
|
|
64
115
|
:last_published_at, :issued_keys_count, :active_keys_count,
|
|
65
|
-
:allowed_device_counts, :support_settings, :terms_settings, :style_settings
|
|
116
|
+
:allowed_device_counts, :support_settings, :terms_settings, :style_settings,
|
|
117
|
+
:metadata
|
|
66
118
|
|
|
67
119
|
def initialize(data)
|
|
68
120
|
@id = data['id']
|
|
@@ -78,19 +130,251 @@ module AccessGrid
|
|
|
78
130
|
@support_settings = data['support_settings']
|
|
79
131
|
@terms_settings = data['terms_settings']
|
|
80
132
|
@style_settings = data['style_settings']
|
|
133
|
+
@metadata = data['metadata'] || {}
|
|
81
134
|
end
|
|
82
135
|
end
|
|
83
136
|
|
|
137
|
+
# Represents a template activity log event.
|
|
84
138
|
class Event
|
|
85
139
|
attr_reader :type, :timestamp, :user_id, :ip_address, :user_agent, :metadata
|
|
86
140
|
|
|
87
141
|
def initialize(data)
|
|
142
|
+
metadata = data['metadata']
|
|
88
143
|
@type = data['event']
|
|
89
144
|
@timestamp = data['created_at']
|
|
90
|
-
@user_id =
|
|
145
|
+
@user_id = metadata['user_id'] if metadata && metadata['user_id']
|
|
91
146
|
@ip_address = data['ip_address']
|
|
92
147
|
@user_agent = data['user_agent']
|
|
148
|
+
@metadata = metadata
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Represents a paired iOS and Android template configuration.
|
|
153
|
+
class PassTemplatePair
|
|
154
|
+
attr_reader :id, :name, :created_at, :android_template, :ios_template
|
|
155
|
+
|
|
156
|
+
def initialize(data)
|
|
157
|
+
android_template = data['android_template']
|
|
158
|
+
ios_template = data['ios_template']
|
|
159
|
+
@id = data['id']
|
|
160
|
+
@name = data['name']
|
|
161
|
+
@created_at = data['created_at']
|
|
162
|
+
@android_template = android_template ? TemplateInfo.new(android_template) : nil
|
|
163
|
+
@ios_template = ios_template ? TemplateInfo.new(ios_template) : nil
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Minimal template info used within PassTemplatePair.
|
|
168
|
+
class TemplateInfo
|
|
169
|
+
attr_reader :id, :name, :platform
|
|
170
|
+
|
|
171
|
+
def initialize(data)
|
|
172
|
+
@id = data['id']
|
|
173
|
+
@name = data['name']
|
|
174
|
+
@platform = data['platform']
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Represents an iOS In-App Provisioning preflight response.
|
|
179
|
+
class IosPreflight
|
|
180
|
+
attr_reader :provisioning_credential_identifier, :sharing_instance_identifier,
|
|
181
|
+
:card_template_identifier, :environment_identifier
|
|
182
|
+
|
|
183
|
+
def initialize(data)
|
|
184
|
+
@provisioning_credential_identifier = data['provisioningCredentialIdentifier']
|
|
185
|
+
@sharing_instance_identifier = data['sharingInstanceIdentifier']
|
|
186
|
+
@card_template_identifier = data['cardTemplateIdentifier']
|
|
187
|
+
@environment_identifier = data['environmentIdentifier']
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Represents a billing ledger item.
|
|
192
|
+
class LedgerItem
|
|
193
|
+
attr_reader :created_at, :amount, :id, :kind, :metadata, :access_pass
|
|
194
|
+
|
|
195
|
+
def initialize(data)
|
|
196
|
+
@created_at = data['created_at']
|
|
197
|
+
@amount = data['amount']
|
|
198
|
+
@id = data['id']
|
|
199
|
+
@kind = data['kind']
|
|
200
|
+
@metadata = data['metadata']
|
|
201
|
+
@access_pass = data['access_pass'] ? LedgerItemAccessPass.new(data['access_pass']) : nil
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# Represents an access pass reference within a ledger item.
|
|
206
|
+
class LedgerItemAccessPass
|
|
207
|
+
attr_reader :id, :full_name, :state, :metadata, :unified_access_pass_ex_id, :pass_template
|
|
208
|
+
|
|
209
|
+
def initialize(data)
|
|
210
|
+
@id = data['id']
|
|
211
|
+
@full_name = data['full_name']
|
|
212
|
+
@state = data['state']
|
|
93
213
|
@metadata = data['metadata']
|
|
214
|
+
@unified_access_pass_ex_id = data['unified_access_pass_ex_id']
|
|
215
|
+
@pass_template = data['pass_template'] ? LedgerItemPassTemplate.new(data['pass_template']) : nil
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# Represents a pass template reference within a ledger item's access pass.
|
|
220
|
+
class LedgerItemPassTemplate
|
|
221
|
+
attr_reader :id, :name, :protocol, :platform, :use_case
|
|
222
|
+
|
|
223
|
+
def initialize(data)
|
|
224
|
+
@id = data['id']
|
|
225
|
+
@name = data['name']
|
|
226
|
+
@protocol = data['protocol']
|
|
227
|
+
@platform = data['platform']
|
|
228
|
+
@use_case = data['use_case']
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# Represents a landing page configuration.
|
|
233
|
+
class LandingPage
|
|
234
|
+
attr_reader :id, :name, :created_at, :kind, :password_protected, :logo_url
|
|
235
|
+
|
|
236
|
+
def initialize(data)
|
|
237
|
+
@id = data['id']
|
|
238
|
+
@name = data['name']
|
|
239
|
+
@created_at = data['created_at']
|
|
240
|
+
@kind = data['kind']
|
|
241
|
+
@password_protected = data['password_protected']
|
|
242
|
+
@logo_url = data['logo_url']
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
# Represents a credential profile configuration.
|
|
247
|
+
class CredentialProfile
|
|
248
|
+
attr_reader :id, :aid, :name, :apple_id, :created_at, :card_storage, :keys, :files
|
|
249
|
+
|
|
250
|
+
def initialize(data)
|
|
251
|
+
@id = data['id']
|
|
252
|
+
@aid = data['aid']
|
|
253
|
+
@name = data['name']
|
|
254
|
+
@apple_id = data['apple_id']
|
|
255
|
+
@created_at = data['created_at']
|
|
256
|
+
@card_storage = data['card_storage']
|
|
257
|
+
@keys = data['keys'] || []
|
|
258
|
+
@files = data['files'] || []
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
# Manages credential profile operations.
|
|
263
|
+
class CredentialProfiles
|
|
264
|
+
def initialize(client)
|
|
265
|
+
@client = client
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
def create(**params)
|
|
269
|
+
response = @client.make_request(:post, '/v1/console/credential-profiles', params)
|
|
270
|
+
CredentialProfile.new(response)
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def list
|
|
274
|
+
response = @client.make_request(:get, '/v1/console/credential-profiles')
|
|
275
|
+
profiles = response.is_a?(Array) ? response : response.fetch('credential_profiles', [])
|
|
276
|
+
profiles.map { |profile| CredentialProfile.new(profile) }
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
# Manages webhook operations.
|
|
281
|
+
class Webhooks
|
|
282
|
+
def initialize(client)
|
|
283
|
+
@client = client
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
def create(name:, url:, subscribed_events:, auth_method: 'bearer_token')
|
|
287
|
+
data = {
|
|
288
|
+
name: name,
|
|
289
|
+
url: url,
|
|
290
|
+
subscribed_events: subscribed_events,
|
|
291
|
+
auth_method: auth_method
|
|
292
|
+
}
|
|
293
|
+
response = @client.make_request(:post, '/v1/console/webhooks', data)
|
|
294
|
+
Webhook.new(response)
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
def list(**params)
|
|
298
|
+
response = @client.make_request(:get, '/v1/console/webhooks', nil, params)
|
|
299
|
+
(response['webhooks'] || []).map { |wh| Webhook.new(wh) }
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
def delete(webhook_id)
|
|
303
|
+
@client.make_request(:delete, "/v1/console/webhooks/#{webhook_id}")
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
# Represents a webhook configuration.
|
|
308
|
+
class Webhook
|
|
309
|
+
attr_reader :id, :name, :url, :auth_method, :subscribed_events,
|
|
310
|
+
:created_at, :private_key, :client_cert, :cert_expires_at
|
|
311
|
+
|
|
312
|
+
def initialize(data)
|
|
313
|
+
@id = data['id']
|
|
314
|
+
@name = data['name']
|
|
315
|
+
@url = data['url']
|
|
316
|
+
@auth_method = data['auth_method']
|
|
317
|
+
@subscribed_events = data['subscribed_events']
|
|
318
|
+
@created_at = data['created_at']
|
|
319
|
+
@private_key = data['private_key']
|
|
320
|
+
@client_cert = data['client_cert']
|
|
321
|
+
@cert_expires_at = data['cert_expires_at']
|
|
322
|
+
end
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
# Provides access to HID-related services.
|
|
326
|
+
class HID
|
|
327
|
+
attr_reader :orgs
|
|
328
|
+
|
|
329
|
+
def initialize(client)
|
|
330
|
+
@orgs = HIDOrgs.new(client)
|
|
331
|
+
end
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
# Manages HID organization operations.
|
|
335
|
+
class HIDOrgs
|
|
336
|
+
def initialize(client)
|
|
337
|
+
@client = client
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
def create(name:, full_address:, phone:, first_name:, last_name:)
|
|
341
|
+
data = {
|
|
342
|
+
name: name,
|
|
343
|
+
full_address: full_address,
|
|
344
|
+
phone: phone,
|
|
345
|
+
first_name: first_name,
|
|
346
|
+
last_name: last_name
|
|
347
|
+
}
|
|
348
|
+
response = @client.make_request(:post, '/v1/console/hid/orgs', data)
|
|
349
|
+
HidOrg.new(response)
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
def list
|
|
353
|
+
response = @client.make_request(:get, '/v1/console/hid/orgs')
|
|
354
|
+
response.map { |org| HidOrg.new(org) }
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
def activate(email:, password:)
|
|
358
|
+
data = { email: email, password: password }
|
|
359
|
+
response = @client.make_request(:post, '/v1/console/hid/orgs/activate', data)
|
|
360
|
+
HidOrg.new(response)
|
|
361
|
+
end
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
# Represents an HID organization.
|
|
365
|
+
class HidOrg
|
|
366
|
+
attr_reader :id, :name, :slug, :first_name, :last_name, :phone, :full_address, :status, :created_at
|
|
367
|
+
|
|
368
|
+
def initialize(data)
|
|
369
|
+
@id = data['id']
|
|
370
|
+
@name = data['name']
|
|
371
|
+
@slug = data['slug']
|
|
372
|
+
@first_name = data['first_name']
|
|
373
|
+
@last_name = data['last_name']
|
|
374
|
+
@phone = data['phone']
|
|
375
|
+
@full_address = data['full_address']
|
|
376
|
+
@status = data['status']
|
|
377
|
+
@created_at = data['created_at']
|
|
94
378
|
end
|
|
95
379
|
end
|
|
96
|
-
end
|
|
380
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AccessGrid
|
|
4
|
+
# base error
|
|
5
|
+
class Error < StandardError; end
|
|
6
|
+
|
|
7
|
+
# Raised when API credentials are invalid.
|
|
8
|
+
class AuthenticationError < Error; end
|
|
9
|
+
|
|
10
|
+
# Raised when a requested resource does not exist.
|
|
11
|
+
class ResourceNotFoundError < Error; end
|
|
12
|
+
|
|
13
|
+
# Raised when request parameters fail validation.
|
|
14
|
+
class ValidationError < Error; end
|
|
15
|
+
|
|
16
|
+
# additional error classes to match Python version
|
|
17
|
+
class AccessGridError < Error; end
|
|
18
|
+
end
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'uri'
|
|
4
|
+
|
|
5
|
+
module AccessGrid
|
|
6
|
+
# Builds and configures HTTP requests for the AccessGrid API.
|
|
7
|
+
class Request
|
|
8
|
+
PAYLOAD_SIGNATURE_PARAM = :sig_payload
|
|
9
|
+
HTTP_METHODS = {
|
|
10
|
+
get: Net::HTTP::Get,
|
|
11
|
+
post: Net::HTTP::Post,
|
|
12
|
+
put: Net::HTTP::Put,
|
|
13
|
+
patch: Net::HTTP::Patch,
|
|
14
|
+
delete: Net::HTTP::Delete
|
|
15
|
+
}.freeze
|
|
16
|
+
|
|
17
|
+
attr_reader :account_id, :body, :http_method, :params, :payload, :uri
|
|
18
|
+
|
|
19
|
+
def initialize(attrs)
|
|
20
|
+
# required attributes
|
|
21
|
+
@http_method = attrs.fetch(:http_method)
|
|
22
|
+
@provided_host = attrs.fetch(:host)
|
|
23
|
+
@provided_path = attrs.fetch(:path)
|
|
24
|
+
|
|
25
|
+
# optional attributes
|
|
26
|
+
@account_id = attrs.fetch(:account_id, nil)
|
|
27
|
+
@body = attrs.fetch(:body, nil)
|
|
28
|
+
@params = attrs.fetch(:params, nil) || {}
|
|
29
|
+
|
|
30
|
+
# computed attributes
|
|
31
|
+
initialize_payload
|
|
32
|
+
initialize_uri
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def net_http_request
|
|
36
|
+
return @net_http_request if defined?(@net_http_request)
|
|
37
|
+
|
|
38
|
+
@net_http_request = generate_net_http_request!.tap do |req|
|
|
39
|
+
req['Content-Type'] = 'application/json'
|
|
40
|
+
req['X-ACCT-ID'] = account_id
|
|
41
|
+
req['User-Agent'] = "accessgrid.rb @ v#{AccessGrid::VERSION}"
|
|
42
|
+
|
|
43
|
+
req.body = body.to_json if body && !get?
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def generate_net_http_request!
|
|
50
|
+
klass = HTTP_METHODS.fetch(http_method) do
|
|
51
|
+
raise ArgumentError, "Unsupported HTTP method: #{http_method}"
|
|
52
|
+
end
|
|
53
|
+
klass.new(uri.request_uri)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def initialize_payload
|
|
57
|
+
return @payload if defined?(@payload)
|
|
58
|
+
return @payload = default_payload unless post_without_body_or_get?
|
|
59
|
+
|
|
60
|
+
if resource_id
|
|
61
|
+
@payload = { id: resource_id }.to_json
|
|
62
|
+
@params[PAYLOAD_SIGNATURE_PARAM] = @payload
|
|
63
|
+
else
|
|
64
|
+
@payload = '{}'
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
@payload
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def initialize_uri
|
|
71
|
+
@uri = URI.parse("#{@provided_host}#{@provided_path}").tap do |parsed_uri|
|
|
72
|
+
parsed_uri.query = URI.encode_www_form(@params) if @params.any?
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def resource_id
|
|
77
|
+
return @resource_id if defined?(@resource_id)
|
|
78
|
+
return @resource_id = nil unless post_without_body_or_get?
|
|
79
|
+
|
|
80
|
+
parts = @provided_path.strip.split('/')
|
|
81
|
+
return @resource_id = nil unless parts.length >= 2
|
|
82
|
+
|
|
83
|
+
last_part = parts.last
|
|
84
|
+
second_to_last_part = parts[-2]
|
|
85
|
+
@resource_id = %w[suspend resume unlink delete].include?(last_part) ? second_to_last_part : last_part
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def get?
|
|
89
|
+
http_method == :get
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def delete?
|
|
93
|
+
http_method == :delete
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def post?
|
|
97
|
+
http_method == :post
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def empty_body?
|
|
101
|
+
body.nil? || body.empty?
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def post_without_body_or_get?
|
|
105
|
+
get? || delete? || (post? && empty_body?)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def default_payload
|
|
109
|
+
body&.to_json || ''
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
data/lib/accessgrid/version.rb
CHANGED
data/lib/accessgrid.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
# lib/accessgrid.rb
|
|
2
4
|
require 'base64'
|
|
3
5
|
require 'json'
|
|
@@ -5,171 +7,97 @@ require 'net/http'
|
|
|
5
7
|
require 'openssl'
|
|
6
8
|
require 'uri'
|
|
7
9
|
|
|
8
|
-
require_relative 'accessgrid/version'
|
|
9
10
|
require_relative 'accessgrid/access_cards'
|
|
10
11
|
require_relative 'accessgrid/console'
|
|
12
|
+
require_relative 'accessgrid/error'
|
|
13
|
+
require_relative 'accessgrid/request'
|
|
14
|
+
require_relative 'accessgrid/version'
|
|
11
15
|
|
|
16
|
+
# Ruby SDK for the AccessGrid API.
|
|
12
17
|
module AccessGrid
|
|
13
|
-
|
|
14
|
-
class AuthenticationError < Error; end
|
|
15
|
-
class ResourceNotFoundError < Error; end
|
|
16
|
-
class ValidationError < Error; end
|
|
17
|
-
|
|
18
|
-
# Additional error classes to match Python version
|
|
19
|
-
class AccessGridError < Error; end
|
|
20
|
-
|
|
18
|
+
# API client for AccessGrid key card and template management.
|
|
21
19
|
class Client
|
|
22
|
-
attr_reader :account_id, :api_secret, :api_host
|
|
23
|
-
|
|
20
|
+
attr_reader :access_cards, :account_id, :api_secret, :api_host, :console
|
|
21
|
+
|
|
24
22
|
def initialize(account_id, api_secret, api_host = 'https://api.accessgrid.com')
|
|
25
|
-
if account_id.nil? || account_id.empty?
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
if api_secret.nil? || api_secret.empty?
|
|
30
|
-
raise ArgumentError, "API Secret is required"
|
|
31
|
-
end
|
|
32
|
-
|
|
23
|
+
raise ArgumentError, 'Account ID is required' if account_id.nil? || account_id.empty?
|
|
24
|
+
raise ArgumentError, 'API Secret is required' if api_secret.nil? || api_secret.empty?
|
|
25
|
+
|
|
33
26
|
@account_id = account_id
|
|
34
27
|
@api_secret = api_secret
|
|
35
28
|
@api_host = api_host.chomp('/')
|
|
29
|
+
@access_cards = AccessCards.new(self)
|
|
30
|
+
@console = Console.new(self)
|
|
36
31
|
end
|
|
37
32
|
|
|
38
|
-
def
|
|
39
|
-
|
|
40
|
-
|
|
33
|
+
def make_request(method, path, body = nil, params = nil)
|
|
34
|
+
request = Request.new(
|
|
35
|
+
account_id: account_id,
|
|
36
|
+
body: body,
|
|
37
|
+
host: api_host,
|
|
38
|
+
http_method: method,
|
|
39
|
+
params: params,
|
|
40
|
+
path: path
|
|
41
|
+
)
|
|
41
42
|
|
|
42
|
-
|
|
43
|
-
@console ||= Console.new(self)
|
|
43
|
+
execute_and_process_request!(request)
|
|
44
44
|
end
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
#
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
http = Net::HTTP.new(uri.host, uri.port)
|
|
55
|
-
http.use_ssl = uri.scheme == 'https'
|
|
56
|
-
|
|
57
|
-
# Create request object based on method
|
|
58
|
-
request = case method
|
|
59
|
-
when :get
|
|
60
|
-
Net::HTTP::Get.new(uri.request_uri)
|
|
61
|
-
when :post
|
|
62
|
-
Net::HTTP::Post.new(uri.request_uri)
|
|
63
|
-
when :put
|
|
64
|
-
Net::HTTP::Put.new(uri.request_uri)
|
|
65
|
-
when :patch
|
|
66
|
-
Net::HTTP::Patch.new(uri.request_uri)
|
|
67
|
-
else
|
|
68
|
-
raise ArgumentError, "Unsupported HTTP method: #{method}"
|
|
69
|
-
end
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def execute_and_process_request!(request)
|
|
49
|
+
# set up net http
|
|
50
|
+
uri = request.uri
|
|
51
|
+
http = Net::HTTP.new(uri.host, uri.port).tap { |http| http.use_ssl = uri.scheme == 'https' }
|
|
70
52
|
|
|
71
|
-
# Set headers
|
|
72
|
-
request['Content-Type'] = 'application/json'
|
|
73
|
-
request['X-ACCT-ID'] = account_id
|
|
74
|
-
request['User-Agent'] = "accessgrid.rb @ v#{AccessGrid::VERSION}"
|
|
75
|
-
|
|
76
|
-
# Extract resource ID from the path if needed for signature
|
|
77
|
-
resource_id = nil
|
|
78
|
-
if method == :get || (method == :post && (body.nil? || body.empty?))
|
|
79
|
-
parts = path.strip.split('/')
|
|
80
|
-
if parts.length >= 2
|
|
81
|
-
if ['suspend', 'resume', 'unlink', 'delete'].include?(parts.last)
|
|
82
|
-
resource_id = parts[-2]
|
|
83
|
-
else
|
|
84
|
-
resource_id = parts.last
|
|
85
|
-
end
|
|
86
|
-
end
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
# Handle signature generation
|
|
90
|
-
if method == :get || (method == :post && (body.nil? || body.empty?))
|
|
91
|
-
payload = resource_id ? { id: resource_id }.to_json : '{}'
|
|
92
|
-
|
|
93
|
-
# Include sig_payload in query params if needed
|
|
94
|
-
if resource_id
|
|
95
|
-
if params.nil?
|
|
96
|
-
params = {}
|
|
97
|
-
end
|
|
98
|
-
params[:sig_payload] = { id: resource_id }.to_json
|
|
99
|
-
|
|
100
|
-
# Update the URI with the new params
|
|
101
|
-
uri.query = URI.encode_www_form(params)
|
|
102
|
-
request = case method
|
|
103
|
-
when :get
|
|
104
|
-
Net::HTTP::Get.new(uri.request_uri)
|
|
105
|
-
when :post
|
|
106
|
-
Net::HTTP::Post.new(uri.request_uri)
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
# Reset headers after creating new request
|
|
110
|
-
request['Content-Type'] = 'application/json'
|
|
111
|
-
request['X-ACCT-ID'] = account_id
|
|
112
|
-
request['User-Agent'] = "accessgrid.rb @ v#{AccessGrid::VERSION}"
|
|
113
|
-
end
|
|
114
|
-
else
|
|
115
|
-
payload = body ? body.to_json : ""
|
|
116
|
-
end
|
|
117
|
-
|
|
118
53
|
# Generate signature
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
# Add the body to the request
|
|
122
|
-
if body && method != :get
|
|
123
|
-
request.body = body.to_json
|
|
124
|
-
end
|
|
54
|
+
net_http_request = request.net_http_request
|
|
55
|
+
net_http_request['X-PAYLOAD-SIG'] = generate_signature_hash(request.payload)
|
|
125
56
|
|
|
126
57
|
# Make request
|
|
127
|
-
response = http.request(
|
|
128
|
-
|
|
58
|
+
response = http.request(net_http_request)
|
|
59
|
+
|
|
129
60
|
# Parse response
|
|
130
61
|
handle_response(response)
|
|
131
62
|
end
|
|
132
63
|
|
|
133
|
-
private
|
|
134
|
-
|
|
135
|
-
def generate_signature(payload)
|
|
136
|
-
# Base64 encode the payload
|
|
137
|
-
encoded_payload = Base64.strict_encode64(payload.to_s)
|
|
138
|
-
|
|
139
|
-
# Generate SHA256 hash
|
|
140
|
-
OpenSSL::HMAC.hexdigest(
|
|
141
|
-
OpenSSL::Digest.new('sha256'),
|
|
142
|
-
api_secret,
|
|
143
|
-
encoded_payload
|
|
144
|
-
)
|
|
145
|
-
end
|
|
146
|
-
|
|
147
64
|
def handle_response(response)
|
|
148
65
|
case response.code.to_i
|
|
149
|
-
when
|
|
150
|
-
|
|
151
|
-
when 401
|
|
152
|
-
|
|
153
|
-
when
|
|
154
|
-
|
|
155
|
-
when 404
|
|
156
|
-
raise ResourceNotFoundError, 'Resource not found'
|
|
157
|
-
when 422
|
|
158
|
-
raise ValidationError, JSON.parse(response.body)['message']
|
|
66
|
+
when 204 then {}
|
|
67
|
+
when 200, 201, 202 then JSON.parse(response.body)
|
|
68
|
+
when 401 then raise AuthenticationError, 'Invalid credentials'
|
|
69
|
+
when 402 then raise Error, 'Insufficient account balance'
|
|
70
|
+
when 404 then raise ResourceNotFoundError, 'Resource not found'
|
|
71
|
+
when 422 then raise ValidationError, JSON.parse(response.body)['message']
|
|
159
72
|
else
|
|
160
|
-
|
|
161
|
-
begin
|
|
162
|
-
error_data = JSON.parse(response.body)
|
|
163
|
-
error_message = error_data['message'] if error_data['message']
|
|
164
|
-
rescue JSON::ParserError
|
|
165
|
-
# If it's not valid JSON, just use the response body
|
|
166
|
-
end
|
|
167
|
-
raise Error, "API request failed: #{error_message}"
|
|
73
|
+
process_unhandled_response(response)
|
|
168
74
|
end
|
|
169
75
|
end
|
|
76
|
+
|
|
77
|
+
def process_unhandled_response(response)
|
|
78
|
+
body = response.body
|
|
79
|
+
error_message = body.empty? ? "HTTP Status #{response.code}" : body
|
|
80
|
+
|
|
81
|
+
begin
|
|
82
|
+
error_data = JSON.parse(body)
|
|
83
|
+
error_message = error_data['message'] if error_data['message']
|
|
84
|
+
rescue JSON::ParserError
|
|
85
|
+
# If it's not valid JSON, just use the response body
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
raise Error, "API request failed: #{error_message}"
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def generate_signature_hash(value)
|
|
92
|
+
# Base64 encode the value
|
|
93
|
+
encoded_value = Base64.strict_encode64(value.to_s)
|
|
94
|
+
|
|
95
|
+
# Generate SHA256 hash
|
|
96
|
+
OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), api_secret, encoded_value)
|
|
97
|
+
end
|
|
170
98
|
end
|
|
171
99
|
|
|
172
100
|
def self.new(account_id, api_secret, api_host = 'https://api.accessgrid.com')
|
|
173
101
|
Client.new(account_id, api_secret, api_host)
|
|
174
102
|
end
|
|
175
|
-
end
|
|
103
|
+
end
|
metadata
CHANGED
|
@@ -1,71 +1,29 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: accessgrid
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Auston Bunsen
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-04-16 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
|
-
name:
|
|
14
|
+
name: base64
|
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
|
16
16
|
requirements:
|
|
17
|
-
- - "
|
|
17
|
+
- - ">="
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: '
|
|
20
|
-
type: :
|
|
19
|
+
version: '0'
|
|
20
|
+
type: :runtime
|
|
21
21
|
prerelease: false
|
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
23
|
requirements:
|
|
24
|
-
- - "
|
|
24
|
+
- - ">="
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
|
-
version: '
|
|
27
|
-
- !ruby/object:Gem::Dependency
|
|
28
|
-
name: rake
|
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
|
30
|
-
requirements:
|
|
31
|
-
- - "~>"
|
|
32
|
-
- !ruby/object:Gem::Version
|
|
33
|
-
version: '13.0'
|
|
34
|
-
type: :development
|
|
35
|
-
prerelease: false
|
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
-
requirements:
|
|
38
|
-
- - "~>"
|
|
39
|
-
- !ruby/object:Gem::Version
|
|
40
|
-
version: '13.0'
|
|
41
|
-
- !ruby/object:Gem::Dependency
|
|
42
|
-
name: rspec
|
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
|
44
|
-
requirements:
|
|
45
|
-
- - "~>"
|
|
46
|
-
- !ruby/object:Gem::Version
|
|
47
|
-
version: '3.0'
|
|
48
|
-
type: :development
|
|
49
|
-
prerelease: false
|
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
-
requirements:
|
|
52
|
-
- - "~>"
|
|
53
|
-
- !ruby/object:Gem::Version
|
|
54
|
-
version: '3.0'
|
|
55
|
-
- !ruby/object:Gem::Dependency
|
|
56
|
-
name: webmock
|
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
|
58
|
-
requirements:
|
|
59
|
-
- - "~>"
|
|
60
|
-
- !ruby/object:Gem::Version
|
|
61
|
-
version: '3.0'
|
|
62
|
-
type: :development
|
|
63
|
-
prerelease: false
|
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
-
requirements:
|
|
66
|
-
- - "~>"
|
|
67
|
-
- !ruby/object:Gem::Version
|
|
68
|
-
version: '3.0'
|
|
26
|
+
version: '0'
|
|
69
27
|
description: A Ruby client for the AccessGrid API
|
|
70
28
|
email:
|
|
71
29
|
- ab@accessgrid.com
|
|
@@ -78,6 +36,8 @@ files:
|
|
|
78
36
|
- lib/accessgrid.rb
|
|
79
37
|
- lib/accessgrid/access_cards.rb
|
|
80
38
|
- lib/accessgrid/console.rb
|
|
39
|
+
- lib/accessgrid/error.rb
|
|
40
|
+
- lib/accessgrid/request.rb
|
|
81
41
|
- lib/accessgrid/version.rb
|
|
82
42
|
homepage: https://github.com/access-grid/accessgrid-rb
|
|
83
43
|
licenses:
|
|
@@ -92,7 +52,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
92
52
|
requirements:
|
|
93
53
|
- - ">="
|
|
94
54
|
- !ruby/object:Gem::Version
|
|
95
|
-
version:
|
|
55
|
+
version: '3.0'
|
|
96
56
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
97
57
|
requirements:
|
|
98
58
|
- - ">="
|