Skip to content

Instantly share code, notes, and snippets.

@daler
Created February 24, 2024 19:04
Show Gist options
  • Save daler/35f5aaa806529bed2a67aadc6882f6c8 to your computer and use it in GitHub Desktop.
Save daler/35f5aaa806529bed2a67aadc6882f6c8 to your computer and use it in GitHub Desktop.
LA API
"""
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