Created
February 24, 2024 19:04
-
-
Save daler/35f5aaa806529bed2a67aadc6882f6c8 to your computer and use it in GitHub Desktop.
LA API
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
Requirements: | |
- access to an external, publicly-accessible server. The user | |
authentication will redirect here. You can tail the logs on that server to | |
see the response. | |
- Access Key ID (akid) from LabArchives support. This requires a ticket and | |
approval from whoever is running the LabArchives instance. It is expected | |
that this is passed as an env var, LA_AKID. | |
- Password, from LabArchives support. Provided out-of-band. It is expected | |
that this is passed as an env var, LA_PASSWD. | |
""" | |
import hmac | |
import hashlib | |
import os | |
import base64 | |
import urllib | |
import requests | |
import time | |
class API(object): | |
def __init__( | |
self, | |
base_url="https://api.labarchives.com", | |
akid=None, | |
passwd=None, | |
expires=None, | |
): | |
self.base_url = base_url | |
self.akid = akid or os.getenv("LA_AKID") | |
self.passwd = passwd or os.getenv("LA_PASSWD") | |
if not expires: | |
# one minute in the future | |
self.expires = str(int(time.time_ns() / 1e6 + (60 * 1e3))) | |
else: | |
self.expires = expires | |
def get_auth(self, method): | |
""" | |
Returns a signature to be used as part of API calls. | |
Parameters | |
---------- | |
method : str | |
Only the part used for making the signature. E.g., | |
"entry_attachment" rather than "api/entries/entry_attachment". | |
""" | |
msg = self.akid + method + self.expires | |
sig = hmac.new(self.passwd.encode(), msg.encode(), hashlib.sha512) | |
auth = urllib.parse.quote(base64.b64encode(sig.digest()), safe="") | |
return auth | |
def compose_url(self, endpoint, split=True, **kwargs): | |
""" | |
Returns a URL with all the components. | |
Parameters | |
---------- | |
endpoint : str | |
Full endpoint, e.g., "api/entries/entry_attachment" | |
split : bool | |
If True, take only the last component of the endpoint, e.g. | |
"entry_attachment" in the example above; this will be provided as | |
`method` to self.get_auth. Set to False and provide the redirect | |
URI when getting | |
kwargs are passed on as URL parameters. | |
""" | |
if split: | |
method = os.path.split(endpoint)[-1] | |
else: | |
method = endpoint | |
sig = self.get_auth(method=method) | |
params = "&".join([f"{k}={v}" for k, v in kwargs.items()]) | |
return f"{self.base_url}/{endpoint}?{params}&akid={self.akid}&expires={self.expires}&sig={sig}" | |
def get_uid(self, redirect_uri): | |
sig = self.get_auth(method=redirect_uri) | |
encoded_uri = urllib.parse.quote(redirect_uri, safe="") | |
return f"{self.base_url}/api_user_login?akid={self.akid}&expires={self.expires}&redirect_uri={encoded_uri}&sig={sig}" | |
# verify that signature matches docs | |
example = API( | |
akid="0234wedkfjrtfd34er", | |
expires="264433207000", | |
passwd="1234567890", | |
base_url="https://<base_url>", | |
) | |
result = example.compose_url( | |
"api/entries/entry_attachment", | |
uid="285489257Ho's9^Lt4116011183268315271", | |
filename="myfile.doc", | |
) | |
# From https://mynotebook.labarchives.com/share/LabArchives%20API/NS4yfDI3LzQvVHJlZU5vZGUvMTF8MTMuMg== | |
expected = ( | |
"https://<base_url>/api/entries/entry_attachment" | |
"?uid=285489257Ho's9^Lt4116011183268315271" | |
"&filename=myfile.doc" | |
"&akid=0234wedkfjrtfd34er" | |
"&expires=264433207000" | |
"&sig=mT7pS%2BKgqlNseR0bo4YLQOVIsgOugMWzlQGllInXS25Q7VpA6lRmL0nUq%2FUUdrlF%2BWV7POYE1vcwvN%2Fpnac7bw%3D%3D" | |
) | |
assert result == expected | |
a = API() | |
# watch logs on server here (or add server you have access to) | |
redirect_uri = "https://snengs.nichd.nih.gov/" | |
# visit printed URL | |
print(a.get_uid(redirect_uri)) | |
# Returns: "GET /?error=The+supplied+signature+parameter+was+invalid HTTP/1.1" 200 5404 "- |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment