Skip to content

Commit

Permalink
Merge pull request #10 from Farmacia-Solidaria/8-Authorization
Browse files Browse the repository at this point in the history
  • Loading branch information
otaviocap authored Sep 30, 2021
2 parents e7b51a7 + 49f09a6 commit 300bf67
Show file tree
Hide file tree
Showing 63 changed files with 435 additions and 557 deletions.
13 changes: 13 additions & 0 deletions .default.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# SERVER
DJANGO_SECRET_KEY=secret_key

# DATABASE
DATABASE_USERNAME=root
DATABASE_PASSWORD=root

# TOKEN SIGNATURE
PRIVATE_KEY=PRIVATE KEY
PUBLIC_KEY=PUBLIC KEY

# OTHERS
DEBUG=TRUE
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "secrets"]
path = secrets
url = https://github.com/Farmacia-Solidaria/secrets
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
## Installation

To install please compile everything using:
```
```bash
python3 safeBuild.py --containers all --update-commons --env dev
```

To deploy use:
```bash
docker-compose --env-file ./secrets/.{ENV}.env up
```
12 changes: 12 additions & 0 deletions common/error/error.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,14 @@
import os
class ActionError(Exception):

def __init__(self, information, status=400, where="undefined") -> None:
self.information = information
self.status = status
self.where = where

if where == "undefined" and 'NAME' in os.environ:
self.where = os.environ['NAME']

super().__init__(self.information)

pass
7 changes: 4 additions & 3 deletions common/error/handling.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
from common.models.message import Message

def handleError(event: Message, information="", where=""):
def handleError(event: Message, information="", where="", status=400):
event.error = True
event.data = {
"information":information,
"where":where
"information": information,
"where": where,
"status": status,
}

def checkError(event: Message, where):
Expand Down
34 changes: 23 additions & 11 deletions common/kafka/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,29 @@

class ActionHandler():

actions: dict = {}
actions: dict = {
"post": {},
"get": {},
"put": {},
"patch": {},
"delete": {},
"options": {},
"head": {},
}
default_function: Callable = None
on_error: Callable = lambda: False

def register(self, func):
def register(self, method:str = "post"):
def decorator(func):

@functools.wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)

self.actions[func.__name__] = wrapper
return wrapper
@functools.wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)

self.actions[method.lower()][func.__name__] = wrapper
return wrapper

return decorator

def default(self, func):
self.default_function = func
Expand All @@ -34,9 +45,10 @@ def run_action(self, action, *args, **kwargs):
return self.on_error(action, ex, *args, **kwargs)


def get_action(self, action):
if action in self.actions:
return self.actions[action]
def get_action(self, method, action):
if method in self.actions:
if action in self.actions[method]:
return self.actions[method][action]

if self.default_function != None:
return self.default_function
Expand Down
5 changes: 3 additions & 2 deletions common/kafka/builders.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@

import asyncio

loop = asyncio.get_event_loop()

def build_kafka_consumer(topic, timeout_in_seconds=15):
return KafkaConsumer(
topic,
Expand All @@ -19,6 +17,9 @@ def build_kafka_consumer(topic, timeout_in_seconds=15):
)

def build_async_kafka_consumer(topic, timeout_in_seconds=15):

loop = asyncio.get_event_loop()

return AIOKafkaConsumer(
topic,
loop=loop,
Expand Down
33 changes: 33 additions & 0 deletions common/kafka/guard.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import functools

import jwt

from common.utils.auth import get_token_permissions
from common.utils.functions import treat_token
from common.models.message import Message
from common.error.error import ActionError

def permissions_needed(permissions_needed: 'list[str]') -> 'function':

def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):

permissions = []

if type(args[0]) is Message:
message: Message = args[0]
permissions = get_token_permissions(treat_token(message.token))

if len(set(permissions_needed).intersection(permissions)) > 0 or 'admin' in permissions:
return func(*args, **kwargs)

raise ActionError(
information="Permission denied",
status=401
)


return wrapper

return decorator
32 changes: 26 additions & 6 deletions common/kafka/send.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from common.error.error import ActionError
import uuid
import json
import os
import hashlib

from common.base import kafkaProducer
from common.models.message import Message
Expand All @@ -28,12 +28,21 @@ async def async_send_and_wait_message(service, action, data, filter=False, suppr

return value

def send_and_wait_message(service, action, data, filter=False, suppress_errors=False) -> dict:
def send_and_wait_message(method, service, action, data, token, filter=False, suppress_errors=False) -> dict:
key = str(uuid.uuid1())

consumer = build_kafka_consumer(service+"-outcome", timeout_in_seconds=2)
consumer = build_kafka_consumer(service+"-outcome", timeout_in_seconds=10)

data = _treat_sensible_information(data)

send_message(service, action, data, key)
send_message(
service=service,
action=action,
data=data,
method=method,
token=token,
key=key
)

for event in consumer:
value = json.loads(event.value.decode("utf-8"))
Expand All @@ -43,13 +52,15 @@ def send_and_wait_message(service, action, data, filter=False, suppress_errors=F
value = _filter_value(value)
return value

def send_message(service, action, data, key=""):
def send_message(service, action, data, method, token, key=""):
finalKey = key if key != "" else str(uuid.uuid1())

message = Message(
action=action,
method=method,
data=data,
id=finalKey
token=token,
id=finalKey,
)
try:
_send_to_topic(service+"-income", finalKey, message.dumps())
Expand Down Expand Up @@ -89,4 +100,13 @@ def _filter_value(value):
"data": value["data"],
}

def _treat_sensible_information(data):
allow_list = ['password']

for i in data:
if i in allow_list:
data[i] = hashlib.sha512(str(i).encode()).hexdigest()

return data

#endregion
4 changes: 3 additions & 1 deletion common/models/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@ class Message(faust.Record, serializer='json'):
action: str
data: dict
id: str
error: bool = False
method: str
error: bool = False
token: str = ""
File renamed without changes.
40 changes: 40 additions & 0 deletions common/shortcuts/classes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from django.http.request import HttpRequest
from rest_framework import status
from rest_framework.response import Response
from rest_framework.views import APIView
from common.kafka.send import send_and_wait_message

class SimpleConnection(APIView):

def __init__(self, name=None):
className = self.__class__.__name__.lower().split("connection")[0]
self.name = className if name is None else name


def _default_send_routine(self, method: str, request: HttpRequest, action: str):

token = request.headers.get("Authorization") or ""

data = send_and_wait_message(
service=self.name,
method=method,
action=action,
data=request.data,
filter=True,
suppress_errors=True,
token=token
)

if data:
return Response(data, status=data["data"]["status"] if data["error"] else status.HTTP_200_OK)

return Response(status=status.HTTP_408_REQUEST_TIMEOUT)


def post(self, request, action): return self._default_send_routine('post', request, action)
def get(self, request, action): return self._default_send_routine( 'get', request, action)
def put(self, request, action): return self._default_send_routine('put', request, action)
def patch(self, request, action): return self._default_send_routine('patch', request, action)
def delete(self, request, action): return self._default_send_routine('delete', request, action)
def options(self, request, action): return self._default_send_routine('options', request, action)
def head(self, request, action): return self._default_send_routine('head', request, action)
File renamed without changes.
18 changes: 18 additions & 0 deletions common/utils/auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from common.error.error import ActionError
import jwt

from common.utils.information import get_public_key

def get_token_permissions(token):
try:
data = jwt.decode(token, get_public_key(), 'RS256')

return data['permissions']
except jwt.ExpiredSignatureError:
raise ActionError(
information="Token has expired",
status=403
)
except:
return []

2 changes: 2 additions & 0 deletions common/utils/functions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
def treat_token(token):
return token.split('Bearer ')[1]
7 changes: 7 additions & 0 deletions common/utils/information.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import os

def get_public_key():
return os.environ["PUBLIC_KEY"].replace("\\n", "\n")

def get_private_key():
return os.environ["PRIVATE_KEY"].replace("\\n", "\n")
7 changes: 7 additions & 0 deletions common/utils/validation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@

def is_key_null(obj, key):
if key in obj:
if obj[key] != "":
return False

return True
2 changes: 1 addition & 1 deletion configuration/kafka-config/server.properties
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ max.request.size=1048576
sasl.enabled.mechanisms=PLAIN,SCRAM-SHA-256,SCRAM-SHA-512
sasl.mechanism.inter.broker.protocol=
zookeeper.connect=zookeeper:2181
log.retention.ms=15000
log.retention.ms=5000
Loading

0 comments on commit 300bf67

Please sign in to comment.