Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

123123123 #2

Merged
merged 44 commits into from
Jan 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
3aae537
Test cryptograpgic elements lige key generation, pbkdf2 encryption an…
May 22, 2020
b3fbaba
Create python-app.yml
DanielKusyDev May 22, 2020
10c0897
Add action to prepare tests to be executed
May 22, 2020
64050c3
Update python-app.yml
DanielKusyDev May 22, 2020
6f25f72
Fixed typo in action yml
May 22, 2020
87736ec
Merge branch 'develop' of github.com:DanielKusyDev/proton into develop
May 22, 2020
ab48f27
Create feature of getting single post and create one
May 25, 2020
8deff5a
Add ability to modify and delete posts
May 25, 2020
ea7e0c2
Update python-app.yml
DanielKusyDev May 25, 2020
a95ca9a
Update python-app.yml
DanielKusyDev May 25, 2020
e29de18
Make server run asynchronous by usage of event based request handlers.
May 25, 2020
d965725
Merge branch 'develop' of github.com:DanielKusyDev/proton into develop
May 25, 2020
c2d13fb
Create Response class to store result of controller call
Jun 1, 2020
619a88a
Create logger class, implement logging events and rewrite non blockin…
Jun 1, 2020
8190bba
Do solid refactor. Make better organized structure.
Jun 3, 2020
50c1068
Fix failing tests due to changes in project structure
Jun 3, 2020
5dea1db
Create response message with ability to serialize data
Jun 3, 2020
4b91c25
Change structure of the project. Handle unexpected error by sending a…
Jun 5, 2020
f34f9a0
Experimental azure CI deploy action
Jun 5, 2020
c55504b
Experimental azure CI deploy action
Jun 5, 2020
8a2c184
Experimental azure CI deploy action
Jun 5, 2020
b5e91f7
Transform event based server into threads driven because of stateless…
Jun 7, 2020
a636e3c
Fix failing tests.
Jun 7, 2020
ed7feaf
Transform app to support stateful version of protocol
Jun 7, 2020
fb5eeb8
Replaced event bases server into threading based because of problems …
Jun 8, 2020
2f0c4f5
Better messages for wrong input.
Jun 8, 2020
373b5f1
Make tests pass.
Jun 8, 2020
51748a8
Create test assets directory and populate it with some cutie.
Jun 8, 2020
aef58b9
Rename assets directory made for tests only.
Jun 8, 2020
6ebad49
Create client side tests
Jun 10, 2020
6fe0243
Add generation of db in runserver: create db when it does not exist i…
Jun 12, 2020
5bbcb54
Add CI action responsible for running and killing test server
Jun 12, 2020
8514e01
Fix failing actions because of missing certs
Jun 12, 2020
5c73e33
Fix failing actions because of missing certs
Jun 12, 2020
54de0a3
Temporary disable client tests due to github actions restriction
Jun 12, 2020
5e48de6
Get rid of PostModelResponse class because custom get_record was not …
Jun 14, 2020
f931164
Better logs, more specific and depent on debug mode
Jun 14, 2020
beeb63c
Add README.MD
Jun 15, 2020
d353675
Disable checking client's cert by server
Jun 15, 2020
29e5629
Added server.key to wrap socket fun
Jun 15, 2020
afe4baf
Ommit checking client ca
Jun 15, 2020
dc12ccf
Fix bug with client ability to close the server
Jun 15, 2020
3649128
Update README.MD
DanielKusyDev Jun 22, 2020
5fd77cc
Update requirements.txt
DanielKusyDev Jan 19, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions .github/workflows/python-app.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# This workflow will install Python dependencies, run tests and lint with a single version of Python
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions

name: Proton

on:
push:
branches: [ master, develop ]
pull_request:
branches: [ master, develop ]

jobs:
build:

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- name: Set up Python 3.8
uses: actions/setup-python@v2
with:
python-version: 3.8
- name: Install dependencies
run: |
python -m pip install --upgrade pip
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
- name: Prepare test environment
run: |
cp config_example.ini config.ini
openssl req -new -x509 -days 365 -nodes -out server.pem -keyout server.pem -subj "/C=PL/ST=Lubelskie/L=Lublin/O=UMCS/OU=MFI/CN=proton"
openssl req -new -x509 -days 365 -nodes -out client.pem -keyout client.pem -subj "/C=PL/ST=Lubelskie/L=Lublin/O=UMCS/OU=MFI/CN=proton"
python runserver.py > /dev/null & echo $! > runserver.pid
- name: Test with unittest
run: |
python -m unittest tests.py
- name: Kill temporary processes
run: |
kill -9 $(cat runserver.pid)
rm server.pem
rm client.pem
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -129,5 +129,9 @@ dmypy.json
.pyre/

.idea
sqlite3.db
core/db/sqlite3.db
config.ini
*.pem
*.key
test_client.py
assets
116 changes: 116 additions & 0 deletions README.MD
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# Proton Protocol

## Opis protokołu


Proton jest protokołem tekstowym, umożliwiającym komunikację pomiędzy serwerem a klientami za pomocą formatu **JSON**.
Każdy JSON jest konwertowany do postaci tekstowej (typ string), a na jego końcu jest dodawany znak końca linii **\r\n**.

Protokół ten działa w oparciu o szyfrowane gniazda TCP. Każdy klient musi sprawdzać klucz serwera.

***

#### Odbiór / wysyłanie wiadomości
Wszystkie wiadomość wysyłane przez klienta mają następujący format tekstowy:

`"{ "action": "{action}", "params": {params}}\r\n"`

gdzie:

`{Action}` - jedna z akcji (wyjaśnionych szczegółowo niżej): login, register, logout, get, create, alter, delete.

`{Params}` - obiekt z danymi wymaganymi do zapytania. Opcjonalne.


Wszystkie wiadomości wysyłane przez serwer mają następujący format tekstowy:

`"{"status": "{status}","message": "{message}", "data": {data}}\r\n"`

gdzie:

`{status}` - informacja o powodzeniu akcji lub jego braku. Jedno z wartości
- OK - polecenie wykonane prawidłowo,
- WRONG - polecenie wykonane, ale nie znaleziono danych potrzebnych w zapytaniu
- ERROR - błąd wykonania polecenia.

`{message}` - Opcjonalne. Wiadomość z opisem wykonania akcji.
`{data}` - Opcjonalne. Tablica z danymi będącymi wynikiem akcji.

Proces odbioru wiadomości polega na nasłuchiwaniu gniazda, dopóki wiadomość nie będzie zakończona znakiem końca linii **\r\n**.

***

### Akcje

##### Register
Umożliwia zarejestrowanie użytkownika

Przykładowy request:
`{action: "register", params: {username: "Test1234", password: "Test1234"}}\r\n`

Przykładowy response:
`{"data": [{"id": 6, "username": "Test1234"}], "message": "", "status": "OK"}\r\n`

##### Login
Uwierzytelnienie użytkownika, umożliwia dostęp do pozostałych akcji w komunikacji.

Przykładowy request:
`{action: "login", params: {username: "Qwerty", password: "Qwerty"}}\r\n`

Przykładowy response:
`{"data": [{"user_id": 5}], "message": "", "status": "OK"}\r\n`

##### Logout

Opis umożliwia bezpieczne wylogowanie użytkownika, zakończenie sesji oraz zamknięcie połączenia klienta.

Przykładowy request:
`{"action":"logout"}`

Przykładowy response:
`{"message": "Logged out.", "status": "OK"}`

##### Get
Pobranie listy postów bloga. Z opcjonalnym parametrem id, umożliwia pobranie pojedynczego posta.

Przykładowy request:
`{"action":"get"}\r\n`

Przykładowy response:
`{"data": [ { "id": 1, "image": "base64…","content": "Lorem ipsum dolor sit amet...","title": "Lorem ipsum dolor sit amet...","user_id": 1} ], "message": "","status": "OK" }\r\n`

gdzie *image* to zdjęcie zapisane w formacie base64.

##### Create
Tworzy nowy post.

Przykładowy request:
`{"action": "create", "params": {"image": "base64…", "content": "Lorem ipsum dolor sit amet...", "title": "Lorem ipsum dolor sit amet..."}}\r\n`


Przykładowy response:
`{"data": [{"content": ""Lorem ipsum dolor sit amet..", "id": 14, "image": "base64", "title": ""Lorem ipsum dolor sit amet..", "user_id": 5}], "message": "Post created successfully.", "status": "OK"}\r\n`

##### Alter
Zmienia parametry posta.

Przykładowy request:
`{"action": "alter", "params": {id: 14, "image": "base64…", "content": "Lorem ipsum dolor sit amet...", "title": "Lorem ipsum dolor sit amet..."}}\r\n`

Przykładowy response:
`{"data": [{"content": "Gghggff", "id": 14, "image": base64", "title": "Hbvggv", "user_id": 5}], "message": "", "status": "OK"}\r\n`

##### Delete
Usuwa post.

Przykładowy request:
`{"action": "delete", "params": {"id": 14}}\r\n`

Przykładowy response:
`{"data": {"id": 14}, "status": "OK"}\r\n`

Repozytorium zawiera implementację serwera obsługującego protokół. Klient w postaci aplikacji mobilnej dostępny pod adresem
https://github.com/lukaszkurantdev/proton-blog-app

