Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 6 additions & 0 deletions .idea/inspectionProfiles/Project_Default.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

192 changes: 192 additions & 0 deletions DDD.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
# Domain Driven Design - Model Aplikacji Bankowej

## Opis zadania

Celem zadania było zamodelowanie bezpiecznej aplikacji bankowej wykorzystując zasady Domain Driven Design. Skupiono się na fragmencie systemu związanym z uwierzytelnieniem użytkowników i wykonywaniem przelewów. Model obejmuje 3 główne encje w obrębie dwóch Bounded Context: Uwierzytelnienie oraz Przelewy. Wszystkie encje zostały zaprojektowane z uwzględnieniem zasad bezpieczeństwa, walidacji danych oraz niezmienności obiektów wartości.

## Model Domain Driven Design

### Bounded Context: Uwierzytelnienie

![Diagram Agregatu Uwierzytelnienia](img/uwierz.drawio.png)

### Bounded Context: Przelewy

![Diagram Agregatu Przelewów](img/przelew.drawio.png)

## Założenia i ograniczenia

### Bounded Context: Uwierzytelnienie

#### Agregat: AgregatSesji

**Root Entity: Sesja**

| Atrybut | Typ | Ograniczenia | Opis |
|---------|-----|--------------|------|
| dataUtworzenia | DateTime | Format: YYYY-MM-DD HH:MM:SS | Data i czas utworzenia sesji |
| dataWygasniecia | DateTime | Format: YYYY-MM-DD HH:MM:SS, min: dataUtworzenia + 30 min | Data i czas wygaśnięcia sesji |
| adresIP | adresIP (Value Object) | Wymagane | Adres IP użytkownika |
| status | Enum | Wartości: AKTYWNA, WYGASLA, WYLOGOWANA | Status sesji |
| uzytkownikID | UUID | Wymagane, referencja do AgregatuUżytkownika | Identyfikator użytkownika (referencja między agregatami) |
| token | Token (Value Object) | Wymagane, unikalny | Token sesji użytkownika |

**Zawarte w agregacie:**
- **Token** (Value Object) - token sesji z wartością, typem i datą wygaśnięcia
- **adresIP** (Value Object) - adres IP z walidacją formatu

**Referencje (poza agregatem):**
- **Użytkownik** (UUID) - referencja do AgregatuUżytkownika

**Operacje:**
- `zakoncz()` - Zakończenie sesji (zmiana statusu na WYLOGOWANA lub WYGASLA)

---

#### Agregat: AgregatUżytkownika

**Root Entity: Użytkownik**

| Atrybut | Typ | Ograniczenia | Opis |
|---------|-----|--------------|------|
| id | UUID | Unikalny, generowany automatycznie | Identyfikator użytkownika |
| pesel | String | Format: 11 cyfr, walidacja sumy kontrolnej, unikalny | PESEL użytkownika |
| email | String | Format: email, unikalny, Max: 100 znaków | Adres email użytkownika |
| status | Enum | Wartości: AKTYWNY, ZABLOKOWANY, NIEAKTYWNY | Status konta użytkownika |
| dataUtworzenia | DateTime | Format: YYYY-MM-DD HH:MM:SS | Data utworzenia konta |

**Zawarte w agregacie:**
- **Hasło** (Value Object) - hasło użytkownika (zahashowane) z metodą walidacji
- **Login** (Value Object) - login użytkownika z metodą walidacji

#### Value Object: Login (zawarte w AgregacieUżytkownika)

| Atrybut | Typ | Ograniczenia | Opis |
|---------|-----|--------------|------|
| wartosc | String | Min: 3 znaki, Max: 50 znaków, tylko litery, cyfry, podkreślenia | Login użytkownika (może być email lub nazwa użytkownika) |

**Operacje:**
- `walidacja()` - Sprawdzenie formatu i długości loginu

**Niezmienność:** Obiekt wartości jest niezmienny.

#### Value Object: Hasło (zawarte w AgregacieUżytkownika)

| Atrybut | Typ | Ograniczenia | Opis |
|---------|-----|--------------|------|
| hash | String | Format: bcrypt hash, Min: 60 znaków | Zahashowane hasło użytkownika |
| sol | String | Format: salt dla bcrypt | Sól użyta do hashowania |

