~ren/magentasso-py

bb9d1cc841e486f26ee4e69845aa1ba84ca83dfb — Lauren Jenkinson 2 years ago b824c5d
Initial code commit
7 files changed, 550 insertions(+), 0 deletions(-)

A .gitignore
A Pipfile
A Pipfile.lock
A magentasso/__init__.py
A magentasso/request.py
A magentasso/response.py
A setup.py
A .gitignore => .gitignore +2 -0
@@ 0,0 1,2 @@
*.pyc
*.egg-info

A Pipfile => Pipfile +16 -0
@@ 0,0 1,16 @@
[[source]]
name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = true

[dev-packages]
black = "*"
pipenv-setup = "*"

[packages]

[requires]
python_version = "3.8"

[pipenv]
allow_prereleases = true

A Pipfile.lock => Pipfile.lock +325 -0
@@ 0,0 1,325 @@
{
    "_meta": {
        "hash": {
            "sha256": "850408d71bbe6a38359cccfbe808d06db06cde1516cf4bd15b4a335ab6ed3616"
        },
        "pipfile-spec": 6,
        "requires": {
            "python_version": "3.8"
        },
        "sources": [
            {
                "name": "pypi",
                "url": "https://pypi.org/simple",
                "verify_ssl": true
            }
        ]
    },
    "default": {},
    "develop": {
        "appdirs": {
            "hashes": [
                "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41",
                "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"
            ],
            "version": "==1.4.4"
        },
        "attrs": {
            "hashes": [
                "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6",
                "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"
            ],
            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
            "version": "==20.3.0"
        },
        "black": {
            "hashes": [
                "sha256:1b30e59be925fafc1ee4565e5e08abef6b03fe455102883820fe5ee2e4734e0b",
                "sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539"
            ],
            "index": "pypi",
            "version": "==19.10b0"
        },
        "cached-property": {
            "hashes": [
                "sha256:9fa5755838eecbb2d234c3aa390bd80fbd3ac6b6869109bfc1b499f7bd89a130",
                "sha256:df4f613cf7ad9a588cc381aaf4a512d26265ecebd5eb9e1ba12f1319eb85a6a0"
            ],
            "version": "==1.5.2"
        },
        "cerberus": {
            "hashes": [
                "sha256:302e6694f206dd85cb63f13fd5025b31ab6d38c99c50c6d769f8fa0b0f299589"
            ],
            "version": "==1.3.2"
        },
        "certifi": {
            "hashes": [
                "sha256:1f422849db327d534e3d0c5f02a263458c3955ec0aae4ff09b95f195c59f4edd",
                "sha256:f05def092c44fbf25834a51509ef6e631dc19765ab8a57b4e7ab85531f0a9cf4"
            ],
            "version": "==2020.11.8"
        },
        "chardet": {
            "hashes": [
                "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
                "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
            ],
            "version": "==3.0.4"
        },
        "click": {
            "hashes": [
                "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a",
                "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"
            ],
            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
            "version": "==7.1.2"
        },
        "colorama": {
            "hashes": [
                "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b",
                "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"
            ],
            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
            "version": "==0.4.4"
        },
        "distlib": {
            "hashes": [
                "sha256:8c09de2c67b3e7deef7184574fc060ab8a793e7adbb183d942c389c8b13c52fb",
                "sha256:edf6116872c863e1aa9d5bb7cb5e05a022c519a4594dc703843343a9ddd9bff1"
            ],
            "version": "==0.3.1"
        },
        "idna": {
            "hashes": [
                "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6",
                "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"
            ],
            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
            "version": "==2.10"
        },
        "orderedmultidict": {
            "hashes": [
                "sha256:04070bbb5e87291cc9bfa51df413677faf2141c73c61d2a5f7b26bea3cd882ad",
                "sha256:43c839a17ee3cdd62234c47deca1a8508a3f2ca1d0678a3bf791c87cf84adbf3"
            ],
            "version": "==1.0.1"
        },
        "packaging": {
            "hashes": [
                "sha256:05af3bb85d320377db281cf254ab050e1a7ebcbf5410685a9a407e18a1f81236",
                "sha256:eb41423378682dadb7166144a4926e443093863024de508ca5c9737d6bc08376"
            ],
            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
            "version": "==20.7"
        },
        "pathspec": {
            "hashes": [
                "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd",
                "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d"
            ],
            "version": "==0.8.1"
        },
        "pep517": {
            "hashes": [
                "sha256:3985b91ebf576883efe5fa501f42a16de2607684f3797ddba7202b71b7d0da51",
                "sha256:aeb78601f2d1aa461960b43add204cc7955667687fbcf9cdb5170f00556f117f"
            ],
            "version": "==0.9.1"
        },
        "pip-shims": {
            "hashes": [
                "sha256:05b00ade9d1e686a98bb656dd9b0608a933897283dc21913fad6ea5409ff7e91",
                "sha256:16ca9f87485667b16b978b68a1aae4f9cc082c0fa018aed28567f9f34a590569"
            ],
            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
            "version": "==0.5.3"
        },
        "pipenv-setup": {
            "hashes": [
                "sha256:8a439aff7b16e18d7e07702c9186fc5fe86156679eace90e10c2578a43bd7af1",
                "sha256:e1bfd55c1152024e762f1c17f6189fcb073166509e7c0228870f7ea160355648"
            ],
            "index": "pypi",
            "version": "==3.1.1"
        },
        "pipfile": {
            "hashes": [
                "sha256:f7d9f15de8b660986557eb3cc5391aa1a16207ac41bc378d03f414762d36c984"
            ],
            "version": "==0.0.2"
        },
        "plette": {
            "extras": [
                "validation"
            ],
            "hashes": [
                "sha256:46402c03e36d6eadddad2a5125990e322dd74f98160c8f2dcd832b2291858a26",
                "sha256:d6c9b96981b347bddd333910b753b6091a2c1eb2ef85bb373b4a67c9d91dca16"
            ],
            "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
            "version": "==0.2.3"
        },
        "pyparsing": {
            "hashes": [
                "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1",
                "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"
            ],
            "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'",
            "version": "==2.4.7"
        },
        "python-dateutil": {
            "hashes": [
                "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c",
                "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"
            ],
            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'",
            "version": "==2.8.1"
        },
        "regex": {
            "hashes": [
                "sha256:02951b7dacb123d8ea6da44fe45ddd084aa6777d4b2454fa0da61d569c6fa538",
                "sha256:0d08e71e70c0237883d0bef12cad5145b84c3705e9c6a588b2a9c7080e5af2a4",
                "sha256:1862a9d9194fae76a7aaf0150d5f2a8ec1da89e8b55890b1786b8f88a0f619dc",
                "sha256:1ab79fcb02b930de09c76d024d279686ec5d532eb814fd0ed1e0051eb8bd2daa",
                "sha256:1fa7ee9c2a0e30405e21031d07d7ba8617bc590d391adfc2b7f1e8b99f46f444",
                "sha256:262c6825b309e6485ec2493ffc7e62a13cf13fb2a8b6d212f72bd53ad34118f1",
                "sha256:2a11a3e90bd9901d70a5b31d7dd85114755a581a5da3fc996abfefa48aee78af",
                "sha256:2c99e97d388cd0a8d30f7c514d67887d8021541b875baf09791a3baad48bb4f8",
                "sha256:3128e30d83f2e70b0bed9b2a34e92707d0877e460b402faca908c6667092ada9",
                "sha256:38c8fd190db64f513fe4e1baa59fed086ae71fa45083b6936b52d34df8f86a88",
                "sha256:3bddc701bdd1efa0d5264d2649588cbfda549b2899dc8d50417e47a82e1387ba",
                "sha256:4902e6aa086cbb224241adbc2f06235927d5cdacffb2425c73e6570e8d862364",
                "sha256:49cae022fa13f09be91b2c880e58e14b6da5d10639ed45ca69b85faf039f7a4e",
                "sha256:56e01daca75eae420bce184edd8bb341c8eebb19dd3bce7266332258f9fb9dd7",
                "sha256:5862975b45d451b6db51c2e654990c1820523a5b07100fc6903e9c86575202a0",
                "sha256:6a8ce43923c518c24a2579fda49f093f1397dad5d18346211e46f134fc624e31",
                "sha256:6c54ce4b5d61a7129bad5c5dc279e222afd00e721bf92f9ef09e4fae28755683",
                "sha256:6e4b08c6f8daca7d8f07c8d24e4331ae7953333dbd09c648ed6ebd24db5a10ee",
                "sha256:717881211f46de3ab130b58ec0908267961fadc06e44f974466d1887f865bd5b",
                "sha256:749078d1eb89484db5f34b4012092ad14b327944ee7f1c4f74d6279a6e4d1884",
                "sha256:7913bd25f4ab274ba37bc97ad0e21c31004224ccb02765ad984eef43e04acc6c",
                "sha256:7a25fcbeae08f96a754b45bdc050e1fb94b95cab046bf56b016c25e9ab127b3e",
                "sha256:83d6b356e116ca119db8e7c6fc2983289d87b27b3fac238cfe5dca529d884562",
                "sha256:8b882a78c320478b12ff024e81dc7d43c1462aa4a3341c754ee65d857a521f85",
                "sha256:8f6a2229e8ad946e36815f2a03386bb8353d4bde368fdf8ca5f0cb97264d3b5c",
                "sha256:9801c4c1d9ae6a70aeb2128e5b4b68c45d4f0af0d1535500884d644fa9b768c6",
                "sha256:a15f64ae3a027b64496a71ab1f722355e570c3fac5ba2801cafce846bf5af01d",
                "sha256:a3d748383762e56337c39ab35c6ed4deb88df5326f97a38946ddd19028ecce6b",
                "sha256:a63f1a07932c9686d2d416fb295ec2c01ab246e89b4d58e5fa468089cab44b70",
                "sha256:b2b1a5ddae3677d89b686e5c625fc5547c6e492bd755b520de5332773a8af06b",
                "sha256:b2f4007bff007c96a173e24dcda236e5e83bde4358a557f9ccf5e014439eae4b",
                "sha256:baf378ba6151f6e272824b86a774326f692bc2ef4cc5ce8d5bc76e38c813a55f",
                "sha256:bafb01b4688833e099d79e7efd23f99172f501a15c44f21ea2118681473fdba0",
                "sha256:bba349276b126947b014e50ab3316c027cac1495992f10e5682dc677b3dfa0c5",
                "sha256:c084582d4215593f2f1d28b65d2a2f3aceff8342aa85afd7be23a9cad74a0de5",
                "sha256:d1ebb090a426db66dd80df8ca85adc4abfcbad8a7c2e9a5ec7513ede522e0a8f",
                "sha256:d2d8ce12b7c12c87e41123997ebaf1a5767a5be3ec545f64675388970f415e2e",
                "sha256:e32f5f3d1b1c663af7f9c4c1e72e6ffe9a78c03a31e149259f531e0fed826512",
                "sha256:e3faaf10a0d1e8e23a9b51d1900b72e1635c2d5b0e1bea1c18022486a8e2e52d",
                "sha256:f7d29a6fc4760300f86ae329e3b6ca28ea9c20823df123a2ea8693e967b29917",
                "sha256:f8f295db00ef5f8bae530fc39af0b40486ca6068733fb860b42115052206466f"
            ],
            "version": "==2020.11.13"
        },
        "requests": {
            "hashes": [
                "sha256:7f1a0b932f4a60a1a65caa4263921bb7d9ee911957e0ae4a23a6dd08185ad5f8",
                "sha256:e786fa28d8c9154e6a4de5d46a1d921b8749f8b74e28bde23768e5e16eece998"
            ],
            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
            "version": "==2.25.0"
        },
        "requirementslib": {
            "hashes": [
                "sha256:50d20f27e4515a2393695b0d886219598302163438ae054253147b2bad9b4a44",
                "sha256:9c1e8666ca4512724cdd1739adcc7df19ec7ad2ed21f0e748f9631ad6b54f321"
            ],
            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
            "version": "==1.5.16"
        },
        "six": {
            "hashes": [
                "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259",
                "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"
            ],
            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'",
            "version": "==1.15.0"
        },
        "toml": {
            "hashes": [
                "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b",
                "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"
            ],
            "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'",
            "version": "==0.10.2"
        },
        "tomlkit": {
            "hashes": [
                "sha256:6babbd33b17d5c9691896b0e68159215a9387ebfa938aa3ac42f4a4beeb2b831",
                "sha256:ac57f29693fab3e309ea789252fcce3061e19110085aa31af5446ca749325618"
            ],
            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
            "version": "==0.7.0"
        },
        "typed-ast": {
            "hashes": [
                "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355",
                "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919",
                "sha256:0d8110d78a5736e16e26213114a38ca35cb15b6515d535413b090bd50951556d",
                "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa",
                "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652",
                "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75",
                "sha256:3742b32cf1c6ef124d57f95be609c473d7ec4c14d0090e5a5e05a15269fb4d0c",
                "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01",
                "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d",
                "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1",
                "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907",
                "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c",
                "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3",
                "sha256:7e4c9d7658aaa1fc80018593abdf8598bf91325af6af5cce4ce7c73bc45ea53d",
                "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b",
                "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614",
                "sha256:92c325624e304ebf0e025d1224b77dd4e6393f18aab8d829b5b7e04afe9b7a2c",
                "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb",
                "sha256:b52ccf7cfe4ce2a1064b18594381bccf4179c2ecf7f513134ec2f993dd4ab395",
                "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b",
                "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41",
                "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6",
                "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34",
                "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe",
                "sha256:d648b8e3bf2fe648745c8ffcee3db3ff903d0817a01a12dd6a6ea7a8f4889072",
                "sha256:f208eb7aff048f6bea9586e61af041ddf7f9ade7caed625742af423f6bae3298",
                "sha256:fac11badff8313e23717f3dada86a15389d0708275bddf766cca67a84ead3e91",
                "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4",
                "sha256:fcf135e17cc74dbfbc05894ebca928ffeb23d9790b3167a674921db19082401f",
                "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"
            ],
            "version": "==1.4.1"
        },
        "urllib3": {
            "hashes": [
                "sha256:19188f96923873c92ccb987120ec4acaa12f0461fa9ce5d3d0772bc965a39e08",
                "sha256:d8ff90d979214d7b4f8ce956e80f4028fc6860e4431f731ea4a8c08f23f99473"
            ],
            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'",
            "version": "==1.26.2"
        },
        "vistir": {
            "hashes": [
                "sha256:a37079cdbd85d31a41cdd18457fe521e15ec08b255811e81aa061fd5f48a20fb",
                "sha256:eff1d19ef50c703a329ed294e5ec0b0fbb35b96c1b3ee6dcdb266dddbe1e935a"
            ],
            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
            "version": "==0.5.2"
        },
        "wheel": {
            "hashes": [
                "sha256:906864fb722c0ab5f2f9c35b2c65e3af3c009402c108a709c0aca27bc2c9187b",
                "sha256:aaef9b8c36db72f8bf7f1e54f85f875c4d466819940863ca0b3f3f77f0a1646f"
            ],
            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
            "version": "==0.36.1"
        }
    }
}

