Skip to content
This repository has been archived by the owner on Apr 9, 2023. It is now read-only.

Commit

Permalink
Fix merge conflicts
Browse files Browse the repository at this point in the history
  • Loading branch information
maiconandsilva committed Nov 4, 2020
2 parents 80408ae + 824af91 commit b2a4590
Show file tree
Hide file tree
Showing 14 changed files with 141 additions and 46 deletions.
5 changes: 1 addition & 4 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,6 @@ ISOLATED_DB_USER=postgres
# -- Used in by backend image on Dockerfile
PYTHON_DEBUG=1

# -- Used by the python debugger
PYTHON_DEBUG_PORT=10001

# -- Flask Settings
FLASK_APPSETTINGS=settings.Dev
FLASK_ENV=development
Expand All @@ -24,4 +21,4 @@ FLASK_PORT=80

# --- SQL Alchemy settings
BASE_DB_URI=postgresql://postgres:{PASSWORD}@db:5432/store
BASE_ISOLATED_DB_URI=postgresql://postgres:{PASSWORD}@$db_isolated:5432/isolatedstore
BASE_ISOLATED_DB_URI=postgresql://postgres:{PASSWORD}@db_isolated:5432/isolatedstore
7 changes: 4 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
FROM postgres as dellstore

ADD https://git.io/JT1ee /docker-entrypoint-initdb.d/dellstore.sql
ADD https://git.io/JTdKr /docker-entrypoint-initdb.d/dellstore.sql
RUN chmod 744 /docker-entrypoint-initdb.d/dellstore.sql

FROM postgres as dellstore_isolated

# COPY sql/dellstore_isolated.sql /usr/src/dellstore_isolated.sql
ADD https://git.io/JTdKZ /docker-entrypoint-initdb.d/dellstore_isolated.sql
RUN chmod 744 /docker-entrypoint-initdb.d/dellstore_isolated.sql

FROM python:3.7 AS backend
FROM python:3.8 AS backend
ARG PYTHON_DEBUG

# Assign value 1 to variables in Debug Mode
Expand Down
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ docker-compose up --build -d
docker-compose up -d