**Operacje:**
- `walidacja()` - Walidacja hasła (sprawdzenie czy hash i sól są poprawne)

**Niezmienność:** Obiekt wartości jest niezmienny - hasło nie może być odczytane w formie plaintext.

#### Value Object: Token (zawarte w AgregacieSesji)

| Atrybut | Typ | Ograniczenia | Opis |
|---------|-----|--------------|------|
| wartosc | String | Format: JWT token, Min: 100 znaków | Wartość tokena sesji |
| typ | Enum | Wartości: SESJA, ODŚWIEŻENIE, RESET_HASLA | Typ tokena |
| dataWygasniecia | DateTime | Format: YYYY-MM-DD HH:MM:SS | Data wygaśnięcia tokena |

**Niezmienność:** Obiekt wartości jest niezmienny.

#### Value Object: adresIP (zawarte w AgregacieSesji)

| Atrybut | Typ | Ograniczenia | Opis |
|---------|-----|--------------|------|
| wartosc | String | Format: IPv4 lub IPv6 | Wartość adresu IP |

**Operacje:**
- `walidacjaFormatu()` - Sprawdzenie czy adres IP ma poprawny format (IPv4 lub IPv6)

**Niezmienność:** Obiekt wartości jest niezmienny.

---

### Bounded Context: Przelewy

#### Agregat: AgregatPrzelewu

**Root Entity: Przelew**

| Atrybut | Typ | Ograniczenia | Opis |
|---------|-----|--------------|------|
| id | UUID | Unikalny, generowany automatycznie | Identyfikator przelewu |
| data wykonania | DateTime | Format: YYYY-MM-DD HH:MM:SS | Data i czas wykonania przelewu |
| status | Enum | Wartości: OCZEKUJACY, WYKONANY, ANULOWANY | Status przelewu |
| użytkownik ID | UUID | Wymagane, referencja do AgregatuUżytkownika | Identyfikator użytkownika wykonującego przelew (referencja między agregatami) |
| konto nadawcy | NumerKonta (Value Object) | Wymagane, Format: XX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX (26 cyfr) | Konto źródłowe przelewu |
| konto odbiorcy | NumerKonta (Value Object) | Wymagane, Format: XX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX (26 cyfr), różne od nadawcy | Konto docelowe przelewu |
| tytuł | String | Wymagane, Max: 140 znaków | Tytuł przelewu |

**Zawarte w agregacie:**
- **Kwota** (Value Object) - kwota przelewu z walutą
- **NumerKonta** (Value Object) - używany dla konta nadawcy i odbiorcy

**Referencje (poza agregatem):**
- **Użytkownik** (UUID) - referencja do AgregatuUżytkownika
- **KontoBankowe** (Entity) - referencja przez NumerKonta
- **Klient** (Entity) - referencja przez KontoBankowe

**Operacje:**
- `zrealizuj()` - Realizacja przelewu (walidacja: użytkownik uwierzytelniony, status = OCZEKUJACY, konto nadawcy i odbiorcy różne, zmiana statusu na WYKONANY)
- `anuluj()` - Anulowanie przelewu (walidacja: użytkownik ma uprawnienia, tylko jeśli status = OCZEKUJACY lub WYKONANY, zmiana statusu na ANULOWANY)

#### Value Object: Kwota (zawarte w AgregaciePrzelewu)

| Atrybut | Typ | Ograniczenia | Opis |
|---------|-----|--------------|------|
| wartosc | Decimal | Min: 0.01, Max: 1000000.00, Precyzja: 2 miejsca po przecinku | Wartość kwoty |
| waluta | String | Wartości: "PLN", "EUR", "USD" | Waluta kwoty |

**Niezmienność:** Obiekt wartości jest niezmienny.

#### Value Object: NumerKonta (zawarte w AgregaciePrzelewu)

| Atrybut | Typ | Ograniczenia | Opis |
|---------|-----|--------------|------|
| numer | String | Format: XX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX (26 cyfr) | Numer konta zgodny z formatem IBAN |
| kodBanku | String | 4 cyfry | Kod banku (pierwsze 4 cyfry numeru konta) |
| cyfraKontrolna | String | 2 cyfry | Cyfra kontrolna (pierwsze 2 cyfry numeru konta) |