A magentasso/__init__.py => magentasso/__init__.py +4 -0
@@ 0,0 1,4 @@
__version__ = "0.0.1"

from .request import MagentaRequest
from .response import MagentaResponse

A magentasso/request.py => magentasso/request.py +98 -0
@@ 0,0 1,98 @@
import base64
import hmac
import json
import random
from urllib.parse import urlencode, urljoin


class MagentaRequest:
    def __init__(self, client_id, client_secret, nonce, scopes, callback_url):
        self.client_id = client_id

        self.client_secret = client_secret
        if not isinstance(client_secret, bytes):
            self.client_secret = base64.b32decode(client_secret.upper())

        self.nonce = nonce
        if nonce is None:
            self.nonce = random.randint(10000000, 99999999)

        self.scopes = scopes
        if not isinstance(scopes, list):
            self.scopes = [
                scopes,
            ]

        self.callback_url = callback_url

    def sign(self):
        data = {
            "client_id": self.client_id,
            "nonce": self.nonce,
            "scopes": self.scopes,
            "callback_url": self.callback_url,
        }

        payload = json.dumps(data).encode("utf-8")
        payload = base64.urlsafe_b64encode(payload)
        signature = hmac.digest(self.client_secret, payload, "sha256")
        signature = base64.urlsafe_b64encode(signature)

        return (payload, signature)

    def begin_url(self, magenta_base):
        payload, signature = self.sign()
        params = {
            "client": self.client_id,
            "payload": payload,
            "signature": signature,
        }

        params = urlencode(params)

        url = urljoin(magenta_base, f"/sso/begin?{params}")
        return url

    @classmethod
    def verify(cls, payload, signature, client_secret):
        if not isinstance(client_secret, bytes):
            client_secret = base64.b32decode(client_secret.upper())

        if not isinstance(signature, bytes):
            signature = signature.encode("utf-8")

        if not isinstance(payload, bytes):
            payload = payload.encode("utf-8")

        signature_check = hmac.digest(client_secret, payload, "sha256")
        signature = base64.urlsafe_b64decode(signature)

        if signature != signature_check:
            raise ValueError("mismatching signatures")

        payload = base64.urlsafe_b64decode(payload)
        payload = json.loads(payload.decode("utf-8"))

        scopes = payload["scopes"]
        if not isinstance(scopes, list):
            scopes = [
                scopes,
            ]

        output = cls(
            payload["client_id"],
            client_secret,
            payload["nonce"],
            scopes,
            payload["callback_url"],
        )

        return output

    def __repr__(self):
        return "MagentaRequest(client_id=%r nonce=%r scopes=%r callback_url=%r)" % (
            self.client_id,
            self.nonce,
            self.scopes,
            self.callback_url,
        )