# URL de acesso
url: http://127.0.0.1:5000
url: http://127.0.0.1/signup
```

## :beers: Contribuições
Expand Down Expand Up @@ -72,15 +72,15 @@ divididas em sprints, e para controle dessas, foram utilizados ferramentas como
- [x] BurnDown / Velocity Chart

### Sprint 4
- [ ] Aplicar máscaras ao cliente deletado no banco para campos o e-mail, cartão de crédito e telefone
- [ ] Aplicar máscaras para visualização de informações pessoais do cliente em sua conta
- [ ] Validação dos campos do usuário
- [ ] Pseudonimização (transferir dados do cliente para outra tabela)
- [ ] Criptografar dados pessoais do cliente "pseudonimizado"
- [ ] Implementar autorização para clientes com senhas antes não criptografadas
- [x] Aplicar máscaras ao cliente deletado no banco para campos o e-mail, cartão de crédito e telefone
- [x] Aplicar máscaras para visualização de informações pessoais do cliente em sua conta
- [x] Pseudonimização (transferir dados do cliente para outra tabela)
- [x] Criptografar dados pessoais do cliente "pseudonimizado"

### Sprint 5
- [ ] Implementar estrutura para guardar chaves advindas da Pseudonimização do cliente
- [ ] Implementar autorização para clientes com senhas antes não criptografadas
- [ ] Validação dos campos do usuário

### Sprint 6
- [ ] Implementar tela de visualização de produtos
Expand Down
12 changes: 11 additions & 1 deletion app.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_sqlalchemy.model import Model
from werkzeug.utils import import_string

import os

# Flask Settings

app = Flask(__name__)

cfg = import_string(os.environ.get('FLASK_APPSETTINGS'))()
app.config.from_object(cfg)

db = SQLAlchemy(app)
# SQLAlchemy settings

class BaseModel(Model):
def save(self):
db.session.add(self)
db.session.commit()

db = SQLAlchemy(app, model_class=BaseModel)
4 changes: 2 additions & 2 deletions forms/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ class SignupForm(ModelForm):
class Meta:
model = Customers
only = [
'firstname', 'lastname',
'email', 'phone', 'firstname', 'lastname',
'gender', 'age', 'income',
'country', 'zip', 'city', 'state',
'address1', 'address2',
'creditcard', 'creditcardexpiration',
'phone', 'username', 'email', 'password',
'username', 'password',
]

class UpdateAccountForm(SignupForm):
Expand Down
22 changes: 19 additions & 3 deletions helpers.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
from flask import request, redirect, url_for, flash, session
from functools import wraps
from functools import partial, wraps

from flask.globals import g

from app import db
from models import Customers

import re


def login_required(view):
Expand All @@ -30,7 +31,7 @@ def wrapper(*args, **kwargs):

def commit_on_finish(f):
"""
Adiciona os objetos atraves da sintaxe 'yield [objeto]' e faz commit.
Adiciona aos objetos atraves da sintaxe 'yield [objeto]' e faz commit.
"""
@wraps(f)
def wrapper(self, *args, **kwargs):
Expand Down Expand Up @@ -63,3 +64,18 @@ def wrapper(self, *args, **kwargs):
kwargs['form'] = form
return f(self, *args, **kwargs)
return wrapper

class _Mask:
EMAIL = r'(?<=[a-z]{2})\w+(?=@)'
EMAIL_ANONYMIZATION = r'^\w+(?=@)'

def __call__(self, data, show_begin=0, show_end=0, /, *,
pattern=None, mask_char='*', n_mask_char=6):
if data is None:
return None
if pattern is None:
return data[:show_begin] + mask_char * n_mask_char \
+ data[len(data) - show_end:]
return re.sub(pattern, mask_char * n_mask_char, data)

mask = _Mask()
25 changes: 13 additions & 12 deletions models/customers.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from datetime import datetime

from flask.globals import request

from helpers import mask
from app import db

import sqlalchemy_utils as su
Expand Down Expand Up @@ -33,22 +35,18 @@ class Customers(db.Model):
city = db.Column(db.String(50), info={'anonymize': True})
state = db.Column(db.String(50), info={'anonymize': True})
zip = db.Column(db.Integer, info={'anonymize': True})
country = db.Column(su.CountryType, info={'anonymize': True})
country = db.Column(su.CountryType)
region = db.Column(db.Integer)
email = db.Column(su.EmailType(50))
phone = db.Column(su.PhoneNumberType(max_length=50),
info={'anonymize': True})
phone = db.Column(su.PhoneNumberType(max_length=50))
creditcardtype = db.Column(db.Integer, info={'anonymize': True})
creditcard = db.Column(db.String(50), info={'anonymize': True})
creditcardexpiration = db.Column(db.String(50),
info={'anonymize': True})
creditcardexpiration = db.Column(db.String(50), info={'anonymize': True})
username = db.Column(db.String(50), info={'anonymize': True})
password = db.Column(su.PasswordType(schemes=['pbkdf2_sha512']),
info={'anonymize': True})
password = db.Column(su.PasswordType(schemes=['pbkdf2_sha512']))
age = db.Column(db.Integer, info={'anonymize': True})
income = db.Column(db.Integer, info={'anonymize': True})
gender = db.Column(su.ChoiceType(GENDERS,
impl=db.String(1)), info={'anonymize': True})
gender = db.Column(su.ChoiceType(GENDERS, impl=db.String(1)))
_deleted_at = db.Column('deleted_at', db.DateTime)
shopping_history = db.relationship('Orders', secondary='cust_hist')

Expand All @@ -65,9 +63,12 @@ def from_form(cls, form):

def anonymized(self):
"""Anonimiza informacoes que identificam uma pessoa"""
# TODO: Change anonymization process to
# TODO: Atualizar forma de aplicar data
self._deleted = datetime.now()
self._deleted_at = datetime.now()
self.phone = self.phone and mask(self.phone.e164[1:], 5, 0)
self.email = self.email and \
mask(self.email, pattern=mask.EMAIL_ANONYMIZATION, n_mask_char=2)
self.password = None

for column in self.__table__.columns:
if column.info.get('anonymize'):
setattr(self, column.name, None)
Expand Down
2 changes: 2 additions & 0 deletions models/isolated/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from .customer_personal_info import *

44 changes: 41 additions & 3 deletions models/isolated/customer_personal_info.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,43 @@
from flask.globals import session
from sqlalchemy_utils.types.encrypted.encrypted_type import AesEngine, FernetEngine
from sqlalchemy_utils import EncryptedType
from cryptography.fernet import Fernet

from app import db
from models.customers import Customers


def _get_customer_personal_info(model, ColumnType, engine, key):

AnonymizedColumnsMixin = type('AnonymizedColumnsMixin', (), {
column.name: db.Column(ColumnType(column.type, key, engine))
for column in model.__table__.columns
if column.info.get('anonymize')
})

class CustomerPersonalInfo(AnonymizedColumnsMixin, db.Model):
"""
Class writes deleted customer's personal info encrypted
on the isolated database
"""
__bind_key__ = 'db_isolated'
__tablename__ = 'customers_personal_info'

customerid = db.Column('customerid', db.Integer, primary_key=True)

@classmethod
def from_customer(cls, customer):
self = cls()
for column in self.__table__.columns:
setattr(self, column.name, getattr(customer, column.name))
return self

return CustomerPersonalInfo


def _get_key():
session['cryptkey'] = session.get('cryptkey', Fernet.generate_key())
return session['cryptkey']

class CustomerPersonalInfo(db.Model):
__bind_key__ = 'db_isolated'

CustomerPersonalInfo = _get_customer_personal_info(
Customers, EncryptedType, FernetEngine, _get_key)
3 changes: 3 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
astroid==2.4.2
Babel==2.8.0
cffi==1.14.3
click==7.1.2
colorama==0.4.3
cryptography==3.2.1
debugpy==1.0.0
decorator==4.4.2
Flask==1.1.2
Expand All @@ -18,6 +20,7 @@ mccabe==0.6.1
passlib==1.7.4
phonenumbers==8.12.11
psycopg2==2.8.6
pycparser==2.20
pylint==2.6.0
pytz==2020.1
six==1.15.0
Expand Down
14 changes: 12 additions & 2 deletions server.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,25 @@

from views import *

from helpers import mask


@app.before_request
def assign_loggedin_customer():
g.user = None
customerid = session.get('customerid')
if customerid is not None:
customer = Customers.query.get(customerid)
if customer is not None:
g.user = customer if customer.is_active else None
if customer is not None and customer.is_active:
g.user = customer
else:
session.clear()

# TODO: for sprint-4. To be refactored
@app.context_processor
def utility_processor():
"""Pass mask function to jinja"""
return dict(mask=mask)


app.add_url_rule('/signin', view_func=Signin.as_view('signin'))
Expand Down
4 changes: 1 addition & 3 deletions settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,4 @@ class Dev(Settings):
DEBUG = True
TESTING = True

SQLALCHEMY_ECHO = True

PYTHON_DEBUG_PORT = os.environ['PYTHON_DEBUG_PORT']
SQLALCHEMY_ECHO = True
11 changes: 10 additions & 1 deletion templates/account.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,17 @@

<table class="uk-table uk-table-middle uk-table-divider uk-text-left">
<tbody>
{# TODO: Refactor this uglyness. It works for now :) #}
{% for field in form %}
<tr><td>{{ field.label.text|upper }}</td><td>{{ field.data }}</td></tr>
{% if field.label.text == 'gender' %}
<tr><td>{{ field.label.text|upper }}</td><td>{{ field.object_data.value or "" }}</td></tr>
{% elif field.label.text == 'email' %}
<tr><td>{{ field.label.text|upper }}</td><td>{{ mask(field.data, pattern=mask.EMAIL) }}</td></tr>
{% elif field.label.text == 'phone' %}
<tr><td>{{ field.label.text|upper }}</td><td>{{ mask(field.data.international, 4, 2) }}</td></tr>
{% else %}
<tr><td>{{ field.label.text|upper }}</td><td>{{ field.data or "" }}</td></tr>
{% endif %}
{% endfor %}
<tbody>
</table>
Expand Down
20 changes: 15 additions & 5 deletions views/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
)
from forms.account import SignupForm, SigninForm, UpdateAccountForm
from models.customers import Customers
from models.isolated import CustomerPersonalInfo
from views.utils import (FormMethodView, MethodViewWrapper,
RequiredLoggedoutViewMixin, RequiredLoginViewMixin,
)
Expand All @@ -26,8 +27,8 @@ def get(self):
# TODO:
return super().get()

@commit_on_finish
@form_validated_or_page_with_errors
@commit_on_finish
def post(self, form=None):
customer = Customers.from_form(form)
yield customer # deletes customer
Expand All @@ -48,17 +49,26 @@ def get(self):
class AccountDelete(MethodViewWrapper, RequiredLoginViewMixin):
"""Rota para anonimizar Customer"""

@commit_on_finish
def get(self):
if g.user is None:
flash('Customer not found', category='error')
abort(404)

yield g.user.anonymized()

user_personal_info = CustomerPersonalInfo.from_customer(g.user)
g.user.anonymized().save()
user_personal_info.save()
self.store_key_id(user_personal_info.customerid)

flash("We're sorry to see you go :(")
return redirect(url_for(Signout.ROUTE))

def store_key_id(self, customerid):
"""Store id;key on an isolated environment"""
# TODO: Change how to manage the keys
# something like KMS from Amazon
with open('isolated_db_keys.txt', 'a') as f:
f.write('%s;%s\n' % (customerid, session['cryptkey'].decode()))


class Signin(FormMethodView, RequiredLoggedoutViewMixin):
"""Rota de login"""
Expand Down Expand Up @@ -86,8 +96,8 @@ class Signup(FormMethodView, RequiredLoggedoutViewMixin):

FORM = SignupForm

@commit_on_finish
@form_validated_or_page_with_errors
@commit_on_finish
def post(self, form=None):
# TODO: Verify if username doesn't exist already
customer = Customers.from_form(form)
Expand Down

0 comments on commit b2a4590

Please sign in to comment.