Ogólny sposób funkcjonowania serwera:
![alt text](https://www.planttext.com/api/plantuml/img/VL913i8m3Bll5Ja24X_O0I7W1S1z2fq54wKTRSRxAQnZxK2SkiPE7BjRUs4dtKqNHHi-6jMqR8Iske6Hh7I0Uy3zO1ql3bndm1xt3ZxltreZpcezcR67RwtnA6eMFh47xJO5AsaUB1X4Uo5QhcAX91SL-dkAd26LX-eSAc_L5JARZwnLjZCj5bJIEu50-eXcjZ9-a8dMGkiND3hi1wi02FxHIhgmgJMgQ6SMptIPCRQaCOpPRUXjbgmZrXAAhmGdf27TVA64ifmayafsU13yMYAjfZcbG7orDKmT_gmd "Request flow")
Empty file added __init__.py
Empty file.
Empty file added backend/__init__.py
Empty file.
3 changes: 2 additions & 1 deletion crypto.py → backend/crypto.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,5 @@ def decrypt(encrypted_message):
encrypted_message = encrypted_message.encode()
f = Fernet(key)
message = f.decrypt(encrypted_message)
return message.decode()
return message.decode()

122 changes: 122 additions & 0 deletions backend/server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import os
import socket
import ssl
import threading
from time import sleep

import settings
from core import messages, controllers, models
from utils import Logger

logger = Logger()


def recv_all(sock: ssl.SSLSocket) -> str:
try:
result = ""
while result[-2:] != "\r\n":
result += sock.read(1).decode()
except ssl.SSLWantReadError as e:
print(e)
finally:
return result


def send(sock: ssl.SSLSocket, response: messages.Response) -> None:
lock = threading.Lock()
lock.acquire()

message_str = response.json_response

if isinstance(message_str, str):
message_str = message_str.encode()
sock.write(message_str)
host, port = sock.getpeername()
lock.release()

host = f"{host}:{port}"
message = response.message if response.message is not None else ""
log_args = (response.action, message, host)

if response.status.upper() == "OK":
logger.success(*log_args)
elif response.status.upper() == "ERROR":
logger.warning(*log_args)
else:
logger.error(*log_args)


class ClientThread(threading.Thread):
def __init__(self, secure_socket: ssl.SSLSocket):
super().__init__()
self.secure_socket = secure_socket
self.auth_token = None

def get_request(self):
raw_message = recv_all(self.secure_socket)
request = messages.Request(raw_message)
return request

def get_response(self, request) -> messages.Response:
controller = controllers.Controller(self.auth_token)
response = getattr(controller, request.action)(request)
if request.action == "login" and response.status == "OK":
token_id = response.data[0]["id"]
token = models.AuthToken().first(id=token_id)[2]
self.auth_token = token
elif request.action == "logout" and response.status == "OK":
self.auth_token = None
return response

def run(self) -> None:
while True:
try:
request = self.get_request()
response = self.get_response(request)
send(self.secure_socket, response)
except PermissionError as e:
response = messages.Response(status="ERROR", message=str(e))
send(self.secure_socket, response)


class Server(object):
def __init__(self, address=("127.0.0.1", 6666)):
self.address = address

def get_raw_socket(self) -> socket.socket:
raw_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
raw_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
raw_socket.bind(self.address)
raw_socket.listen(100)
return raw_socket

def get_secure_socket(self, raw_socket: socket.socket) -> ssl.SSLSocket:
context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
context.load_cert_chain(os.path.join(settings.CERTS_DIR, "server.pem"), os.path.join(settings.CERTS_DIR, "server.key"))
ssock = context.wrap_socket(raw_socket, server_side=True)
return ssock

def process(self, server_socket: socket.socket):
try:
while True:
try:
conn, c_addr = server_socket.accept()
secure_client = self.get_secure_socket(conn)
except Exception as e:
logger.info(str(e))
continue
try:
logger.info(f"Connected by {c_addr[0]}:{c_addr[1]}")
c = ClientThread(secure_client)
c.start()
except Exception as e:
response = messages.Response(status="ERROR", message=str(e))
send(secure_client, response)
secure_client.close()
except Exception as e:
logger.info(str(e))

def runserver(self):
logger.info(f"Starting server at {self.address[0]}:{self.address[1]}")
server_socket = self.get_raw_socket()
self.process(server_socket)
5 changes: 4 additions & 1 deletion config_example.ini
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
[SECRET]
KEY = ...
SALT = ...
SALT = ...

[GENERAL]
DEBUG = False
72 changes: 0 additions & 72 deletions controllers.py

This file was deleted.

Empty file added core/__init__.py
Empty file.
Loading
Loading