A magentasso/response.py => magentasso/response.py +75 -0
@@ 0,0 1,75 @@
import base64
import hmac
import json

from urllib.parse import urlencode


class MagentaResponse:
    def __init__(self, client_id, client_secret, nonce, user_data, scope_data):
        self.client_id = client_id

        self.client_secret = client_secret
        if not isinstance(client_secret, bytes):
            self.client_secret = base64.b32decode(client_secret.upper())

        self.nonce = nonce
        self.user_data = user_data
        self.scope_data = scope_data

    def sign(self):
        data = {
            "client_id": self.client_id,
            "nonce": self.nonce,
            "user_data": self.user_data,
            "scope_data": self.scope_data,
        }

        payload = json.dumps(data).encode("utf-8")
        payload = base64.urlsafe_b64encode(payload)
        signature = hmac.digest(self.client_secret, payload, "sha256")
        signature = base64.urlsafe_b64encode(signature)

        return (payload, signature)

    def callback_query(self):
        payload, signature = self.sign()
        params = {
            "payload": payload,
            "signature": signature,
        }

        return urlencode(params)

    @classmethod
    def verify(cls, payload, signature, client_secret):
        if not isinstance(client_secret, bytes):
            client_secret = base64.b32decode(client_secret.upper())

        if not isinstance(signature, bytes):
            signature = signature.encode("utf-8")

        if not isinstance(payload, bytes):
            payload = payload.encode("utf-8")

        signature_check = hmac.digest(client_secret, payload, "sha256")
        signature = base64.urlsafe_b64decode(signature)

        if signature != signature_check:
            raise ValueError("mismatching signatures")

        payload = base64.urlsafe_b64decode(payload)
        payload = json.loads(payload.decode("utf-8"))

        output = cls(
            payload["client_id"],
            client_secret,
            payload["nonce"],
            payload["user_data"],
            payload["scope_data"],
        )

        return output

    def __repr__(self):
        return "MagentaResponse(client_id=%r nonce=%r)" % (self.client_id, self.nonce,)

A setup.py => setup.py +30 -0
@@ 0,0 1,30 @@
import sys
from setuptools import setup, find_packages
from os import path

here = path.abspath(path.dirname(__file__))
sys.path.insert(0, here)

import magentasso

with open(path.join(here, "README.md"), encoding="utf-8") as f:
    long_description = f.read()

setup(
    name="magentasso",
    version=magentasso.__version__,
    description="MagentaSSO protocol library",
    long_description=long_description,
    long_description_content_type="text/markdown",
    url="https://sr.ht/~ren/magenta",
    author="Lauren Jenkinson",
    author_email="lauren@kat.net.nz",
    classifiers=[
        "License :: OSI Approved :: MIT License",
        "Programming Language :: Python :: 3.8",
    ],
    packages=find_packages(exclude=["contrib", "docs", "tests"]),
    python_requires=">=3.8",
    install_requires=[],
    extras_require={"dev": []},
)