**Referencje (poza agregatem):**
- **KontoBankowe** (Entity) - referencja do konta bankowego powiązanego z numerem konta

**Niezmienność:** Obiekt wartości jest niezmienny.

---

#### Entity: KontoBankowe (referencja z NumerKonta)

| Atrybut | Typ | Ograniczenia | Opis |
|---------|-----|--------------|------|
| numerkonta | String | Format: XX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX (26 cyfr), unikalny | Numer konta bankowego |
| wlascicielId | UUID | Wymagane, referencja do Klienta | Identyfikator właściciela konta |

**Referencje:**
- **Klient** (Entity) - właściciel konta

---

#### Entity: Klient (referencja z KontoBankowe)

| Atrybut | Typ | Ograniczenia | Opis |
|---------|-----|--------------|------|
| id | UUID | Unikalny, generowany automatycznie | Identyfikator klienta |
| imie | String | Min: 2 znaki, Max: 50 znaków, tylko litery | Imię klienta |
| nazwisko | String | Min: 2 znaki, Max: 50 znaków, tylko litery | Nazwisko klienta |
| adres | String | Min: 3 znaki, Max: 100 znaków | Adres zamieszkania klienta |
| kodPocztowy | String | Format: XX-XXX (5 cyfr, myślnik, 3 cyfry) | Kod pocztowy zgodny z formatem polskim |
| Miejscowosc | String | Min: 2 znaki, Max: 50 znaków, tylko litery | Miejscowość zamieszkania klienta |

---

### Integracja między kontekstami

Kontekst **Przelewy** komunikuje się z kontekstem **Uwierzytelnienie** w celu weryfikacji tożsamości użytkownika przed wykonaniem operacji na przelewach. Przed utworzeniem, wykonaniem lub anulowaniem przelewu następuje weryfikacja tokena sesji oraz statusu użytkownika. W przypadku zablokowania konta użytkownika, wszystkie aktywne sesje są automatycznie wylogowywane.

### Zasady bezpieczeństwa

Wszystkie dane wejściowe są walidowane przed utworzeniem obiektów. Obiekty wartości są niezmienne, a operacje na agregatach wykonywane w transakcjach. Hasła przechowywane są w formie hash (bcrypt), a tokeny sesji mają ograniczony czas życia (30 minut). Sesje są automatycznie wylogowywane przy blokadzie konta użytkownika.
15 changes: 11 additions & 4 deletions Python/Flask_Book_Library/project/books/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from project import db
from project.books.models import Book
from project.books.forms import CreateBook
from bleach import clean


# Blueprint for books
Expand Down Expand Up @@ -32,7 +33,11 @@ def list_books_json():
def create_book():
data = request.get_json()

new_book = Book(name=data['name'], author=data['author'], year_published=data['year_published'], book_type=data['book_type'])
# Sanitize input data to prevent XSS attacks
sanitized_name = clean(data['name'], tags=[], strip=True)
sanitized_author = clean(data['author'], tags=[], strip=True)

new_book = Book(name=sanitized_name, author=sanitized_author, year_published=data['year_published'], book_type=data['book_type'])

try:
# Add the new book to the session and commit to save to the database
Expand Down Expand Up @@ -62,9 +67,11 @@ def edit_book(book_id):
# Get data from the request as JSON
data = request.get_json()

# Update book details
book.name = data.get('name', book.name) # Update if data exists, otherwise keep the same
book.author = data.get('author', book.author)
# Sanitize input data to prevent XSS attacks
if data.get('name'):
book.name = clean(data.get('name'), tags=[], strip=True)
if data.get('author'):
book.author = clean(data.get('author'), tags=[], strip=True)
book.year_published = data.get('year_published', book.year_published)
book.book_type = data.get('book_type', book.book_type)

Expand Down
15 changes: 12 additions & 3 deletions Python/Flask_Book_Library/project/customers/views.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from flask import render_template, Blueprint, request, redirect, url_for, jsonify
from project import db
from project.customers.models import Customer
from bleach import clean


# Blueprint for customers
Expand Down Expand Up @@ -35,7 +36,11 @@ def create_customer():
print('Invalid form data')
return jsonify({'error': 'Invalid form data'}), 400

new_customer = Customer(name=data['name'], city=data['city'], age=data['age'])
# Sanitize input data to prevent XSS attacks
sanitized_name = clean(data['name'], tags=[], strip=True)
sanitized_city = clean(data['city'], tags=[], strip=True)

new_customer = Customer(name=sanitized_name, city=sanitized_city, age=data['age'])

try:
# Add the new customer to the session and commit to save to the database
Expand Down Expand Up @@ -84,9 +89,13 @@ def edit_customer(customer_id):
# Get data from the request
data = request.form

# Sanitize input data to prevent XSS attacks
sanitized_name = clean(data['name'], tags=[], strip=True)
sanitized_city = clean(data['city'], tags=[], strip=True)

# Update customer details
customer.name = data['name']
customer.city = data['city']
customer.name = sanitized_name
customer.city = sanitized_city
customer.age = data['age']

# Commit the changes to the database
Expand Down
Binary file added Python/Flask_Book_Library/project/data.sqlite
Binary file not shown.
4 changes: 2 additions & 2 deletions Python/Flask_Book_Library/project/templates/books.html
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ <h1 class="my-4">Books</h1>
<!-- Loop through books and display each book -->
{% for book in books %}
<tr>
<td>{{ book.name | safe }}</td>
<td>{{ book.author | safe }}</td>
<td>{{ book.name }}</td>
<td>{{ book.author }}</td>
<td>{{ book.year_published }}</td> <!-- Display Year Published -->
<td>{{ book.book_type }}</td> <!-- Display Book Type -->
<td>
Expand Down
4 changes: 2 additions & 2 deletions Python/Flask_Book_Library/project/templates/customers.html
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ <h1 class="my-4">Customers</h1>
<!-- Loop through customers and display each customer -->
{% for customer in customers %}
<tr>
<td>{{ customer.name | safe }}</td>
<td>{{ customer.city | safe }}</td> <!-- Display 'city' instead of 'author' -->
<td>{{ customer.name }}</td>
<td>{{ customer.city }}</td> <!-- Display 'city' instead of 'author' -->
<td>{{ customer.age }}</td> <!-- Display 'age' -->
<td>
<a href="#" class="btn btn-warning btn-sm" onclick="editCustomer({{ customer.id }})">Edit</a>
Expand Down
1 change: 1 addition & 0 deletions Python/Flask_Book_Library/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ SQLAlchemy==2.0.21
typing_extensions==4.8.0
Werkzeug==2.3.7
WTForms==3.0.1
bleach==6.1.0
Binary file added __pycache__/test.cpython-311.pyc
Binary file not shown.
Binary file added img/po_poprawce_proba_wstrzykniecia.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added img/po_poprawce_rezultat.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added img/po_poprawce_rezultat2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added img/przelew.drawio.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added img/uwierz.drawio.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added img/wstrzykniecie_blah.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added img/wstrzykniecie_rezultat.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added img/wstrzykniete_kody.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
41 changes: 41 additions & 0 deletions test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
"""
Test jednostkowy weryfikujący walidator danych wejściowych.

Ten test weryfikuje funkcję sanitizacji (bleach.clean) używaną w aplikacji Flask
do zapobiegania atakom XSS. Test sprawdza zarówno poprawne, jak i niepoprawne
warianty danych wejściowych.
"""

import unittest
from bleach import clean

class TestXSSPrevention(unittest.TestCase):

def test_xss_prevention(self):
"""Test: kod XSS powinien być usunięty przez sanitizację"""
malicious_input = "<script>alert('XSS')</script>"
sanitized = clean(malicious_input, tags=[], strip=True)

self.assertNotIn('<script>', sanitized)
self.assertNotIn('</script>', sanitized)
self.assertNotIn('<', sanitized)

def test_valid_input(self):
"""Test: poprawne dane powinny być zaakceptowane"""
valid_input = "Jan Kowalski"
sanitized = clean(valid_input, tags=[], strip=True)

# Poprawne dane powinny pozostać niezmienione
self.assertEqual(valid_input, sanitized)

def test_html_tags_removed(self):
"""Test: tagi HTML powinny być usunięte"""
html_input = "<b>Bold text</b>"
sanitized = clean(html_input, tags=[], strip=True)

self.assertNotIn('<b>', sanitized)
self.assertNotIn('</b>', sanitized)
self.assertEqual("Bold text", sanitized)

if __name__ == '__main__':
unittest.main()