Created endpoint to get the device pairing code
parent
75fe9eed96
commit
64e513a7df
|
@ -0,0 +1,53 @@
|
|||
import json
|
||||
import hashlib
|
||||
import random
|
||||
import uuid
|
||||
|
||||
from selene.api import SeleneEndpoint
|
||||
from selene.util.selene_cache import SeleneCache
|
||||
|
||||
|
||||
class DeviceCodeEndpoint(SeleneEndpoint):
|
||||
"""Endpoint to get a pairing code"""
|
||||
|
||||
# We need to do that to avoid ambiguous characters, like 0 and O, that is even harder to distinguish
|
||||
# in the display of the mark 1
|
||||
ALLOWED_CHARACTERS = "ACEFHJKLMNPRTUVWXY3479"
|
||||
|
||||
def __init__(self):
|
||||
super(DeviceCodeEndpoint, self).__init__()
|
||||
self.device_pairing_time = 86400
|
||||
self.cache: SeleneCache = self.config.get('SELENE_CACHE')
|
||||
self.sha512 = hashlib.sha512()
|
||||
|
||||
def get(self):
|
||||
# The pairing process happens in two steps, the step where we get the pairing code and
|
||||
# the step to activate the device. The state parameter is used to make sure that the device that is
|
||||
# trying to be activate is the device which started the previous step
|
||||
state = self.request.args['state']
|
||||
return self._create(state)
|
||||
|
||||
def _create(self, state):
|
||||
self.sha512.update(bytes(str(uuid.uuid4()), 'utf-8'))
|
||||
token = self.sha512.hexdigest()
|
||||
code = self._pairing_code()
|
||||
pairing = json.dumps({'code': code,
|
||||
'state': state,
|
||||
'token': token,
|
||||
'expiration': self.device_pairing_time})
|
||||
# This is to deal with the case where we generate a pairing code that already exists in the
|
||||
# cache, meaning another device is trying to pairing using the same code. In this case, we should
|
||||
# call the method again to get another random pairing code
|
||||
if self.cache.set_if_not_exists_with_expiration(self._code_key(code),
|
||||
value=pairing,
|
||||
expiration=self.device_pairing_time):
|
||||
return code
|
||||
else:
|
||||
return self._create(state)
|
||||
|
||||
@staticmethod
|
||||
def _code_key(code):
|
||||
return 'pairing.code:{}'.format(code)
|
||||
|
||||
def _pairing_code(self):
|
||||
return ''.join(random.choice(self.ALLOWED_CHARACTERS) for _ in range(6))
|
|
@ -10,6 +10,7 @@ psycopg2-binary = "*"
|
|||
passlib = "*"
|
||||
pyhamcrest = "*"
|
||||
schematics = "*"
|
||||
redis = "*"
|
||||
|
||||
[dev-packages]
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "63164ec5172150b56a6a3930e1eb1bfa195837d6dbc019864a1b5475cdfdf590"
|
||||
"sha256": "c0c1a9ec56002f00c9a50fc67aba1b0463d05323813944a244860fd69f49b75c"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
|
@ -111,6 +111,14 @@
|
|||
"index": "pypi",
|
||||
"version": "==1.7.1"
|
||||
},
|
||||
"redis": {
|
||||
"hashes": [
|
||||
"sha256:724932360d48e5407e8f82e405ab3650a36ed02c7e460d1e6fddf0f038422b54",
|
||||
"sha256:9b19425a38fd074eb5795ff2b0d9a55b46a44f91f5347995f27e3ad257a7d775"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.2.0"
|
||||
},
|
||||
"requests": {
|
||||
"hashes": [
|
||||
"sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e",
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
import os
|
||||
|
||||
from redis import Redis
|
||||
|
||||
|
||||
class SeleneCache(object):
|
||||
|
||||
def __init__(self):
|
||||
# should the variables host and port be in the config class?
|
||||
redis_host = os.environ['REDIS_HOST']
|
||||
redis_port = int(os.environ['REDIS_PORT'])
|
||||
self.redis = Redis(host=redis_host, port=redis_port)
|
||||
|
||||
def set_if_not_exists_with_expiration(self, key, value, expiration):
|
||||
"""Sets a key only if it doesn't exist and using a given expiration time"""
|
||||
return self.redis.set(name=key, value=value, ex=expiration, nx=True)
|
|
@ -16,6 +16,8 @@ setup(
|
|||
'pyhamcrest',
|
||||
'pyjwt',
|
||||
'psycopg2-binary',
|
||||
'schematics'
|
||||
'schematics',
|
||||
'validator-collection',
|
||||
'redis'
|
||||
]
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue