accessgrid 0.1.0__tar.gz
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.
- accessgrid-0.1.0/LICENSE +21 -0
- accessgrid-0.1.0/PKG-INFO +241 -0
- accessgrid-0.1.0/README.md +199 -0
- accessgrid-0.1.0/accessgrid/__init__.py +39 -0
- accessgrid-0.1.0/accessgrid/client.py +183 -0
- accessgrid-0.1.0/accessgrid.egg-info/PKG-INFO +241 -0
- accessgrid-0.1.0/accessgrid.egg-info/SOURCES.txt +13 -0
- accessgrid-0.1.0/accessgrid.egg-info/dependency_links.txt +1 -0
- accessgrid-0.1.0/accessgrid.egg-info/requires.txt +12 -0
- accessgrid-0.1.0/accessgrid.egg-info/top_level.txt +2 -0
- accessgrid-0.1.0/pyproject.toml +3 -0
- accessgrid-0.1.0/setup.cfg +4 -0
- accessgrid-0.1.0/setup.py +46 -0
- accessgrid-0.1.0/tests/__init__.py +0 -0
- accessgrid-0.1.0/tests/test_accessgrid.py +181 -0
accessgrid-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 AccessGrid
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
|
+
Name: accessgrid
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python SDK for the AccessGrid API
|
|
5
|
+
Home-page: https://github.com/yourusername/accessgrid-python
|
|
6
|
+
Author: Your Name
|
|
7
|
+
Author-email: your.email@example.com
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
18
|
+
Requires-Python: >=3.7
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Requires-Dist: requests>=2.25.0
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
24
|
+
Requires-Dist: pytest-mock>=3.10.0; extra == "dev"
|
|
25
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
26
|
+
Requires-Dist: black>=22.3.0; extra == "dev"
|
|
27
|
+
Requires-Dist: isort>=5.10.1; extra == "dev"
|
|
28
|
+
Requires-Dist: flake8>=4.0.1; extra == "dev"
|
|
29
|
+
Requires-Dist: mypy>=0.981; extra == "dev"
|
|
30
|
+
Requires-Dist: build>=0.10.0; extra == "dev"
|
|
31
|
+
Requires-Dist: twine>=4.0.2; extra == "dev"
|
|
32
|
+
Dynamic: author
|
|
33
|
+
Dynamic: author-email
|
|
34
|
+
Dynamic: classifier
|
|
35
|
+
Dynamic: description
|
|
36
|
+
Dynamic: description-content-type
|
|
37
|
+
Dynamic: home-page
|
|
38
|
+
Dynamic: provides-extra
|
|
39
|
+
Dynamic: requires-dist
|
|
40
|
+
Dynamic: requires-python
|
|
41
|
+
Dynamic: summary
|
|
42
|
+
|
|
43
|
+
# AccessGrid SDK
|
|
44
|
+
|
|
45
|
+
A JavaScript 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
|
|
46
|
+
|
|
47
|
+
## Installation
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
npm install accessgrid
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Quick Start
|
|
54
|
+
|
|
55
|
+
```javascript
|
|
56
|
+
import AccessGrid from 'accessgrid';
|
|
57
|
+
|
|
58
|
+
const accountId = process.env.ACCOUNT_ID;
|
|
59
|
+
const secretKey = process.env.SECRET_KEY;
|
|
60
|
+
|
|
61
|
+
const client = new AccessGrid(accountId, secretKey);
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## API Reference
|
|
65
|
+
|
|
66
|
+
### Access Cards
|
|
67
|
+
|
|
68
|
+
#### Provision a new card
|
|
69
|
+
|
|
70
|
+
```javascript
|
|
71
|
+
const card = await client.accessCards.provision({
|
|
72
|
+
cardTemplateId: "0xd3adb00b5",
|
|
73
|
+
employeeId: "123456789",
|
|
74
|
+
tagId: "DDEADB33FB00B5",
|
|
75
|
+
allowOnMultipleDevices: true,
|
|
76
|
+
fullName: "Employee name",
|
|
77
|
+
email: "employee@yourwebsite.com",
|
|
78
|
+
phoneNumber: "+19547212241",
|
|
79
|
+
classification: "full_time",
|
|
80
|
+
startDate: "2025-01-31T22:46:25.601Z",
|
|
81
|
+
expirationDate: "2025-04-30T22:46:25.601Z",
|
|
82
|
+
employeePhoto: "[image_in_base64_encoded_format]"
|
|
83
|
+
});
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
#### Update a card
|
|
87
|
+
|
|
88
|
+
```javascript
|
|
89
|
+
const card = await client.accessCards.update({
|
|
90
|
+
cardId: "0xc4rd1d",
|
|
91
|
+
employeeId: "987654321",
|
|
92
|
+
fullName: "Updated Employee Name",
|
|
93
|
+
classification: "contractor",
|
|
94
|
+
expirationDate: "2025-02-22T21:04:03.664Z",
|
|
95
|
+
employeePhoto: "[image_in_base64_encoded_format]"
|
|
96
|
+
});
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
#### Manage card states
|
|
100
|
+
|
|
101
|
+
```javascript
|
|
102
|
+
// Suspend a card
|
|
103
|
+
await client.accessCards.suspend({
|
|
104
|
+
cardId: "0xc4rd1d"
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// Resume a card
|
|
108
|
+
await client.accessCards.resume({
|
|
109
|
+
cardId: "0xc4rd1d"
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// Unlink a card
|
|
113
|
+
await client.accessCards.unlink({
|
|
114
|
+
cardId: "0xc4rd1d"
|
|
115
|
+
});
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Enterprise Console
|
|
119
|
+
|
|
120
|
+
#### Create a template
|
|
121
|
+
|
|
122
|
+
```javascript
|
|
123
|
+
const template = await client.console.createTemplate({
|
|
124
|
+
name: "Employee NFC key",
|
|
125
|
+
platform: "apple",
|
|
126
|
+
useCase: "employee_badge",
|
|
127
|
+
protocol: "desfire",
|
|
128
|
+
allowOnMultipleDevices: true,
|
|
129
|
+
watchCount: 2,
|
|
130
|
+
iphoneCount: 3,
|
|
131
|
+
design: {
|
|
132
|
+
backgroundColor: "#FFFFFF",
|
|
133
|
+
labelColor: "#000000",
|
|
134
|
+
labelSecondaryColor: "#333333",
|
|
135
|
+
backgroundImage: "[image_in_base64_encoded_format]",
|
|
136
|
+
logoImage: "[image_in_base64_encoded_format]",
|
|
137
|
+
iconImage: "[image_in_base64_encoded_format]"
|
|
138
|
+
},
|
|
139
|
+
supportInfo: {
|
|
140
|
+
supportUrl: "https://help.yourcompany.com",
|
|
141
|
+
supportPhoneNumber: "+1-555-123-4567",
|
|
142
|
+
supportEmail: "support@yourcompany.com",
|
|
143
|
+
privacyPolicyUrl: "https://yourcompany.com/privacy",
|
|
144
|
+
termsAndConditionsUrl: "https://yourcompany.com/terms"
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
#### Update a template
|
|
150
|
+
|
|
151
|
+
```javascript
|
|
152
|
+
const template = await client.console.updateTemplate({
|
|
153
|
+
cardTemplateId: "0xd3adb00b5",
|
|
154
|
+
name: "Updated Employee NFC key",
|
|
155
|
+
allowOnMultipleDevices: true,
|
|
156
|
+
watchCount: 2,
|
|
157
|
+
iphoneCount: 3,
|
|
158
|
+
supportInfo: {
|
|
159
|
+
supportUrl: "https://help.yourcompany.com",
|
|
160
|
+
supportPhoneNumber: "+1-555-123-4567",
|
|
161
|
+
supportEmail: "support@yourcompany.com",
|
|
162
|
+
privacyPolicyUrl: "https://yourcompany.com/privacy",
|
|
163
|
+
termsAndConditionsUrl: "https://yourcompany.com/terms"
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
#### Read a template
|
|
169
|
+
|
|
170
|
+
```javascript
|
|
171
|
+
const template = await client.console.readTemplate({
|
|
172
|
+
cardTemplateId: "0xd3adb00b5"
|
|
173
|
+
});
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
#### Get event logs
|
|
177
|
+
|
|
178
|
+
```javascript
|
|
179
|
+
const events = await client.console.eventLog({
|
|
180
|
+
cardTemplateId: "0xd3adb00b5",
|
|
181
|
+
filters: {
|
|
182
|
+
device: "mobile", // "mobile" or "watch"
|
|
183
|
+
startDate: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString(),
|
|
184
|
+
endDate: new Date().toISOString(),
|
|
185
|
+
eventType: "install"
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Configuration
|
|
191
|
+
|
|
192
|
+
The SDK can be configured with custom options:
|
|
193
|
+
|
|
194
|
+
```javascript
|
|
195
|
+
const client = new AccessGrid(accountId, secretKey, {
|
|
196
|
+
baseUrl: 'https://api.staging.accessgrid.com' // Use a different API endpoint
|
|
197
|
+
});
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Error Handling
|
|
201
|
+
|
|
202
|
+
The SDK throws errors for various scenarios including:
|
|
203
|
+
- Missing required credentials
|
|
204
|
+
- API request failures
|
|
205
|
+
- Invalid parameters
|
|
206
|
+
- Server errors
|
|
207
|
+
|
|
208
|
+
Example error handling:
|
|
209
|
+
|
|
210
|
+
```javascript
|
|
211
|
+
try {
|
|
212
|
+
const card = await client.accessCards.provision({
|
|
213
|
+
// ... parameters
|
|
214
|
+
});
|
|
215
|
+
} catch (error) {
|
|
216
|
+
console.error('Failed to provision card:', error.message);
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
## Requirements
|
|
221
|
+
|
|
222
|
+
- Node.js 12 or higher
|
|
223
|
+
- Modern browser environment with support for:
|
|
224
|
+
- Fetch API
|
|
225
|
+
- Web Crypto API
|
|
226
|
+
- Promises
|
|
227
|
+
- async/await
|
|
228
|
+
|
|
229
|
+
## Security
|
|
230
|
+
|
|
231
|
+
The SDK automatically handles:
|
|
232
|
+
- Request signing using HMAC-SHA256
|
|
233
|
+
- Secure payload encoding
|
|
234
|
+
- Authentication headers
|
|
235
|
+
- HTTPS communication
|
|
236
|
+
|
|
237
|
+
Never expose your `secretKey` in client-side code. Always use environment variables or a secure configuration management system.
|
|
238
|
+
|
|
239
|
+
## License
|
|
240
|
+
|
|
241
|
+
MIT License - See LICENSE file for details.
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# AccessGrid SDK
|
|
2
|
+
|
|
3
|
+
A JavaScript 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
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install accessgrid
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```javascript
|
|
14
|
+
import AccessGrid from 'accessgrid';
|
|
15
|
+
|
|
16
|
+
const accountId = process.env.ACCOUNT_ID;
|
|
17
|
+
const secretKey = process.env.SECRET_KEY;
|
|
18
|
+
|
|
19
|
+
const client = new AccessGrid(accountId, secretKey);
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## API Reference
|
|
23
|
+
|
|
24
|
+
### Access Cards
|
|
25
|
+
|
|
26
|
+
#### Provision a new card
|
|
27
|
+
|
|
28
|
+
```javascript
|
|
29
|
+
const card = await client.accessCards.provision({
|
|
30
|
+
cardTemplateId: "0xd3adb00b5",
|
|
31
|
+
employeeId: "123456789",
|
|
32
|
+
tagId: "DDEADB33FB00B5",
|
|
33
|
+
allowOnMultipleDevices: true,
|
|
34
|
+
fullName: "Employee name",
|
|
35
|
+
email: "employee@yourwebsite.com",
|
|
36
|
+
phoneNumber: "+19547212241",
|
|
37
|
+
classification: "full_time",
|
|
38
|
+
startDate: "2025-01-31T22:46:25.601Z",
|
|
39
|
+
expirationDate: "2025-04-30T22:46:25.601Z",
|
|
40
|
+
employeePhoto: "[image_in_base64_encoded_format]"
|
|
41
|
+
});
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
#### Update a card
|
|
45
|
+
|
|
46
|
+
```javascript
|
|
47
|
+
const card = await client.accessCards.update({
|
|
48
|
+
cardId: "0xc4rd1d",
|
|
49
|
+
employeeId: "987654321",
|
|
50
|
+
fullName: "Updated Employee Name",
|
|
51
|
+
classification: "contractor",
|
|
52
|
+
expirationDate: "2025-02-22T21:04:03.664Z",
|
|
53
|
+
employeePhoto: "[image_in_base64_encoded_format]"
|
|
54
|
+
});
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
#### Manage card states
|
|
58
|
+
|
|
59
|
+
```javascript
|
|
60
|
+
// Suspend a card
|
|
61
|
+
await client.accessCards.suspend({
|
|
62
|
+
cardId: "0xc4rd1d"
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// Resume a card
|
|
66
|
+
await client.accessCards.resume({
|
|
67
|
+
cardId: "0xc4rd1d"
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Unlink a card
|
|
71
|
+
await client.accessCards.unlink({
|
|
72
|
+
cardId: "0xc4rd1d"
|
|
73
|
+
});
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Enterprise Console
|
|
77
|
+
|
|
78
|
+
#### Create a template
|
|
79
|
+
|
|
80
|
+
```javascript
|
|
81
|
+
const template = await client.console.createTemplate({
|
|
82
|
+
name: "Employee NFC key",
|
|
83
|
+
platform: "apple",
|
|
84
|
+
useCase: "employee_badge",
|
|
85
|
+
protocol: "desfire",
|
|
86
|
+
allowOnMultipleDevices: true,
|
|
87
|
+
watchCount: 2,
|
|
88
|
+
iphoneCount: 3,
|
|
89
|
+
design: {
|
|
90
|
+
backgroundColor: "#FFFFFF",
|
|
91
|
+
labelColor: "#000000",
|
|
92
|
+
labelSecondaryColor: "#333333",
|
|
93
|
+
backgroundImage: "[image_in_base64_encoded_format]",
|
|
94
|
+
logoImage: "[image_in_base64_encoded_format]",
|
|
95
|
+
iconImage: "[image_in_base64_encoded_format]"
|
|
96
|
+
},
|
|
97
|
+
supportInfo: {
|
|
98
|
+
supportUrl: "https://help.yourcompany.com",
|
|
99
|
+
supportPhoneNumber: "+1-555-123-4567",
|
|
100
|
+
supportEmail: "support@yourcompany.com",
|
|
101
|
+
privacyPolicyUrl: "https://yourcompany.com/privacy",
|
|
102
|
+
termsAndConditionsUrl: "https://yourcompany.com/terms"
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
#### Update a template
|
|
108
|
+
|
|
109
|
+
```javascript
|
|
110
|
+
const template = await client.console.updateTemplate({
|
|
111
|
+
cardTemplateId: "0xd3adb00b5",
|
|
112
|
+
name: "Updated Employee NFC key",
|
|
113
|
+
allowOnMultipleDevices: true,
|
|
114
|
+
watchCount: 2,
|
|
115
|
+
iphoneCount: 3,
|
|
116
|
+
supportInfo: {
|
|
117
|
+
supportUrl: "https://help.yourcompany.com",
|
|
118
|
+
supportPhoneNumber: "+1-555-123-4567",
|
|
119
|
+
supportEmail: "support@yourcompany.com",
|
|
120
|
+
privacyPolicyUrl: "https://yourcompany.com/privacy",
|
|
121
|
+
termsAndConditionsUrl: "https://yourcompany.com/terms"
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
#### Read a template
|
|
127
|
+
|
|
128
|
+
```javascript
|
|
129
|
+
const template = await client.console.readTemplate({
|
|
130
|
+
cardTemplateId: "0xd3adb00b5"
|
|
131
|
+
});
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
#### Get event logs
|
|
135
|
+
|
|
136
|
+
```javascript
|
|
137
|
+
const events = await client.console.eventLog({
|
|
138
|
+
cardTemplateId: "0xd3adb00b5",
|
|
139
|
+
filters: {
|
|
140
|
+
device: "mobile", // "mobile" or "watch"
|
|
141
|
+
startDate: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString(),
|
|
142
|
+
endDate: new Date().toISOString(),
|
|
143
|
+
eventType: "install"
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Configuration
|
|
149
|
+
|
|
150
|
+
The SDK can be configured with custom options:
|
|
151
|
+
|
|
152
|
+
```javascript
|
|
153
|
+
const client = new AccessGrid(accountId, secretKey, {
|
|
154
|
+
baseUrl: 'https://api.staging.accessgrid.com' // Use a different API endpoint
|
|
155
|
+
});
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Error Handling
|
|
159
|
+
|
|
160
|
+
The SDK throws errors for various scenarios including:
|
|
161
|
+
- Missing required credentials
|
|
162
|
+
- API request failures
|
|
163
|
+
- Invalid parameters
|
|
164
|
+
- Server errors
|
|
165
|
+
|
|
166
|
+
Example error handling:
|
|
167
|
+
|
|
168
|
+
```javascript
|
|
169
|
+
try {
|
|
170
|
+
const card = await client.accessCards.provision({
|
|
171
|
+
// ... parameters
|
|
172
|
+
});
|
|
173
|
+
} catch (error) {
|
|
174
|
+
console.error('Failed to provision card:', error.message);
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Requirements
|
|
179
|
+
|
|
180
|
+
- Node.js 12 or higher
|
|
181
|
+
- Modern browser environment with support for:
|
|
182
|
+
- Fetch API
|
|
183
|
+
- Web Crypto API
|
|
184
|
+
- Promises
|
|
185
|
+
- async/await
|
|
186
|
+
|
|
187
|
+
## Security
|
|
188
|
+
|
|
189
|
+
The SDK automatically handles:
|
|
190
|
+
- Request signing using HMAC-SHA256
|
|
191
|
+
- Secure payload encoding
|
|
192
|
+
- Authentication headers
|
|
193
|
+
- HTTPS communication
|
|
194
|
+
|
|
195
|
+
Never expose your `secretKey` in client-side code. Always use environment variables or a secure configuration management system.
|
|
196
|
+
|
|
197
|
+
## License
|
|
198
|
+
|
|
199
|
+
MIT License - See LICENSE file for details.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AccessGrid Python SDK
|
|
3
|
+
~~~~~~~~~~~~~~~~~~~~
|
|
4
|
+
|
|
5
|
+
A Python SDK for interacting with the AccessGrid.com API.
|
|
6
|
+
|
|
7
|
+
Basic usage:
|
|
8
|
+
|
|
9
|
+
>>> from accessgrid import AccessGrid
|
|
10
|
+
>>> client = AccessGrid(account_id="your_id", secret_key="your_key")
|
|
11
|
+
>>> card = client.access_cards.provision(
|
|
12
|
+
... card_template_id="template_id",
|
|
13
|
+
... full_name="Employee Name"
|
|
14
|
+
... )
|
|
15
|
+
>>> print(card.url)
|
|
16
|
+
|
|
17
|
+
For more information, see https://www.accessgrid.com/docs
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
# Import all public components
|
|
21
|
+
from .client import (
|
|
22
|
+
AccessGrid,
|
|
23
|
+
AccessGridError,
|
|
24
|
+
AuthenticationError,
|
|
25
|
+
AccessCard,
|
|
26
|
+
Template
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
# Version of the accessgrid package
|
|
30
|
+
__version__ = "0.1.0"
|
|
31
|
+
|
|
32
|
+
# List of public objects that will be exported with "from accessgrid import *"
|
|
33
|
+
__all__ = [
|
|
34
|
+
'AccessGrid',
|
|
35
|
+
'AccessGridError',
|
|
36
|
+
'AuthenticationError',
|
|
37
|
+
'AccessCard',
|
|
38
|
+
'Template'
|
|
39
|
+
]
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import hmac
|
|
3
|
+
import hashlib
|
|
4
|
+
import json
|
|
5
|
+
import requests
|
|
6
|
+
from datetime import datetime, timezone
|
|
7
|
+
from urllib.parse import quote
|
|
8
|
+
from typing import Optional, Dict, Any, List
|
|
9
|
+
|
|
10
|
+
class AccessGridError(Exception):
|
|
11
|
+
"""Base exception for AccessGrid SDK"""
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
class AuthenticationError(AccessGridError):
|
|
15
|
+
"""Raised when authentication fails"""
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
class AccessCard:
|
|
19
|
+
def __init__(self, client, data: Dict[str, Any]):
|
|
20
|
+
self._client = client
|
|
21
|
+
self.id = data.get('id')
|
|
22
|
+
self.url = data.get('install_url')
|
|
23
|
+
self.state = data.get('state')
|
|
24
|
+
self.full_name = data.get('full_name')
|
|
25
|
+
self.expiration_date = data.get('expiration_date')
|
|
26
|
+
|
|
27
|
+
class Template:
|
|
28
|
+
def __init__(self, client, data: Dict[str, Any]):
|
|
29
|
+
self._client = client
|
|
30
|
+
self.id = data.get('id')
|
|
31
|
+
self.name = data.get('name')
|
|
32
|
+
self.platform = data.get('platform')
|
|
33
|
+
self.use_case = data.get('use_case')
|
|
34
|
+
self.protocol = data.get('protocol')
|
|
35
|
+
self.created_at = data.get('created_at')
|
|
36
|
+
self.last_published_at = data.get('last_published_at')
|
|
37
|
+
self.issued_keys_count = data.get('issued_keys_count')
|
|
38
|
+
self.active_keys_count = data.get('active_keys_count')
|
|
39
|
+
self.allowed_device_counts = data.get('allowed_device_counts')
|
|
40
|
+
self.support_settings = data.get('support_settings')
|
|
41
|
+
self.terms_settings = data.get('terms_settings')
|
|
42
|
+
self.style_settings = data.get('style_settings')
|
|
43
|
+
|
|
44
|
+
class AccessCards:
|
|
45
|
+
def __init__(self, client):
|
|
46
|
+
self._client = client
|
|
47
|
+
|
|
48
|
+
def provision(self, card_template_id: str, **kwargs) -> AccessCard:
|
|
49
|
+
"""Provision a new access card"""
|
|
50
|
+
payload = {
|
|
51
|
+
'card_template_id': card_template_id,
|
|
52
|
+
**kwargs
|
|
53
|
+
}
|
|
54
|
+
response = self._client._post('/api/v1/nfc_keys/issue', payload)
|
|
55
|
+
return AccessCard(self._client, response)
|
|
56
|
+
|
|
57
|
+
def update(self, card_id: str, **kwargs) -> AccessCard:
|
|
58
|
+
"""Update an existing access card"""
|
|
59
|
+
payload = {
|
|
60
|
+
'card_id': card_id,
|
|
61
|
+
**kwargs
|
|
62
|
+
}
|
|
63
|
+
response = self._client._post('/api/v1/nfc_keys/update', payload)
|
|
64
|
+
return AccessCard(self._client, response)
|
|
65
|
+
|
|
66
|
+
def _manage(self, card_id: str, action: str) -> AccessCard:
|
|
67
|
+
"""Internal method for card management actions"""
|
|
68
|
+
payload = {
|
|
69
|
+
'card_id': card_id,
|
|
70
|
+
'manage_action': action
|
|
71
|
+
}
|
|
72
|
+
response = self._client._post('/api/v1/nfc_keys/manage', payload)
|
|
73
|
+
return AccessCard(self._client, response)
|
|
74
|
+
|
|
75
|
+
def suspend(self, card_id: str) -> AccessCard:
|
|
76
|
+
"""Suspend an access card"""
|
|
77
|
+
return self._manage(card_id, 'suspend')
|
|
78
|
+
|
|
79
|
+
def resume(self, card_id: str) -> AccessCard:
|
|
80
|
+
"""Resume a suspended access card"""
|
|
81
|
+
return self._manage(card_id, 'resume')
|
|
82
|
+
|
|
83
|
+
def unlink(self, card_id: str) -> AccessCard:
|
|
84
|
+
"""Unlink an access card"""
|
|
85
|
+
return self._manage(card_id, 'unlink')
|
|
86
|
+
|
|
87
|
+
class Console:
|
|
88
|
+
def __init__(self, client):
|
|
89
|
+
self._client = client
|
|
90
|
+
|
|
91
|
+
def create_template(self, **kwargs) -> Template:
|
|
92
|
+
"""Create a new card template"""
|
|
93
|
+
response = self._client._post('/api/v1/enterprise/create_template', kwargs)
|
|
94
|
+
return Template(self._client, response)
|
|
95
|
+
|
|
96
|
+
def update_template(self, card_template_id: str, **kwargs) -> Template:
|
|
97
|
+
"""Update an existing card template"""
|
|
98
|
+
response = self._client._post(f'/api/v1/enterprise/update_template/{card_template_id}', kwargs)
|
|
99
|
+
return Template(self._client, response)
|
|
100
|
+
|
|
101
|
+
def read_template(self, card_template_id: str) -> Template:
|
|
102
|
+
"""Get details of a card template"""
|
|
103
|
+
response = self._client._get(f'/api/v1/enterprise/read_template/{card_template_id}')
|
|
104
|
+
return Template(self._client, response)
|
|
105
|
+
|
|
106
|
+
def event_log(self, card_template_id: str, filters: Optional[Dict] = None,
|
|
107
|
+
page: int = 1, per_page: int = 50) -> Dict[str, Any]:
|
|
108
|
+
"""Get event logs for a card template"""
|
|
109
|
+
params = {
|
|
110
|
+
'page': page,
|
|
111
|
+
'per_page': per_page
|
|
112
|
+
}
|
|
113
|
+
if filters:
|
|
114
|
+
params['filters'] = filters
|
|
115
|
+
|
|
116
|
+
return self._client._get(f'/api/v1/enterprise/logs/{card_template_id}', params)
|
|
117
|
+
|
|
118
|
+
class AccessGrid:
|
|
119
|
+
def __init__(self, account_id: str, secret_key: str, base_url: str = 'https://api.accessgrid.com'):
|
|
120
|
+
if not account_id:
|
|
121
|
+
raise ValueError("Account ID is required")
|
|
122
|
+
if not secret_key:
|
|
123
|
+
raise ValueError("Secret Key is required")
|
|
124
|
+
|
|
125
|
+
self.account_id = account_id
|
|
126
|
+
self.secret_key = secret_key
|
|
127
|
+
self.base_url = base_url.rstrip('/')
|
|
128
|
+
self.access_cards = AccessCards(self)
|
|
129
|
+
self.console = Console(self)
|
|
130
|
+
|
|
131
|
+
def _generate_signature(self, payload: str) -> str:
|
|
132
|
+
"""Generate HMAC signature for the payload"""
|
|
133
|
+
encoded_payload = base64.b64encode(payload.encode()).decode()
|
|
134
|
+
return hmac.new(
|
|
135
|
+
self.secret_key.encode(),
|
|
136
|
+
encoded_payload.encode(),
|
|
137
|
+
hashlib.sha256
|
|
138
|
+
).hexdigest()
|
|
139
|
+
|
|
140
|
+
def _make_request(self, method: str, endpoint: str,
|
|
141
|
+
data: Optional[Dict] = None,
|
|
142
|
+
params: Optional[Dict] = None) -> Dict[str, Any]:
|
|
143
|
+
"""Make an HTTP request to the API"""
|
|
144
|
+
url = f"{self.base_url}{endpoint}"
|
|
145
|
+
|
|
146
|
+
# Prepare payload and signature
|
|
147
|
+
payload = json.dumps(data) if data else ""
|
|
148
|
+
headers = {
|
|
149
|
+
'X-ACCT-ID': self.account_id,
|
|
150
|
+
'X-PAYLOAD-SIG': self._generate_signature(payload),
|
|
151
|
+
'Content-Type': 'application/json'
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
try:
|
|
155
|
+
response = requests.request(
|
|
156
|
+
method=method,
|
|
157
|
+
url=url,
|
|
158
|
+
headers=headers,
|
|
159
|
+
json=data if data else None,
|
|
160
|
+
params=params
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
if response.status_code == 401:
|
|
164
|
+
raise AuthenticationError("Invalid credentials")
|
|
165
|
+
elif response.status_code == 402:
|
|
166
|
+
raise AccessGridError("Insufficient account balance")
|
|
167
|
+
elif not 200 <= response.status_code < 300:
|
|
168
|
+
error_data = response.json() if response.text else {}
|
|
169
|
+
error_message = error_data.get('message', response.text)
|
|
170
|
+
raise AccessGridError(f"API request failed: {error_message}")
|
|
171
|
+
|
|
172
|
+
return response.json()
|
|
173
|
+
|
|
174
|
+
except requests.exceptions.RequestException as e:
|
|
175
|
+
raise AccessGridError(f"Request failed: {str(e)}")
|
|
176
|
+
|
|
177
|
+
def _get(self, endpoint: str, params: Optional[Dict] = None) -> Dict[str, Any]:
|
|
178
|
+
"""Make a GET request"""
|
|
179
|
+
return self._make_request('GET', endpoint, params=params)
|
|
180
|
+
|
|
181
|
+
def _post(self, endpoint: str, data: Dict) -> Dict[str, Any]:
|
|
182
|
+
"""Make a POST request"""
|
|
183
|
+
return self._make_request('POST', endpoint, data=data)
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
|
+
Name: accessgrid
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python SDK for the AccessGrid API
|
|
5
|
+
Home-page: https://github.com/yourusername/accessgrid-python
|
|
6
|
+
Author: Your Name
|
|
7
|
+
Author-email: your.email@example.com
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
18
|
+
Requires-Python: >=3.7
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Requires-Dist: requests>=2.25.0
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
24
|
+
Requires-Dist: pytest-mock>=3.10.0; extra == "dev"
|
|
25
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
26
|
+
Requires-Dist: black>=22.3.0; extra == "dev"
|
|
27
|
+
Requires-Dist: isort>=5.10.1; extra == "dev"
|
|
28
|
+
Requires-Dist: flake8>=4.0.1; extra == "dev"
|
|
29
|
+
Requires-Dist: mypy>=0.981; extra == "dev"
|
|
30
|
+
Requires-Dist: build>=0.10.0; extra == "dev"
|
|
31
|
+
Requires-Dist: twine>=4.0.2; extra == "dev"
|
|
32
|
+
Dynamic: author
|
|
33
|
+
Dynamic: author-email
|
|
34
|
+
Dynamic: classifier
|
|
35
|
+
Dynamic: description
|
|
36
|
+
Dynamic: description-content-type
|
|
37
|
+
Dynamic: home-page
|
|
38
|
+
Dynamic: provides-extra
|
|
39
|
+
Dynamic: requires-dist
|
|
40
|
+
Dynamic: requires-python
|
|
41
|
+
Dynamic: summary
|
|
42
|
+
|
|
43
|
+
# AccessGrid SDK
|
|
44
|
+
|
|
45
|
+
A JavaScript 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
|
|
46
|
+
|
|
47
|
+
## Installation
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
npm install accessgrid
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Quick Start
|
|
54
|
+
|
|
55
|
+
```javascript
|
|
56
|
+
import AccessGrid from 'accessgrid';
|
|
57
|
+
|
|
58
|
+
const accountId = process.env.ACCOUNT_ID;
|
|
59
|
+
const secretKey = process.env.SECRET_KEY;
|
|
60
|
+
|
|
61
|
+
const client = new AccessGrid(accountId, secretKey);
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## API Reference
|
|
65
|
+
|
|
66
|
+
### Access Cards
|
|
67
|
+
|
|
68
|
+
#### Provision a new card
|
|
69
|
+
|
|
70
|
+
```javascript
|
|
71
|
+
const card = await client.accessCards.provision({
|
|
72
|
+
cardTemplateId: "0xd3adb00b5",
|
|
73
|
+
employeeId: "123456789",
|
|
74
|
+
tagId: "DDEADB33FB00B5",
|
|
75
|
+
allowOnMultipleDevices: true,
|
|
76
|
+
fullName: "Employee name",
|
|
77
|
+
email: "employee@yourwebsite.com",
|
|
78
|
+
phoneNumber: "+19547212241",
|
|
79
|
+
classification: "full_time",
|
|
80
|
+
startDate: "2025-01-31T22:46:25.601Z",
|
|
81
|
+
expirationDate: "2025-04-30T22:46:25.601Z",
|
|
82
|
+
employeePhoto: "[image_in_base64_encoded_format]"
|
|
83
|
+
});
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
#### Update a card
|
|
87
|
+
|
|
88
|
+
```javascript
|
|
89
|
+
const card = await client.accessCards.update({
|
|
90
|
+
cardId: "0xc4rd1d",
|
|
91
|
+
employeeId: "987654321",
|
|
92
|
+
fullName: "Updated Employee Name",
|
|
93
|
+
classification: "contractor",
|
|
94
|
+
expirationDate: "2025-02-22T21:04:03.664Z",
|
|
95
|
+
employeePhoto: "[image_in_base64_encoded_format]"
|
|
96
|
+
});
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
#### Manage card states
|
|
100
|
+
|
|
101
|
+
```javascript
|
|
102
|
+
// Suspend a card
|
|
103
|
+
await client.accessCards.suspend({
|
|
104
|
+
cardId: "0xc4rd1d"
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// Resume a card
|
|
108
|
+
await client.accessCards.resume({
|
|
109
|
+
cardId: "0xc4rd1d"
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// Unlink a card
|
|
113
|
+
await client.accessCards.unlink({
|
|
114
|
+
cardId: "0xc4rd1d"
|
|
115
|
+
});
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Enterprise Console
|
|
119
|
+
|
|
120
|
+
#### Create a template
|
|
121
|
+
|
|
122
|
+
```javascript
|
|
123
|
+
const template = await client.console.createTemplate({
|
|
124
|
+
name: "Employee NFC key",
|
|
125
|
+
platform: "apple",
|
|
126
|
+
useCase: "employee_badge",
|
|
127
|
+
protocol: "desfire",
|
|
128
|
+
allowOnMultipleDevices: true,
|
|
129
|
+
watchCount: 2,
|
|
130
|
+
iphoneCount: 3,
|
|
131
|
+
design: {
|
|
132
|
+
backgroundColor: "#FFFFFF",
|
|
133
|
+
labelColor: "#000000",
|
|
134
|
+
labelSecondaryColor: "#333333",
|
|
135
|
+
backgroundImage: "[image_in_base64_encoded_format]",
|
|
136
|
+
logoImage: "[image_in_base64_encoded_format]",
|
|
137
|
+
iconImage: "[image_in_base64_encoded_format]"
|
|
138
|
+
},
|
|
139
|
+
supportInfo: {
|
|
140
|
+
supportUrl: "https://help.yourcompany.com",
|
|
141
|
+
supportPhoneNumber: "+1-555-123-4567",
|
|
142
|
+
supportEmail: "support@yourcompany.com",
|
|
143
|
+
privacyPolicyUrl: "https://yourcompany.com/privacy",
|
|
144
|
+
termsAndConditionsUrl: "https://yourcompany.com/terms"
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
#### Update a template
|
|
150
|
+
|
|
151
|
+
```javascript
|
|
152
|
+
const template = await client.console.updateTemplate({
|
|
153
|
+
cardTemplateId: "0xd3adb00b5",
|
|
154
|
+
name: "Updated Employee NFC key",
|
|
155
|
+
allowOnMultipleDevices: true,
|
|
156
|
+
watchCount: 2,
|
|
157
|
+
iphoneCount: 3,
|
|
158
|
+
supportInfo: {
|
|
159
|
+
supportUrl: "https://help.yourcompany.com",
|
|
160
|
+
supportPhoneNumber: "+1-555-123-4567",
|
|
161
|
+
supportEmail: "support@yourcompany.com",
|
|
162
|
+
privacyPolicyUrl: "https://yourcompany.com/privacy",
|
|
163
|
+
termsAndConditionsUrl: "https://yourcompany.com/terms"
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
#### Read a template
|
|
169
|
+
|
|
170
|
+
```javascript
|
|
171
|
+
const template = await client.console.readTemplate({
|
|
172
|
+
cardTemplateId: "0xd3adb00b5"
|
|
173
|
+
});
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
#### Get event logs
|
|
177
|
+
|
|
178
|
+
```javascript
|
|
179
|
+
const events = await client.console.eventLog({
|
|
180
|
+
cardTemplateId: "0xd3adb00b5",
|
|
181
|
+
filters: {
|
|
182
|
+
device: "mobile", // "mobile" or "watch"
|
|
183
|
+
startDate: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString(),
|
|
184
|
+
endDate: new Date().toISOString(),
|
|
185
|
+
eventType: "install"
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Configuration
|
|
191
|
+
|
|
192
|
+
The SDK can be configured with custom options:
|
|
193
|
+
|
|
194
|
+
```javascript
|
|
195
|
+
const client = new AccessGrid(accountId, secretKey, {
|
|
196
|
+
baseUrl: 'https://api.staging.accessgrid.com' // Use a different API endpoint
|
|
197
|
+
});
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Error Handling
|
|
201
|
+
|
|
202
|
+
The SDK throws errors for various scenarios including:
|
|
203
|
+
- Missing required credentials
|
|
204
|
+
- API request failures
|
|
205
|
+
- Invalid parameters
|
|
206
|
+
- Server errors
|
|
207
|
+
|
|
208
|
+
Example error handling:
|
|
209
|
+
|
|
210
|
+
```javascript
|
|
211
|
+
try {
|
|
212
|
+
const card = await client.accessCards.provision({
|
|
213
|
+
// ... parameters
|
|
214
|
+
});
|
|
215
|
+
} catch (error) {
|
|
216
|
+
console.error('Failed to provision card:', error.message);
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
## Requirements
|
|
221
|
+
|
|
222
|
+
- Node.js 12 or higher
|
|
223
|
+
- Modern browser environment with support for:
|
|
224
|
+
- Fetch API
|
|
225
|
+
- Web Crypto API
|
|
226
|
+
- Promises
|
|
227
|
+
- async/await
|
|
228
|
+
|
|
229
|
+
## Security
|
|
230
|
+
|
|
231
|
+
The SDK automatically handles:
|
|
232
|
+
- Request signing using HMAC-SHA256
|
|
233
|
+
- Secure payload encoding
|
|
234
|
+
- Authentication headers
|
|
235
|
+
- HTTPS communication
|
|
236
|
+
|
|
237
|
+
Never expose your `secretKey` in client-side code. Always use environment variables or a secure configuration management system.
|
|
238
|
+
|
|
239
|
+
## License
|
|
240
|
+
|
|
241
|
+
MIT License - See LICENSE file for details.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
setup.py
|
|
5
|
+
accessgrid/__init__.py
|
|
6
|
+
accessgrid/client.py
|
|
7
|
+
accessgrid.egg-info/PKG-INFO
|
|
8
|
+
accessgrid.egg-info/SOURCES.txt
|
|
9
|
+
accessgrid.egg-info/dependency_links.txt
|
|
10
|
+
accessgrid.egg-info/requires.txt
|
|
11
|
+
accessgrid.egg-info/top_level.txt
|
|
12
|
+
tests/__init__.py
|
|
13
|
+
tests/test_accessgrid.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
|
|
3
|
+
# Read README for the long description
|
|
4
|
+
with open("README.md", "r", encoding="utf-8") as fh:
|
|
5
|
+
long_description = fh.read()
|
|
6
|
+
|
|
7
|
+
setup(
|
|
8
|
+
name="accessgrid",
|
|
9
|
+
version="0.1.0",
|
|
10
|
+
author="Your Name",
|
|
11
|
+
author_email="your.email@example.com",
|
|
12
|
+
description="Python SDK for the AccessGrid API",
|
|
13
|
+
long_description=long_description,
|
|
14
|
+
long_description_content_type="text/markdown",
|
|
15
|
+
url="https://github.com/yourusername/accessgrid-python",
|
|
16
|
+
packages=find_packages(),
|
|
17
|
+
classifiers=[
|
|
18
|
+
"Development Status :: 4 - Beta",
|
|
19
|
+
"Intended Audience :: Developers",
|
|
20
|
+
"License :: OSI Approved :: MIT License",
|
|
21
|
+
"Operating System :: OS Independent",
|
|
22
|
+
"Programming Language :: Python :: 3",
|
|
23
|
+
"Programming Language :: Python :: 3.7",
|
|
24
|
+
"Programming Language :: Python :: 3.8",
|
|
25
|
+
"Programming Language :: Python :: 3.9",
|
|
26
|
+
"Programming Language :: Python :: 3.10",
|
|
27
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
28
|
+
],
|
|
29
|
+
python_requires=">=3.7",
|
|
30
|
+
install_requires=[
|
|
31
|
+
"requests>=2.25.0",
|
|
32
|
+
],
|
|
33
|
+
extras_require={
|
|
34
|
+
"dev": [
|
|
35
|
+
"pytest>=7.0.0",
|
|
36
|
+
"pytest-mock>=3.10.0",
|
|
37
|
+
"pytest-cov>=4.0.0",
|
|
38
|
+
"black>=22.3.0",
|
|
39
|
+
"isort>=5.10.1",
|
|
40
|
+
"flake8>=4.0.1",
|
|
41
|
+
"mypy>=0.981",
|
|
42
|
+
"build>=0.10.0",
|
|
43
|
+
"twine>=4.0.2",
|
|
44
|
+
],
|
|
45
|
+
},
|
|
46
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from unittest.mock import patch, Mock
|
|
3
|
+
from accessgrid import AccessGrid, AccessGridError, AuthenticationError
|
|
4
|
+
|
|
5
|
+
MOCK_ACCOUNT_ID = 'test-account-id'
|
|
6
|
+
MOCK_SECRET_KEY = 'test-secret-key'
|
|
7
|
+
|
|
8
|
+
@pytest.fixture
|
|
9
|
+
def client():
|
|
10
|
+
return AccessGrid(MOCK_ACCOUNT_ID, MOCK_SECRET_KEY)
|
|
11
|
+
|
|
12
|
+
@pytest.fixture
|
|
13
|
+
def mock_response():
|
|
14
|
+
mock = Mock()
|
|
15
|
+
mock.json.return_value = {'status': 'success'}
|
|
16
|
+
mock.status_code = 200
|
|
17
|
+
mock.text = '{"status": "success"}'
|
|
18
|
+
return mock
|
|
19
|
+
|
|
20
|
+
class TestAccessGrid:
|
|
21
|
+
def test_constructor_missing_account_id(self):
|
|
22
|
+
with pytest.raises(ValueError, match='Account ID is required'):
|
|
23
|
+
AccessGrid(None, MOCK_SECRET_KEY)
|
|
24
|
+
|
|
25
|
+
def test_constructor_missing_secret_key(self):
|
|
26
|
+
with pytest.raises(ValueError, match='Secret Key is required'):
|
|
27
|
+
AccessGrid(MOCK_ACCOUNT_ID, None)
|
|
28
|
+
|
|
29
|
+
def test_constructor_with_custom_base_url(self):
|
|
30
|
+
custom_url = 'https://custom.api.com'
|
|
31
|
+
client = AccessGrid(MOCK_ACCOUNT_ID, MOCK_SECRET_KEY, base_url=custom_url)
|
|
32
|
+
assert client.base_url == custom_url.rstrip('/')
|
|
33
|
+
|
|
34
|
+
class TestAccessCards:
|
|
35
|
+
@pytest.fixture
|
|
36
|
+
def mock_provision_params(self):
|
|
37
|
+
return {
|
|
38
|
+
'card_template_id': '0xd3adb00b5',
|
|
39
|
+
'employee_id': '123456789',
|
|
40
|
+
'tag_id': 'DDEADB33FB00B5',
|
|
41
|
+
'allow_on_multiple_devices': True,
|
|
42
|
+
'full_name': 'Employee name',
|
|
43
|
+
'email': 'employee@yourwebsite.com',
|
|
44
|
+
'phone_number': '+19547212241',
|
|
45
|
+
'classification': 'full_time',
|
|
46
|
+
'start_date': '2025-01-31T22:46:25.601Z',
|
|
47
|
+
'expiration_date': '2025-04-30T22:46:25.601Z',
|
|
48
|
+
'employee_photo': 'base64photo'
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
@patch('requests.request')
|
|
52
|
+
def test_provision_card(self, mock_request, client, mock_response, mock_provision_params):
|
|
53
|
+
mock_request.return_value = mock_response
|
|
54
|
+
|
|
55
|
+
card = client.access_cards.provision(**mock_provision_params)
|
|
56
|
+
|
|
57
|
+
mock_request.assert_called_once()
|
|
58
|
+
call_args = mock_request.call_args[1]
|
|
59
|
+
assert call_args['method'] == 'POST'
|
|
60
|
+
assert call_args['url'] == f"{client.base_url}/api/v1/nfc_keys/issue"
|
|
61
|
+
assert call_args['json'] == mock_provision_params
|
|
62
|
+
assert call_args['headers']['X-ACCT-ID'] == MOCK_ACCOUNT_ID
|
|
63
|
+
assert 'X-PAYLOAD-SIG' in call_args['headers']
|
|
64
|
+
assert call_args['headers']['Content-Type'] == 'application/json'
|
|
65
|
+
|
|
66
|
+
@patch('requests.request')
|
|
67
|
+
def test_provision_card_error(self, mock_request, client, mock_provision_params):
|
|
68
|
+
error_response = Mock()
|
|
69
|
+
error_response.status_code = 400
|
|
70
|
+
error_response.text = '{"message": "Invalid template ID"}'
|
|
71
|
+
error_response.json.return_value = {"message": "Invalid template ID"}
|
|
72
|
+
mock_request.return_value = error_response
|
|
73
|
+
|
|
74
|
+
with pytest.raises(AccessGridError, match='API request failed: Invalid template ID'):
|
|
75
|
+
client.access_cards.provision(**mock_provision_params)
|
|
76
|
+
|
|
77
|
+
@patch('requests.request')
|
|
78
|
+
def test_update_card(self, mock_request, client, mock_response):
|
|
79
|
+
mock_request.return_value = mock_response
|
|
80
|
+
update_params = {
|
|
81
|
+
'card_id': '0xc4rd1d',
|
|
82
|
+
'employee_id': '987654321',
|
|
83
|
+
'full_name': 'Updated Employee Name',
|
|
84
|
+
'classification': 'contractor',
|
|
85
|
+
'expiration_date': '2025-02-22T21:04:03.664Z'
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
card = client.access_cards.update(**update_params)
|
|
89
|
+
|
|
90
|
+
mock_request.assert_called_once()
|
|
91
|
+
call_args = mock_request.call_args[1]
|
|
92
|
+
assert call_args['method'] == 'POST'
|
|
93
|
+
assert call_args['url'] == f"{client.base_url}/api/v1/nfc_keys/update"
|
|
94
|
+
assert call_args['json'] == update_params
|
|
95
|
+
|
|
96
|
+
@patch('requests.request')
|
|
97
|
+
def test_manage_operations(self, mock_request, client, mock_response):
|
|
98
|
+
mock_request.return_value = mock_response
|
|
99
|
+
card_id = '0xc4rd1d'
|
|
100
|
+
|
|
101
|
+
# Test suspend
|
|
102
|
+
client.access_cards.suspend(card_id)
|
|
103
|
+
call_args = mock_request.call_args[1]
|
|
104
|
+
assert call_args['method'] == 'POST'
|
|
105
|
+
assert call_args['url'] == f"{client.base_url}/api/v1/nfc_keys/manage"
|
|
106
|
+
assert call_args['json'] == {'card_id': card_id, 'manage_action': 'suspend'}
|
|
107
|
+
|
|
108
|
+
# Test resume
|
|
109
|
+
client.access_cards.resume(card_id)
|
|
110
|
+
call_args = mock_request.call_args[1]
|
|
111
|
+
assert call_args['json'] == {'card_id': card_id, 'manage_action': 'resume'}
|
|
112
|
+
|
|
113
|
+
# Test unlink
|
|
114
|
+
client.access_cards.unlink(card_id)
|
|
115
|
+
call_args = mock_request.call_args[1]
|
|
116
|
+
assert call_args['json'] == {'card_id': card_id, 'manage_action': 'unlink'}
|
|
117
|
+
|
|
118
|
+
class TestConsole:
|
|
119
|
+
@pytest.fixture
|
|
120
|
+
def mock_template_params(self):
|
|
121
|
+
return {
|
|
122
|
+
'name': 'Employee NFC key',
|
|
123
|
+
'platform': 'apple',
|
|
124
|
+
'use_case': 'employee_badge',
|
|
125
|
+
'protocol': 'desfire',
|
|
126
|
+
'allow_on_multiple_devices': True,
|
|
127
|
+
'watch_count': 2,
|
|
128
|
+
'iphone_count': 3,
|
|
129
|
+
'design': {
|
|
130
|
+
'background_color': '#FFFFFF',
|
|
131
|
+
'label_color': '#000000',
|
|
132
|
+
'label_secondary_color': '#333333'
|
|
133
|
+
},
|
|
134
|
+
'support_info': {
|
|
135
|
+
'support_url': 'https://help.yourcompany.com',
|
|
136
|
+
'support_phone_number': '+1-555-123-4567',
|
|
137
|
+
'support_email': 'support@yourcompany.com',
|
|
138
|
+
'privacy_policy_url': 'https://yourcompany.com/privacy',
|
|
139
|
+
'terms_and_conditions_url': 'https://yourcompany.com/terms'
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
@patch('requests.request')
|
|
144
|
+
def test_create_template(self, mock_request, client, mock_response, mock_template_params):
|
|
145
|
+
mock_request.return_value = mock_response
|
|
146
|
+
|
|
147
|
+
template = client.console.create_template(**mock_template_params)
|
|
148
|
+
|
|
149
|
+
call_args = mock_request.call_args[1]
|
|
150
|
+
assert call_args['method'] == 'POST'
|
|
151
|
+
assert call_args['url'] == f"{client.base_url}/api/v1/enterprise/create_template"
|
|
152
|
+
assert call_args['json'] == mock_template_params
|
|
153
|
+
|
|
154
|
+
@patch('requests.request')
|
|
155
|
+
def test_read_template(self, mock_request, client, mock_response):
|
|
156
|
+
mock_request.return_value = mock_response
|
|
157
|
+
template_id = '0xd3adb00b5'
|
|
158
|
+
|
|
159
|
+
template = client.console.read_template(template_id)
|
|
160
|
+
|
|
161
|
+
call_args = mock_request.call_args[1]
|
|
162
|
+
assert call_args['method'] == 'GET'
|
|
163
|
+
assert call_args['url'] == f"{client.base_url}/api/v1/enterprise/read_template/{template_id}"
|
|
164
|
+
|
|
165
|
+
@patch('requests.request')
|
|
166
|
+
def test_event_log(self, mock_request, client, mock_response):
|
|
167
|
+
mock_request.return_value = mock_response
|
|
168
|
+
template_id = '0xd3adb00b5'
|
|
169
|
+
filters = {
|
|
170
|
+
'device': 'mobile',
|
|
171
|
+
'start_date': '2025-01-01T00:00:00Z',
|
|
172
|
+
'end_date': '2025-02-01T00:00:00Z',
|
|
173
|
+
'event_type': 'install'
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
events = client.console.event_log(template_id, filters=filters)
|
|
177
|
+
|
|
178
|
+
call_args = mock_request.call_args[1]
|
|
179
|
+
assert call_args['method'] == 'GET'
|
|
180
|
+
assert call_args['url'] == f"{client.base_url}/api/v1/enterprise/logs/{template_id}"
|
|
181
|
+
assert call_args['params'] == {'filters': filters, 'page': 1, 'per_page': 50}
|