cupl Backend Documentation¶
See also cuplcodec and cuplfrontend.
API Specifications¶
Admin API Specification¶
Tag¶
-
GET
/tag
¶ get a tag
Get a tag from an id.
- Query Parameters
id (integer) – Tag id
Example request:
GET /tag HTTP/1.1 Host: example.com
- Status Codes
200 OK –
A tag object
Example response:
HTTP/1.1 200 OK Content-Type: application/json { "id": 1, "serial": "string", "secretKey": "string", "timeregistered": "2020-08-27T10:57:36.257820" }
bad input parameter
Example response:
HTTP/1.1 400 Bad Request Content-Type: application/json {}
-
DELETE
/tag
¶ delete a tag
delete a tag
- Query Parameters
id (integer) – Tag id
- Status Codes
Tag has been deleted
Example response:
HTTP/1.1 204 No Content Content-Type: application/json {}
bad input parameter
Example response:
HTTP/1.1 400 Bad Request Content-Type: application/json {}
No tag found
Example response:
HTTP/1.1 404 Not Found Content-Type: application/json {}
Get a list of tags ordered by ID.
Get a list of tags
- Query Parameters
offset (integer) – Return samples starting from this index.
limit (integer) – Limit the number of samples returned.
Example request:
GET /tags HTTP/1.1 Host: example.com
- Status Codes
200 OK –
List of tags
Example response:
HTTP/1.1 200 OK Content-Type: application/json [ { "id": 1, "serial": "string", "secretKey": "string", "timeregistered": "2020-08-27T10:57:36.257820" } ]
bad input parameter
Example response:
HTTP/1.1 400 Bad Request Content-Type: application/json {}
create a tag
Create a tag
Example request:
POST /tags HTTP/1.1 Host: example.com Content-Type: application/json { "id": 1 }
- Status Codes
Tag created
Example response:
HTTP/1.1 201 Created Content-Type: application/json { "id": 1, "serial": "string", "secretKey": "string", "timeregistered": "2020-08-27T10:57:36.257820" }
invalid input, object invalid
Example response:
HTTP/1.1 400 Bad Request Content-Type: application/json {}
a user with the same oauth_id already exists
Example response:
HTTP/1.1 409 Conflict Content-Type: application/json {}
-
GET
/tagviews
¶ Get a list of TagViews.
Get a list of TagViews.
- Query Parameters
distinctOnTag (boolean) – Return only the latest TagView for each scanned tag.
tag_id (integer) – Filter TagViews by tag_id.
Example request:
GET /tagviews HTTP/1.1 Host: example.com
- Status Codes
200 OK –
A list of tagview objects ordered from newest to oldest.
Example response:
HTTP/1.1 200 OK Content-Type: application/json [ { "id": 1, "tagserial": "string", "timestamp": "2020-08-27T10:57:36.257820" } ]
-
GET
/tagviews/{id}
¶ Get a tagview
Get a tagview
- Parameters
id (integer) – Tag view ID
Example request:
GET /tagviews/{id} HTTP/1.1 Host: example.com
- Status Codes
200 OK –
A tagview object
Example response:
HTTP/1.1 200 OK Content-Type: application/json { "id": 1, "tagserial": "string", "timestamp": "2020-08-27T10:57:36.257820" }
TagView not found.
Example response:
HTTP/1.1 404 Not Found Content-Type: application/json {}
-
DELETE
/tagviews/{id}
¶ Delete a tag view
- Parameters
id (integer) – Tag view ID
- Status Codes
TagView deleted
Example response:
HTTP/1.1 204 No Content Content-Type: application/json {}
Bad input.
Example response:
HTTP/1.1 400 Bad Request Content-Type: application/json {}
TagView not found
Example response:
HTTP/1.1 404 Not Found Content-Type: application/json {}
Simulate¶
-
GET
/tag/{id}/simulate
¶ Simulate URL.
Obtain a simulated URL from the tag. This will contain mock sensor data.
- Parameters
id (integer) – Tag id to simulate
- Query Parameters
frontendurl (string) – URL of the consumer frontend application that will decode wscodec URLs. (Required)
nsamples (integer) – Number of temperature and humidity samples to include in the wscodec URL. 0 samples is valid and should raise an error.
smplintervalmins (integer) – Time interval between samples in minutes.
format (integer) – Format code e.g. 1 for temperature and humidity and 2 for temperarture samples only.
usehmac (boolean) – Use HMAC rather than MD5.
batvoltagemv (integer) – Battery voltage in mV.
bor (boolean) – Reset caused by Brownout.
svsh (boolean) – Reset caused by Supply Voltage Supervisor (High Side).
wdt (boolean) – Reset caused by watchdog timer.
misc (boolean) – Miscellaneous error flag on cupl Tag.
lpm5wu (boolean) – Low Power Mode x.5 wakeup flag.
clockfail (boolean) – Clock failure error flag.
tagerror (boolean) – Initialise encoder with the error flag set. The circular buffer will be empty.
Example request:
GET /tag/{id}/simulate?frontendurl=string HTTP/1.1 Host: example.com
- Status Codes
200 OK –
URL containing sensor data.
Example response:
HTTP/1.1 200 OK Content-Type: application/json string
bad input parameter
Example response:
HTTP/1.1 400 Bad Request Content-Type: application/json {}
Capture¶
-
GET
/capture
¶ get a capture.
Get a capture by its ID.
- Query Parameters
id (integer) – Capture id
Example request:
GET /capture HTTP/1.1 Host: example.com
- Status Codes
200 OK –
A capture object
Example response:
HTTP/1.1 200 OK Content-Type: application/json { "id": 1, "serial": "string", "statusb64": "string", "timeintb64": "string", "circbufb64": "string", "hmac": "string", "start-timestamp": "2020-08-27T10:57:36.257820", "end-timestamp": "2020-08-27T10:57:36.257820" }
bad input parameter
Example response:
HTTP/1.1 400 Bad Request Content-Type: application/json {}
-
DELETE
/capture
¶ delete a capture
delete a capture
- Query Parameters
id (integer) – Capture id
- Status Codes
Capture has been deleted
Example response:
HTTP/1.1 204 No Content Content-Type: application/json {}
bad input parameter
Example response:
HTTP/1.1 400 Bad Request Content-Type: application/json {}
No capture found
Example response:
HTTP/1.1 404 Not Found Content-Type: application/json {}
-
GET
/captures
¶ Get a list of captures ordered by ID.
Get a list of captures
- Query Parameters
offset (integer) – Return captures starting from this index.
limit (integer) – Limit the number of captures returned.
tag_id (integer) – Only returns captures from tag_id.
Example request:
GET /captures HTTP/1.1 Host: example.com
- Status Codes
200 OK –
List of captures
Example response:
HTTP/1.1 200 OK Content-Type: application/json [ { "id": 1, "serial": "string", "statusb64": "string", "timeintb64": "string", "circbufb64": "string", "hmac": "string", "start-timestamp": "2020-08-27T10:57:36.257820", "end-timestamp": "2020-08-27T10:57:36.257820" } ]
bad input parameter
Example response:
HTTP/1.1 400 Bad Request Content-Type: application/json {}
-
POST
/captures
¶ create a capture
Create a capture
Example request:
POST /captures HTTP/1.1 Host: example.com Content-Type: application/json { "id": 1, "serial": "string", "statusb64": "string", "timeintb64": "string", "circbufb64": "string", "hmac": "string", "start-timestamp": "2020-08-27T10:57:36.257820", "end-timestamp": "2020-08-27T10:57:36.257820" }
- Status Codes
Capture created
Example response:
HTTP/1.1 201 Created Content-Type: application/json { "id": 1, "serial": "string", "statusb64": "string", "timeintb64": "string", "circbufb64": "string", "hmac": "string", "start-timestamp": "2020-08-27T10:57:36.257820", "end-timestamp": "2020-08-27T10:57:36.257820" }
invalid input, object invalid
Example response:
HTTP/1.1 400 Bad Request Content-Type: application/json {}
a capture with the same id already exists
Example response:
HTTP/1.1 409 Conflict Content-Type: application/json {}
-
GET
/capture
¶ get a capture.
Get a capture by its ID.
- Query Parameters
id (integer) – Capture id
Example request:
GET /capture HTTP/1.1 Host: example.com
- Status Codes
200 OK –
A capture object
Example response:
HTTP/1.1 200 OK Content-Type: application/json { "id": 1, "serial": "string", "statusb64": "string", "timeintb64": "string", "circbufb64": "string", "hmac": "string", "start-timestamp": "2020-08-27T10:57:36.257820", "end-timestamp": "2020-08-27T10:57:36.257820" }
bad input parameter
Example response:
HTTP/1.1 400 Bad Request Content-Type: application/json {}
-
DELETE
/capture
¶ delete a capture
delete a capture
- Query Parameters
id (integer) – Capture id
- Status Codes
Capture has been deleted
Example response:
HTTP/1.1 204 No Content Content-Type: application/json {}
bad input parameter
Example response:
HTTP/1.1 400 Bad Request Content-Type: application/json {}
No capture found
Example response:
HTTP/1.1 404 Not Found Content-Type: application/json {}
-
GET
/captures
¶ Get a list of captures ordered by ID.
Get a list of captures
- Query Parameters
offset (integer) – Return captures starting from this index.
limit (integer) – Limit the number of captures returned.
tag_id (integer) – Only returns captures from tag_id.
Example request:
GET /captures HTTP/1.1 Host: example.com
- Status Codes
200 OK –
List of captures
Example response:
HTTP/1.1 200 OK Content-Type: application/json [ { "id": 1, "serial": "string", "statusb64": "string", "timeintb64": "string", "circbufb64": "string", "hmac": "string", "start-timestamp": "2020-08-27T10:57:36.257820", "end-timestamp": "2020-08-27T10:57:36.257820" } ]
bad input parameter
Example response:
HTTP/1.1 400 Bad Request Content-Type: application/json {}
-
POST
/captures
¶ create a capture
Create a capture
Example request:
POST /captures HTTP/1.1 Host: example.com Content-Type: application/json { "id": 1, "serial": "string", "statusb64": "string", "timeintb64": "string", "circbufb64": "string", "hmac": "string", "start-timestamp": "2020-08-27T10:57:36.257820", "end-timestamp": "2020-08-27T10:57:36.257820" }
- Status Codes
Capture created
Example response:
HTTP/1.1 201 Created Content-Type: application/json { "id": 1, "serial": "string", "statusb64": "string", "timeintb64": "string", "circbufb64": "string", "hmac": "string", "start-timestamp": "2020-08-27T10:57:36.257820", "end-timestamp": "2020-08-27T10:57:36.257820" }
invalid input, object invalid
Example response:
HTTP/1.1 400 Bad Request Content-Type: application/json {}
a capture with the same id already exists
Example response:
HTTP/1.1 409 Conflict Content-Type: application/json {}
TagView¶
-
GET
/tagviews
¶ Get a list of TagViews.
Get a list of TagViews.
- Query Parameters
distinctOnTag (boolean) – Return only the latest TagView for each scanned tag.
tag_id (integer) – Filter TagViews by tag_id.
Example request:
GET /tagviews HTTP/1.1 Host: example.com
- Status Codes
200 OK –
A list of tagview objects ordered from newest to oldest.
Example response:
HTTP/1.1 200 OK Content-Type: application/json [ { "id": 1, "tagserial": "string", "timestamp": "2020-08-27T10:57:36.257820" } ]
-
GET
/tagviews/{id}
¶ Get a tagview
Get a tagview
- Parameters
id (integer) – Tag view ID
Example request:
GET /tagviews/{id} HTTP/1.1 Host: example.com
- Status Codes
200 OK –
A tagview object
Example response:
HTTP/1.1 200 OK Content-Type: application/json { "id": 1, "tagserial": "string", "timestamp": "2020-08-27T10:57:36.257820" }
TagView not found.
Example response:
HTTP/1.1 404 Not Found Content-Type: application/json {}
-
DELETE
/tagviews/{id}
¶ Delete a tag view
- Parameters
id (integer) – Tag view ID
- Status Codes
TagView deleted
Example response:
HTTP/1.1 204 No Content Content-Type: application/json {}
Bad input.
Example response:
HTTP/1.1 400 Bad Request Content-Type: application/json {}
TagView not found
Example response:
HTTP/1.1 404 Not Found Content-Type: application/json {}
-
GET
/tagviews
¶ Get a list of TagViews.
Get a list of TagViews.
- Query Parameters
distinctOnTag (boolean) – Return only the latest TagView for each scanned tag.
tag_id (integer) – Filter TagViews by tag_id.
Example request:
GET /tagviews HTTP/1.1 Host: example.com
- Status Codes
200 OK –
A list of tagview objects ordered from newest to oldest.
Example response:
HTTP/1.1 200 OK Content-Type: application/json [ { "id": 1, "tagserial": "string", "timestamp": "2020-08-27T10:57:36.257820" } ]
-
GET
/tagviews/{id}
¶ Get a tagview
Get a tagview
- Parameters
id (integer) – Tag view ID
Example request:
GET /tagviews/{id} HTTP/1.1 Host: example.com
- Status Codes
200 OK –
A tagview object
Example response:
HTTP/1.1 200 OK Content-Type: application/json { "id": 1, "tagserial": "string", "timestamp": "2020-08-27T10:57:36.257820" }
TagView not found.
Example response:
HTTP/1.1 404 Not Found Content-Type: application/json {}
-
DELETE
/tagviews/{id}
¶ Delete a tag view
- Parameters
id (integer) – Tag view ID
- Status Codes
TagView deleted
Example response:
HTTP/1.1 204 No Content Content-Type: application/json {}
Bad input.
Example response:
HTTP/1.1 400 Bad Request Content-Type: application/json {}
TagView not found
Example response:
HTTP/1.1 404 Not Found Content-Type: application/json {}
User¶
Token¶
-
POST
/token
¶ Obtain a bearer token.
Obtain a JWT for interacting with this API.
Example request:
POST /token HTTP/1.1 Host: example.com Content-Type: application/json { "client_id": "string", "client_secret": "string" }
- Status Codes
200 OK –
A bearer token that can be used to make calls to other endpoints in this API.
Example response:
HTTP/1.1 200 OK Content-Type: application/json { "token": "string", "token_type": "string" }
No credentials supplied
Example response:
HTTP/1.1 400 Bad Request Content-Type: application/json {}
Bad credentials
Example response:
HTTP/1.1 401 Unauthorized Content-Type: application/json {}
Consumer API Specification¶
Tag¶
-
GET
/tag/{serial}
¶ Get a tag by its serial.
- Query Parameters
serial (string) – Tag serial
Example request:
GET /tag/{serial} HTTP/1.1 Host: example.com
- Status Codes
200 OK –
A tag object
Example response:
HTTP/1.1 200 OK Content-Type: application/json { "serial": "string", "timeregistered": "2020-08-27T10:57:36.257820" }
Bad input parameter.
Example response:
HTTP/1.1 400 Bad Request Content-Type: application/json {}
404 Not Found – Tag not found.
Scanned¶
Determines if a tag has been scanned by the current user.
-
GET
/tag/{serial}/scanned
¶ Has a tag with a given serial been scanned by the current user?
- Query Parameters
serial (string) – Tag serial (Required)
Example request:
GET /tag/{serial}/scanned?serial=string HTTP/1.1 Host: example.com
- Status Codes
200 OK – True if the tag has a capture taken by the current user.
Bad input parameter
Example response:
HTTP/1.1 400 Bad Request Content-Type: application/json {}
Tag or user not found.
Example response:
HTTP/1.1 404 Not Found Content-Type: application/json {}
Captures¶
-
GET
/captures
¶ Get a list of captures for a tag
- Query Parameters
serial (string) – Tag serial (Required)
offset (integer) – Return samples starting from this index.
limit (integer) – Limit the number of samples returned.
Example request:
GET /captures?serial=string HTTP/1.1 Host: example.com
- Status Codes
200 OK –
A capture object
Example response:
HTTP/1.1 200 OK Content-Type: application/json { "batvoltagemv": 1, "tagserial": "string", "cursorpos": 1, "id": 1, "loopcount": 1, "md5": "string", "status": { "brownout": true, "clockfail": true, "id": 1, "lpm5wakeup": true, "misc": true, "parent_capture": 1, "resetsalltime": 1, "supervisor": true, "watchdog": true }, "timeintmins": 1, "timestamp": "2020-08-27T10:57:36.257820", "version": 1 }
invalid input, object invalid
Example response:
HTTP/1.1 400 Bad Request Content-Type: application/json {}
Tag with serial not found.
Example response:
HTTP/1.1 404 Not Found Content-Type: application/json {}
-
POST
/captures
¶ Create a capture
- Status Codes
200 OK –
A capture object
Example response:
HTTP/1.1 200 OK Content-Type: application/json { "batvoltagemv": 1, "tagserial": "string", "cursorpos": 1, "id": 1, "loopcount": 1, "md5": "string", "status": { "brownout": true, "clockfail": true, "id": 1, "lpm5wakeup": true, "misc": true, "parent_capture": 1, "resetsalltime": 1, "supervisor": true, "watchdog": true }, "timeintmins": 1, "timestamp": "2020-08-27T10:57:36.257820", "version": 1 }
invalid input, object invalid
Example response:
HTTP/1.1 400 Bad Request Content-Type: application/json {}
Not authorised. HMAC does not correspond to input data.
Example response:
HTTP/1.1 401 Unauthorized Content-Type: application/json {}
Tag not found
Example response:
HTTP/1.1 404 Not Found Content-Type: application/json {}
Conflict. A capture with this HMAC already exists. Dead battery or replay attack.
Example response:
HTTP/1.1 409 Conflict Content-Type: application/json {}
-
GET
/captures/{id}
¶ Get a capture by ID
Example request:
GET /captures/{id} HTTP/1.1 Host: example.com
- Status Codes
200 OK –
A capture object
Example response:
HTTP/1.1 200 OK Content-Type: application/json { "batvoltagemv": 1, "tagserial": "string", "cursorpos": 1, "id": 1, "loopcount": 1, "md5": "string", "status": { "brownout": true, "clockfail": true, "id": 1, "lpm5wakeup": true, "misc": true, "parent_capture": 1, "resetsalltime": 1, "supervisor": true, "watchdog": true }, "timeintmins": 1, "timestamp": "2020-08-27T10:57:36.257820", "version": 1 }
invalid input, object invalid
Example response:
HTTP/1.1 400 Bad Request Content-Type: application/json {}
Capture not found.
Example response:
HTTP/1.1 404 Not Found Content-Type: application/json {}
Samples¶
Returns samples for a given capture.
-
GET
/captures/{id}/samples
¶ Get samples for a capture.
- Query Parameters
offset (integer) – Return samples starting from this index.
limit (integer) – Limit the number of samples returned.
Example request:
GET /captures/{id}/samples HTTP/1.1 Host: example.com
- Status Codes
200 OK –
A list of sample objects
Example response:
HTTP/1.1 200 OK Content-Type: application/json [ { "capture_id": 1, "id": 1, "location": { "capturesample_id": 1, "description": "string", "id": 1, "timestamp": "2020-08-27T10:57:36.257820" }, "rh": 1.0, "temp": 1.0, "timestamp": "2020-08-27T10:57:36.257820" } ]
bad input parameter
Example response:
HTTP/1.1 400 Bad Request Content-Type: application/json {}
Samples¶
-
GET
/samples
¶ Get unique samples for a tag in a given time range
- Query Parameters
serial (string) – Tag serial (Required)
starttime (string) – start timestamp as an ISO-8601 string. (Required)
endtime (string) – end timestamp as an ISO-8601 string. (Required)
offset (integer) – Return samples starting from this index.
limit (integer) – Limit the number of samples returned.
Example request:
GET /samples?serial=string&starttime=string&endtime=string HTTP/1.1 Host: example.com
- Status Codes
200 OK –
A list of samples from newest to oldest
Example response:
HTTP/1.1 200 OK Content-Type: application/json [ { "capture_id": 1, "id": 1, "location": { "capturesample_id": 1, "description": "string", "id": 1, "timestamp": "2020-08-27T10:57:36.257820" }, "rh": 1.0, "temp": 1.0, "timestamp": "2020-08-27T10:57:36.257820" } ]
bad input parameter
Example response:
HTTP/1.1 400 Bad Request Content-Type: application/json {}
Users¶
-
POST
/users
¶ Create a new user from the Auth0 access token.
- Status Codes
201 Created – User created
Invalid input, object invalid
Example response:
HTTP/1.1 400 Bad Request Content-Type: application/json {}
Invalid JWT
Example response:
HTTP/1.1 403 Forbidden Content-Type: application/json {}
Conflict. User already exists.
Example response:
HTTP/1.1 409 Conflict Content-Type: application/json {}
Me¶
-
GET
/me
¶ Get current user from the Auth0 access token.
Auth0 ID of the user.
Example request:
GET /me HTTP/1.1 Host: example.com
- Status Codes
200 OK –
A user object
Example response:
HTTP/1.1 200 OK Content-Type: application/json { "id": 1, "oauth_id": "string", "roles": "string", "userinfo": { "family_name": "string", "given_name": "string", "locale": "string", "name": "string", "nickname": "string", "picture": "string", "sub": "string", "updated_at": "2020-08-27T10:57:36.257820" } }
invalid JWT
Example response:
HTTP/1.1 403 Forbidden Content-Type: application/json {}
-
DELETE
/me
¶ Delete current user.
- Status Codes
User deleted
Example response:
HTTP/1.1 204 No Content Content-Type: application/json {}
Bad input parameter
Example response:
HTTP/1.1 400 Bad Request Content-Type: application/json {}
User not found
Example response:
HTTP/1.1 404 Not Found Content-Type: application/json {}
Captures¶
-
GET
/me/captures
¶ Get a list of captures taken by the current user ordered by most recent first.
- Query Parameters
distinctOnTag (boolean) – Return only the latest capture for each scanned tag.
Example request:
GET /me/captures HTTP/1.1 Host: example.com
- Status Codes
200 OK –
A list of capture objects ordered from newest to oldest
Example response:
HTTP/1.1 200 OK Content-Type: application/json [ { "batvoltagemv": 1, "tagserial": "string", "cursorpos": 1, "id": 1, "loopcount": 1, "md5": "string", "status": { "brownout": true, "clockfail": true, "id": 1, "lpm5wakeup": true, "misc": true, "parent_capture": 1, "resetsalltime": 1, "supervisor": true, "watchdog": true }, "timeintmins": 1, "timestamp": "2020-08-27T10:57:36.257820", "version": 1 } ]
invalid input, object invalid
Example response:
HTTP/1.1 400 Bad Request Content-Type: application/json {}
Not authorised. HMAC does not correspond to input data or invalid JWT.
Example response:
HTTP/1.1 401 Unauthorized Content-Type: application/json {}
Tag not found.
Example response:
HTTP/1.1 404 Not Found Content-Type: application/json {}
-
POST
/me/captures
¶ Create a capture for a user
- Status Codes
200 OK –
A capture object
Example response:
HTTP/1.1 200 OK Content-Type: application/json { "batvoltagemv": 1, "tagserial": "string", "cursorpos": 1, "id": 1, "loopcount": 1, "md5": "string", "status": { "brownout": true, "clockfail": true, "id": 1, "lpm5wakeup": true, "misc": true, "parent_capture": 1, "resetsalltime": 1, "supervisor": true, "watchdog": true }, "timeintmins": 1, "timestamp": "2020-08-27T10:57:36.257820", "version": 1 }
Invalid input, object invalid
Example response:
HTTP/1.1 400 Bad Request Content-Type: application/json {}
Not authorised. HMAC does not correspond to input data.
Example response:
HTTP/1.1 401 Unauthorized Content-Type: application/json {}
Not authorised. Invalid JWT.
Example response:
HTTP/1.1 403 Forbidden Content-Type: application/json {}
Parent tag or user not found.
Example response:
HTTP/1.1 404 Not Found Content-Type: application/json {}
Conflict. A capture with this HMAC already exists. Dead battery or replay attack.
Example response:
HTTP/1.1 409 Conflict Content-Type: application/json {}
TagViews¶
-
GET
/me/tagviews
¶ Get a list of TagViews for the current user.
Auth0 ID of the user.
- Query Parameters
distinctOnTag (boolean) – Return only the latest TagView for each scanned tag.
Example request:
GET /me/tagviews HTTP/1.1 Host: example.com
- Status Codes
200 OK –
A list of tagview objects ordered from newest to oldest.
Example response:
HTTP/1.1 200 OK Content-Type: application/json [ { "tagserial": "string", "id": 1, "timestamp": "2020-08-27T10:57:36.257820" } ]
Invalid JWT
Example response:
HTTP/1.1 403 Forbidden Content-Type: application/json {}
-
POST
/me/tagviews
¶ Post a tag view
- Status Codes
TagView created
Example response:
HTTP/1.1 201 Created Content-Type: application/json { "tagserial": "string", "id": 1, "timestamp": "2020-08-27T10:57:36.257820" }
Invalid input, object invalid
Example response:
HTTP/1.1 400 Bad Request Content-Type: application/json {}
Invalid JWT
Example response:
HTTP/1.1 403 Forbidden Content-Type: application/json {}
Parent resource not found.
Example response:
HTTP/1.1 404 Not Found Content-Type: application/json {}
-
GET
/me/tagviews/{id}
¶ Get a tagview for the current user
Auth0 ID of the user.
- Parameters
id (integer) – Tag view ID
Example request:
GET /me/tagviews/{id} HTTP/1.1 Host: example.com
- Status Codes
200 OK –
A tagview object
Example response:
HTTP/1.1 200 OK Content-Type: application/json { "tagserial": "string", "id": 1, "timestamp": "2020-08-27T10:57:36.257820" }
Invalid JWT
Example response:
HTTP/1.1 403 Forbidden Content-Type: application/json {}
TagView not found.
Example response:
HTTP/1.1 404 Not Found Content-Type: application/json {}
-
DELETE
/me/tagviews/{id}
¶ Delete tag view from the current user.
- Status Codes
TagView deleted
Example response:
HTTP/1.1 204 No Content Content-Type: application/json {}
Bad input.
Example response:
HTTP/1.1 400 Bad Request Content-Type: application/json {}
TagView not found
Example response:
HTTP/1.1 404 Not Found Content-Type: application/json {}
Locations¶
-
GET
/locations
¶ Get a list of locations for a tag ordered by most recent
- Query Parameters
serial (string) – Tag serial (Required)
starttime (string) – start timestamp as an ISO-8601 string.
endtime (string) – end timestamp as an ISO-8601 string.
Example request:
GET /locations?serial=string HTTP/1.1 Host: example.com
- Status Codes
200 OK –
A list of locations ordered from newest to oldest.
Example response:
HTTP/1.1 200 OK Content-Type: application/json [ { "capturesample_id": 1, "description": "string", "id": 1, "timestamp": "2020-08-27T10:57:36.257820" } ]
invalid input, object invalid
Example response:
HTTP/1.1 400 Bad Request Content-Type: application/json {}
Location not found.
Example response:
HTTP/1.1 404 Not Found Content-Type: application/json {}
-
POST
/locations
¶ Add location information to a tag
- Status Codes
200 OK –
A capture object
Example response:
HTTP/1.1 200 OK Content-Type: application/json { "capturesample_id": 1, "description": "string", "id": 1, "timestamp": "2020-08-27T10:57:36.257820" }
invalid input, object invalid
Example response:
HTTP/1.1 400 Bad Request Content-Type: application/json {}
Not authorised. The user has no scanned this tag.
Example response:
HTTP/1.1 401 Unauthorized Content-Type: application/json {}
Not authorised. Invalid JWT.
Example response:
HTTP/1.1 403 Forbidden Content-Type: application/json {}
Parent resource not found.
Example response:
HTTP/1.1 404 Not Found Content-Type: application/json {}
-
GET
/locations/{id}
¶ Get a list of locations for a tag ordered by most recent
- Parameters
id (integer) – Location ID
Example request:
GET /locations/{id} HTTP/1.1 Host: example.com
- Status Codes
200 OK –
A list of locations ordered from newest to oldest.
Example response:
HTTP/1.1 200 OK Content-Type: application/json { "capturesample_id": 1, "description": "string", "id": 1, "timestamp": "2020-08-27T10:57:36.257820" }
invalid input, object invalid
Example response:
HTTP/1.1 400 Bad Request Content-Type: application/json {}
Location not found.
Example response:
HTTP/1.1 404 Not Found Content-Type: application/json {}
-
DELETE
/locations/{id}
¶ Delete a location
- Status Codes
Location deleted
Example response:
HTTP/1.1 204 No Content Content-Type: application/json {}
Bad input parameter
Example response:
HTTP/1.1 400 Bad Request Content-Type: application/json {}
Not authorised. The user has no scanned this tag.
Example response:
HTTP/1.1 401 Unauthorized Content-Type: application/json {}
Not authorised. Invalid JWT.
Example response:
HTTP/1.1 403 Forbidden Content-Type: application/json {}
Location not found.
Example response:
HTTP/1.1 404 Not Found Content-Type: application/json {}
-
PATCH
/locations/{id}
¶ Edit location information for a tag
- Status Codes
200 OK –
A capture object
Example response:
HTTP/1.1 200 OK Content-Type: application/json { "capturesample_id": 1, "description": "string", "id": 1, "timestamp": "2020-08-27T10:57:36.257820" }
invalid input, object invalid
Example response:
HTTP/1.1 400 Bad Request Content-Type: application/json {}
Not authorised. The user has no scanned this tag.
Example response:
HTTP/1.1 401 Unauthorized Content-Type: application/json {}
Not authorised. Invalid JWT.
Example response:
HTTP/1.1 403 Forbidden Content-Type: application/json {}
Parent resource not found.
Example response:
HTTP/1.1 404 Not Found Content-Type: application/json {}
Develop Locally¶
wsbackend has external dependencies such as a database and an identity provider. It is designed to be deployed to a cloud provider. Nonetheless it is easy to test this application locally without an internet connection.
Add / Edit an API endpoint¶
Update Specification¶
The API specification should be updated first. These are contained in files named api.yaml
,
which conform to the Swagger OpenAPI standard. It is sometimes easier to edit these files
by copying them into and out of the Swagger web application.
Write a Test that Fails¶
Tavern is used for API testing. Simple tests are defined within yaml files that reside in the tests directory.
The script conftest.py contains pytest fixtures. These obtain access tokens or read environment variables on behalf of test scripts.
Executing py.test
will run all tests against a live wsbackend server. Its address is set
by environment variables:
WSB_PROTOCOL
WSB_HOST
WSB_PORT
The newly developed application must be
installed and made to serve pages at this address first! To automate this process I have created a
Docker Compose file docker-compose.test.yml
for quickly running tests locally.
First build all images with:
docker-compose -f docker-compose.test.yml build
This will take a few minutes the first time, but afterwards the docker layers will be cached so it should copy your new files in within seconds.
Next run tests with:
docker-compose -f docker-compose.test.yml up
Implement feature. Test until success¶
Push to GitHub. Tests will run nodejs.yml. A docker image is tested and built on every pull request. Readthedocs will execute on webookhook. If tagging the build then package is deployed to pypi. Build docker image.
Write a Frontend Application¶
When writing frontend applications you may want to deploy wsbackend locally. To do this I have
created docker-compose.yml
.
Consumer API Authorization¶
Some API endpoints require authorization. Only users with a third party account (e.g. Google or Facebook) are granted access. Such accounts cannot be set up without some human interaction. The requirement for user authentication guards against bots filling the wsbackend database with rubbish.
The websensor Web Application uses the Open ID Connect (OIDC) protocol to communicate with a third-party identity provider (IdP). When a user authenticates, the IdP produces an access token. This token is unique to a user and decodes to a number of claims. These include:
User name.
Audience (URL of the websensor API for which access has been granted).
Issuer (URL of the identity provider).
Issued timestamp.
Expiry timestamp.
Scope (granular API permissions).
Tokens are not encrypted. They must always be transmitted through a secure channel (HTTPS). The value of tokens is they include a digital signature. If signature verification is successful then the claims can be trusted. In this way access tokens are used to access protected API resources.
The access token also grants the Web Application permission to read some personal data about
a user (e.g. name and profile picture). Crucially these data are not stored in the Web Application itself.
They are requested by making an
API call to /userinfo
API endpoint of the IdP. It can be assumed that the IdP handles these data
in a secure and GDPR compliant way.
Production¶
In a production environment, the IdP is Auth0.com. Others can be used if they adhere to the OIDC protocol.
Auth0.com acts as an intermediary. It allows users to authenticate with a large number of OAuth2 providers such as Google, GitHub and Facebook.
Obtain an API Access Token¶
wsfrontend obtains an access token fom the identify provider using the Authorization Code Grant Flow.
Protected API Resource is Called¶
wsfrontend calls wsbackend endpoints with the access token:
curl -X GET "{WSB_HOST}:{WSB_PORT}/api/consumer/v1/me" -H "accept: application/json" -H "Authorization: Bearer eyJhbGciOiJS... ZOA4t7Q"
Access Token is Validated¶
The access token signature is generated asymetrically (RS-256). A private key (on Auth0.com) generates the signature. A public key (hosted by Auth0.com) is used for validation.
wsbackend downloads the public_key
(JWKs) from Auth0.com:
GET {IDP_ORIGIN}{IDP_JWKS} => GET https://plotsensor.eu.auth0.com/.well-known/jwks.json
Signature verification and decoding are performed using PyJWT:
decoded = jwt.decode(
token,
public_key,
algorithms=self.algorithms,
audience={API_AUDIENCE},
issuer={IDP_ORIGIN}
)
If validation fails, an exception is raised. The token is rejected and the API
responds with an error 403: Forbidden
.
Protected Resource Content are Served¶
If validation succeeds, wsbackend transmits a 200 OK
response to the wsfrontend, along with the requested resource data.
Testing¶
For test, the OIDC provider is substituted with a mock https://www.npmjs.com/package/oauth2-mock-server. The test workflow
sets this up first on http://127.0.0.1:3000
.
Obtain an API Access Token¶
Access tokens are obtained from this using the client-creditials OAuth2 flow.
Test Scripts Call Protected API Resources¶
wsbackend endpoints are called with the access token in the HTTP header.
Access Token Validated¶
wsbackend downloads a public_key
from the mock provider JWKs endpoint. from the mock provider on port 3000:
GET {IDP_ORIGIN}{IDP_JWKS} => GET http://127.0.0.1:3000/jwks
Userinfo can also be mocked up.
wsapiwrapper¶
Installation¶
Install from pip.
Reference¶
Admin API Wrapper¶
Tag¶
-
class
wsapiwrapper.admin.tag.
TagFormat
(value)[source]¶ An enumeration.
-
FORMAT_HDC2021_TEMPONLY
= 2¶
-
FORMAT_HDC2021_TRH
= 1¶
-
-
class
wsapiwrapper.admin.tag.
TagWrapper
(baseurl: str, adminapi_token: str)[source]¶ Wraps calls to tag endpoints on the Admin API.
-
post
(serial: str = None, secretkey: str = None, fwversion: str = None, hwversion: str = None, description: str = None) → dict[source]¶ Make a POST request to the Tag endpoint.
- Parameters
serial – Tag serial string (8 characters).
secretkey – Secret key (16 characters).
fwversion – Tag firmware version.
hwversion – Tag hardware version.
description – Tag description.
- Returns
dict: A dictionary representing the new tag object.
-
simulate
(tagid: int, frontendurl: str, nsamples: int = 100, smplintervalmins: int = 100, tagformat: wsapiwrapper.admin.tag.TagFormat = <TagFormat.FORMAT_HDC2021_TRH: 1>, usehmac: bool = False, batvoltagemv: int = 3000, bor: bool = False, svsh: bool = False, wdt: bool = False, misc: bool = False, lpm5wu: bool = False, clockfail: bool = False, tagerror: bool = False) → str[source]¶ Make a GET request to the Tag simulate endpoint.
- Parameters
tagid – Database ID of the tag to simulate.
frontendurl – URL of a cuplfrontend instance that will be contained in the simulated tag URL.
nsamples – Number of samples to put in the simulated tag URL.
smplintervalmins – Time interval between samples in minutes.
tagformat – Indicates the datatype for each sample.
usehmac – Specifies whether the hash is HMAC-MD5 or just MD5.
batvoltagemv – Battery voltage of the simulated tag in mV.
bor – Brown-out-Reset flag.
svsh – Supply Voltage Supervisor (high-side) reset flag.
wdt – Watchdog reset flag.
misc – Miscellaneous reset flag.
lpm5wu – Low power mode wake-up flag.
clockfail – Clock failure reset flag.
tagerror – Specify a tag error URL where the circular buffer is omitted.
- Returns
A string containing a simulated tag URL
- Return type
-
Capture¶
-
class
wsapiwrapper.admin.capture.
CaptureWrapper
(baseurl: str, adminapi_token: str)[source]¶ Wraps calls to capture endpoints on the Admin API.
-
get_many
(offset: int = 0, limit: int = None, tag_id: int = None) → list[source]¶ Makes a GET request to endpoint_many.
-
post
(capturepayload: dict)[source]¶ Create a capture in the database directly.
The is made from a dictionary including a list of samples, a tag_id and a user_id. The endpoint is used for testing. It removes the need to encode test vectors with wscodec (introduces a second point of failure in an external library).
Captures can be created with a known list of samples. Then samples are collected from the tag using the consumer API. These are compared with a vector of expected samples. If captures overlap slightly in time it is expected that duplicate samples are removed.
- Parameters
capturepayload (dict) – Dictionary following the capture schema.
Returns: HTTP response from wsbackend.
-
-
class
wsapiwrapper.admin.
AdminApiWrapper
(baseurl: str, tokenstr: str, endpoint_one: str, endpoint_many: str)[source]¶ Wraps calls to the wsbackend Admin API
The Admin API is intended for administrators.
-
__init__
(baseurl: str, tokenstr: str, endpoint_one: str, endpoint_many: str)[source]¶ Constructor for AdminApiWrapper
- Parameters
baseurl (str) – Websensor backend URL.
adminapi_client_id (str) – Client ID API access credential. A long base64 string e.g. SVpP…kO8
adminapi_client_secret (str) – Client Secret API access credential. A long base64 string e.g. CM300…1aVB
endpoint_one (str) – Endpoint for returning one resource instance.
endpoint_many (str) – Endpoint for returning a list of resource instances.
-
delete
(id: int)[source]¶ Make a DELETE request to the Tag endpoint.
- Parameters
id (int) – ID of the resource to delete
-
-
wsapiwrapper.admin.
request_admin_token
(baseurl: str, adminapi_client_id: str, adminapi_client_secret: str) → str[source]¶ Request a token from the token endpoint.
A client_id and client_secret are exchanged for a token. This uses the OAuth Client Credentials flow:
A POST request is made to the token endpoint of wsbackend.
Client ID and Client Secret are validated.
Access token is returned.
- Returns
token received from wsbackend.
- Return type
Consumer API Wrapper¶
Me¶
-
class
wsapiwrapper.consumer.user.
UserWrapper
(baseurl: str, tokenstr: str = None)[source]¶ Wraps users and me endpoints of the Consumer API. Consumers can retrieve more information about themselves (me) than they can about other users.
-
delete
() → None[source]¶ Delete current user.
Consumer API permits users to delete their own database entry.
Makes a DELETE request to the Me endpoint.
Current user is identified by an access token passed to the
constructor
.- Returns
None
-
get
() → dict[source]¶ Get current user.
Retrieves database record for the current user.
Makes a GET request to the Me endpoint.
Current user is identified by an access token passed to the
constructor
.- Returns
API representation of the current user.
- Return type
-
TagView¶
-
class
wsapiwrapper.consumer.tagview.
TagViewWrapper
(baseurl: str, tokenstr: str)[source]¶ -
-
get
(distinct: bool = False) → list[source]¶ Get a list of tag views by the current user.
Current user is identified by an API access token passed to the
constructor
.
-
Captures¶
-
class
wsapiwrapper.consumer.mecapture.
MeCaptureWrapper
(baseurl: str, tokenstr: str = None)[source]¶ Create and retrieve captures linked to the current user.
-
get
(distinct: bool = False)[source]¶ Get a list of captures made by the current user.
Makes a GET request to the MeCaptures endpoint.
Current user is identified by an access token passed to the
constructor
.
-
post
(circbufb64: str, serial: str, statusb64: str, timeintb64: str, versionStr: str) → dict[source]¶ Create a new capture. Record that it was made by the current user.
See
wsapiwrapper.consumer.capture.CaptureWrapper.post()
.Current user is identified by an access token passed to the
constructor
.
-
Tag¶
-
class
wsapiwrapper.consumer.tag.
TagWrapper
(baseurl: str, tokenstr: str = None)[source]¶ Wraps calls to tag endpoints on the Consumer API
Scanned¶
Wraps a Consumer API endpoint for determining if a tag has been scanned by a user.
Makes a GET request to the TagScanned Consumer API endpoint.
Current user is identified by an access token passed to the
constructor
.
Capture¶
-
class
wsapiwrapper.consumer.capture.
CaptureWrapper
(baseurl: str, tokenstr: str = None)[source]¶ Wraps capture endpoints of the Consumer API.
-
get
(capture_id: int) → dict[source]¶ Get one capture by its ID.
Makes a GET request to the Capture endpoint.
-
get_list
(serial: str, offset: int = 0, limit: int = None) → list[source]¶ Get a list of captures for a tag ordered newest first.
The list can be paginated so that each call returns ‘limit’ captures.
-
get_samples
(capture_id: int, offset: int = 0, limit: int = None) → list[source]¶ Get a list of samples from a capture.
Makes a GET request to the CaptureSamples endpoint.
The list can be paginated so that each call returns ‘limit’ samples at an ‘offset’ from the first.
-
post
(circbufb64: str, serial: str, statusb64: str, timeintb64: str, vfmtb64: str) → dict[source]¶ Create a new capture from parameters encoded by wscodec.
These data are included in URL parameters passed to wsfrontend when a tag is scanned.
Makes a POST request to the Capture endpoint, which unwraps the circular buffer and decodes samples.
- Parameters
circbufb64 (str) – Circular buffer containing base64 encoded samples. Ouptut by wscodec.
serial (str) – Base64 serial identifying the tag the capture has originated from.
statusb64 (str) – Base64 encoded status information (e.g. battery level). Output by wscodec.
timeintb64 (str) – Base64 encoded time interval between samples. Output by wscodec.
vfmtb64 (str) – Tag version string. See wscodec.
- Returns
Capture dictionary.
- Return type
-
Sample¶
-
class
wsapiwrapper.consumer.sample.
SampleWrapper
(baseurl: str, tokenstr: str = None)[source]¶ Wraps samples endpoint of the Consumer API.
-
get_samples
(serial: str, starttime: str, endtime: str, offset: int = 0, limit: int = None) → list[source]¶ Get a list of temperature/humidity samples collected by a tag.
A time window can be specified so that only samples that fall between starttime and endtime are returned.
Makes a GET request to the Samples endpoint.
- Parameters
- Returns
A list of samples. Each is a dictionary.
- Return type
-
Location¶
-
class
wsapiwrapper.consumer.location.
LocationWrapper
(baseurl: str, tokenstr: str)[source]¶ Wraps location endpoints of the Consumer API.
-
delete
(location_id: int) → int[source]¶ Delete one location by ID.
Makes a DELETE request to the Location endpoint. To delete locations the current user must have scanned the tag.
-
get
(location_id: int) → dict[source]¶ Get one location by its ID.
Makes a GET request to the Location endpoint.
-
get_list
(tagserial: str, starttime: str = None, endtime: str = None) → list[source]¶ Get a list of locations for a tag.
Optionally a time window can be specified, so that only location timestamps within that window will be returned.
Makes a GET request to the Locations endpoint.
- Parameters
tagserial – Base64 serial identifying a tag.
starttime – Start datetime as an ISO-8601 string.
endtime – End datetime as an ISO-8601 string.
- Returns
A list of Location dictionaries.
- Return type
-
patch
(location_id: int, description: str) → dict[source]¶ Change description of an existing location.
The current user must have scanned the tag.
-
post
(capturesample_id: int, description: str) → dict[source]¶ Annotate a sample with location.
The timestamp of a location corresponds to that of its parent sample. All samples can be traced back to the tag that created them. This must have been scanned by the current user.
-
static
process_status
(status_code: int, desc='') → None[source]¶ Raise an exception in response to an HTTP error code.
- Parameters
status_code – HTTP status code.
-
-
class
wsapiwrapper.consumer.
ConsumerApiWrapper
(baseurl: str, tokenstr: str = None)[source]¶
-
exception
wsapiwrapper.consumer.
Exception401
(message='Not authorised to access this resource. Invalid JWT or bad HMAC.')[source]¶
wsbackend¶
Installation¶
This tutorial assumes you have Git and Python 3 installed.
Install Prerequisites¶
One of the dependencies (Postgres library Psycopg2) should be built from source.
GCC Compiler¶
To install GCC on Debian, open a terminal window and type:
$ sudo apt install build-essential
To check gcc is installed, type:
$ gcc --version
Clone the Repo¶
First you will need to download wsbackend by cloning its Git repository. Open a terminal window, browse to a folder of your choice and type:
$ git clone https://github.com/websensor/wsbackend.git
The GitHub repository will be cloned to a folder named wsbackend
.
Open this by typing:
$ cd wsbackend
Create a Virtual Environment¶
We will use virtualbox to create a virtual environment. This will isolate our wsbackend installation from the rest of the system.
First install virtualenv:
$ sudo pip3 install virtualenv
Next create a virtual environment named wsbackend_env
:
$ virtualenv wsbackend_env
Activate the Virtual Environment¶
In order for pip
to install dependencies into the virtual enviornment, you must activate it first with:
$ source wsbackend_env/bin/activate
Install Dependencies¶
wsbackend is dependent on a number of Python packages including flask. These are listed in requirements.txt.
To install all requirements, type:
$ pip3 install -r requirements.txt
Environment Variables¶
Host address¶
Identity Provider¶
IDP_PROTOCOL
¶
Default: http://
Protocol of the OpenID Connect Identity Provider.
Defaults to the protocol of the mock IdP.
IDP_JWKS
¶
Default: /jwks
Endpoint on the IdP from which the public JSON Web Key set can be downloaded.
Defaults to the mock IdP JWKs endpoint.
Database¶
DB_PASS
¶
Database password.
No default for security reasons. This variable must be set before runtime.
Consumer API¶
API_ISSUER
¶
Default: http://localhost:3000
API issuer claim. Must correspond to the issuer of the API access token.
API_AUDIENCE
¶
Default: mock_api_audience
Access tokens signed by the IdP must contain this audience claim. Without it, the consumer API will reject the token and deny access to an endpoint that requires authorization.
Defaults to a value used by the mock IdP.
Admin API¶
ADMINAPI_AUDIENCE
¶
Default: default_adminapi_audience
Access tokens issued using the client credentials flow will contain this audience claim.
ADMINAPI_CLIENTID
¶
Default: default_adminapi_clientid
A token request using the client credentials flow
must contain this client_id
. This is a shared secret between the sender and wsbackend
.
ADMINAPI_CLIENTSECRET
¶
A token request using the client credentials flow must contain this client_secret
.
This is a shared secret between the sender and wsbackend
.
No default for security reasons. This variable must be set before runtime.