Commit 8297969f by Oleksandr Barabash

is_auth added

parent 6386fc33
...@@ -74,6 +74,7 @@ ADAPTER.on_turn_error = on_error ...@@ -74,6 +74,7 @@ ADAPTER.on_turn_error = on_error
BOT = TeamsMessagingExtensionsActionPreviewBot(app_settings, ADAPTER) BOT = TeamsMessagingExtensionsActionPreviewBot(app_settings, ADAPTER)
@TOKEN_HELPER.is_auth
async def v1_get_initiations(request: Request) -> Response: async def v1_get_initiations(request: Request) -> Response:
""" Get Initiations by Notification ID """ """ Get Initiations by Notification ID """
# noinspection PyBroadException # noinspection PyBroadException
...@@ -92,6 +93,7 @@ async def v1_get_initiations(request: Request) -> Response: ...@@ -92,6 +93,7 @@ async def v1_get_initiations(request: Request) -> Response:
return Response(status=HTTPStatus.BAD_REQUEST) return Response(status=HTTPStatus.BAD_REQUEST)
@TOKEN_HELPER.is_auth
async def v1_get_notification(request: Request) -> Response: async def v1_get_notification(request: Request) -> Response:
""" Get Notification by ID """ """ Get Notification by ID """
# noinspection PyBroadException # noinspection PyBroadException
...@@ -114,6 +116,7 @@ async def v1_get_notification(request: Request) -> Response: ...@@ -114,6 +116,7 @@ async def v1_get_notification(request: Request) -> Response:
return Response(status=HTTPStatus.BAD_REQUEST) return Response(status=HTTPStatus.BAD_REQUEST)
@TOKEN_HELPER.is_auth
async def v1_notification(request: Request) -> Response: async def v1_notification(request: Request) -> Response:
""" Notify channel with the link """ """ Notify channel with the link """
# todo(s1z): add auth # todo(s1z): add auth
......
...@@ -14,10 +14,16 @@ CARDS_PATH = os.path.join(ASSETS_PATH, "cards") ...@@ -14,10 +14,16 @@ CARDS_PATH = os.path.join(ASSETS_PATH, "cards")
class Auth: class Auth:
""" Auth type """ """ Auth type """
TYPE = "jwt"
RS256 = "RS256" RS256 = "RS256"
HS256 = "HS256" HS256 = "HS256"
CURRENT = RS256 CURRENT = RS256
BEARER = "Bearer"
ALG = RS256
ADMIN_LOGIN_SECRET = "adminLogin" ADMIN_LOGIN_SECRET = "adminLogin"
ADMIN_PASSW_SECRET = "adminPassword" ADMIN_PASSW_SECRET = "adminPassword"
......
...@@ -53,8 +53,12 @@ class AzureKeyVaultClient: ...@@ -53,8 +53,12 @@ class AzureKeyVaultClient:
""" Async get secret """ """ Async get secret """
return self.execute_blocking(self.secret_client.get_secret, name) return self.execute_blocking(self.secret_client.get_secret, name)
def get_key_bl(self, name: str) -> "KeyVaultKey":
""" Get key, blocking """
return self.key_client.get_key(name)
def get_key(self, name: str) -> Awaitable["KeyVaultKey"]: def get_key(self, name: str) -> Awaitable["KeyVaultKey"]:
""" Async get key """ """ Get key, Async """
return self.execute_blocking(self.key_client.get_key, name) return self.execute_blocking(self.key_client.get_key, name)
def create_key(self, name: str) -> Awaitable["KeyVaultKey"]: def create_key(self, name: str) -> Awaitable["KeyVaultKey"]:
......
""" Handy Functions """ """ Handy Functions """
from base64 import b64encode, b64decode from base64 import b64encode, b64decode
from typing import List, Optional, Dict from typing import List, Optional, Dict, Tuple
def get_first_or_none(items: List) -> Optional[Dict[str, any]]: def get_first_or_none(items: List) -> Optional[Dict[str, any]]:
...@@ -10,6 +10,18 @@ def get_first_or_none(items: List) -> Optional[Dict[str, any]]: ...@@ -10,6 +10,18 @@ def get_first_or_none(items: List) -> Optional[Dict[str, any]]:
return None return None
def parse_auth_header(header: Optional[str]) -> Tuple[Optional[str],
Optional[str]]:
""" Parse Authorization header and return Type, Value or None """
if isinstance(header, str):
try:
t, v = header.split(":")[:2]
return t, v
except ValueError:
pass
return None, None
def b64encode_str(data: str, encoding="utf-8") -> str: def b64encode_str(data: str, encoding="utf-8") -> str:
""" Decode base64 str and return decoded string """ """ Decode base64 str and return decoded string """
return b64encode_np(data.encode(encoding)).decode(encoding) return b64encode_np(data.encode(encoding)).decode(encoding)
......
""" Token Helper """ """ Token Helper """
import asyncio import asyncio
import sys
from calendar import timegm from calendar import timegm
from concurrent.futures.thread import ThreadPoolExecutor from concurrent.futures.thread import ThreadPoolExecutor
from datetime import datetime, timedelta from datetime import datetime, timedelta
from http import HTTPStatus
from typing import Dict, Union from typing import Dict, Union
from Crypto.Hash import SHA256 from Crypto.Hash import SHA256
from aiohttp.web import Request, Response
from azure.core.exceptions import ResourceNotFoundError, HttpResponseError
from config import Auth
from entities.json.admin_user import AdminUser from entities.json.admin_user import AdminUser
from utils.azure_key_vault_client import AzureKeyVaultClient from utils.azure_key_vault_client import AzureKeyVaultClient
from utils.functions import b64encode_str, b64encode_np from utils.functions import b64encode_str, b64encode_np, parse_auth_header, \
from utils.json_func import json_dumps b64decode_str
from utils.json_func import json_dumps, json_loads
from utils.log import Log
class MimeTypes: class MimeTypes:
...@@ -21,8 +28,8 @@ class MimeTypes: ...@@ -21,8 +28,8 @@ class MimeTypes:
class TokenHelper: class TokenHelper:
""" Token Helper implementation """ """ Token Helper implementation """
def __init__(self, azure_cli: AzureKeyVaultClient): def __init__(self, azure_kv: AzureKeyVaultClient):
self.azure_cli = azure_cli self.azure_kv = azure_kv
self.executor = ThreadPoolExecutor(10) self.executor = ThreadPoolExecutor(10)
self.io_loop = asyncio.get_event_loop() self.io_loop = asyncio.get_event_loop()
...@@ -34,13 +41,13 @@ class TokenHelper: ...@@ -34,13 +41,13 @@ class TokenHelper:
if alg == Auth.RS256: if alg == Auth.RS256:
""" RSA signature with SHA-256 """ """ RSA signature with SHA-256 """
key = self.azure_cli.get_or_create_random_key_bl() key = self.azure_kv.get_or_create_random_key_bl()
header.update(dict(kid=key.name)) header.update(dict(kid=key.name))
token_unsigned = "{}.{}".format(b64encode_str(json_dumps(header)), token_unsigned = "{}.{}".format(b64encode_str(json_dumps(header)),
b64encode_str(json_dumps(body))) b64encode_str(json_dumps(body)))
signature = SHA256.new(token_unsigned.encode("utf-8")).digest() signature = SHA256.new(token_unsigned.encode("utf-8")).digest()
signature_encrypted = self.azure_cli.encrypt_bl(key, signature) signature_encrypted = self.azure_kv.encrypt_bl(key, signature)
signature_b64 = b64encode_np(signature_encrypted).decode("utf-8") signature_b64 = b64encode_np(signature_encrypted).decode("utf-8")
return "{}.{}".format(token_unsigned, signature_b64) return "{}.{}".format(token_unsigned, signature_b64)
elif alg == Auth.HS256: elif alg == Auth.HS256:
...@@ -64,12 +71,12 @@ class TokenHelper: ...@@ -64,12 +71,12 @@ class TokenHelper:
""" Perform Auth blocking """ """ Perform Auth blocking """
from config import Auth from config import Auth
kv_login = self.azure_cli.get_secret_bl(Auth.ADMIN_LOGIN_SECRET).value login = self.azure_kv.get_secret_bl(Auth.ADMIN_LOGIN_SECRET).value
kv_passw = self.azure_cli.get_secret_bl(Auth.ADMIN_PASSW_SECRET).value passw = self.azure_kv.get_secret_bl(Auth.ADMIN_PASSW_SECRET).value
if user.login == kv_login and user.password == kv_passw: if user.login == login and user.password == passw:
ttl = 3600 ttl = 3600
token = self.create_token_bl(user.login, ttl) token = self.create_token_bl(user.login, ttl)
return dict(tokenType="Bearer", return dict(tokenType=Auth.BEARER,
expiresIn=ttl, expiresIn=ttl,
accessToken=token) accessToken=token)
return None return None
...@@ -78,3 +85,65 @@ class TokenHelper: ...@@ -78,3 +85,65 @@ class TokenHelper:
""" Perform auth async """ """ Perform auth async """
return self.io_loop.run_in_executor(self.executor, self.do_auth_bl, return self.io_loop.run_in_executor(self.executor, self.do_auth_bl,
user) user)
def is_token_valid(self, token: str) -> bool:
""" Check if token is Valid """
# split first
header_b64_str, body_b64_str, signature = token.split(".")
token_unsigned = "{}.{}".format(header_b64_str, body_b64_str)
# parse
header = json_loads(b64decode_str(header_b64_str))
body = json_loads(b64decode_str(body_b64_str))
# check fields
token_typ = header.get("typ", None)
token_alg = header.get("alg", None)
token_kid = header.get("kid", None)
token_sub = body.get("sub", None)
token_exp = body.get("exp", None)
if None in [token_typ, token_alg, token_kid, token_sub, token_exp]:
return False
# check type
if token_typ != Auth.TYPE:
return False
# check alg
if token_alg != Auth.ALG:
return False
# check expiration
if datetime.utcnow().timestamp() > token_exp:
return False
# TODO(s1z): Cache this please
# check sub
login = self.azure_kv.get_secret_bl(Auth.ADMIN_LOGIN_SECRET).value
if token_sub != login:
return False
# check signature
try:
key = self.azure_kv.get_key_bl(token_kid)
except (ResourceNotFoundError, HttpResponseError):
Log.e(__name__, "Key not found: '{}'".format(token_kid))
return False
# signature_gen = SHA256.new(token_unsigned.encode("utf-8")).digest()
# signature_encrypted = self.azure_kv.encrypt_bl(key, signature_gen)
# signature_b64 = b64encode_np(signature_encrypted).decode("utf-8")
# Log.e(__name__, exc_info=sys.exc_info())
return True
def is_auth(self, f):
""" Is auth decorator """
async def wr(request: Request) -> Response:
""" Wrapper """
a_type, a_value = parse_auth_header(
request.headers.get("Authorization")
)
if a_type == Auth.BEARER and self.is_token_valid(a_value):
return await f(request)
return Response(status=HTTPStatus.FORBIDDEN)
return wr
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment