-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtools.py
384 lines (305 loc) · 15.2 KB
/
tools.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
import os
from datetime import datetime, timedelta
import threading
import math
import string
import random
import pyotp
import qrcode
import json
import requests
# import time # For testing
from flask import copy_current_request_context, redirect, url_for, request, render_template
from flask_mail import Message as _Message
from flask_sqlalchemy import SQLAlchemy
from PIL import Image, ImageFont
from io import BytesIO
# from captcha.image import ImageCaptcha
from flask_scrypt import generate_random_salt, generate_password_hash, check_password_hash
from app import app, mail, db, cookie_maxAge, client_maxAge
from app import RESTRIC_PASSWORD_RESET, PASSWORD_MAX_LENGTH, PASSWORD_MIN_LENGTH
from models import Cookies, Account, User, CommonPasswords
DEFAULT_RECIPIENTS = ["[email protected]"] # Dette er ei liste over alle default mottakere av mailen, hver mottaker skilles med komma
DOMAIN_NAME = 'jamvp.tk'
DEFAULT_MESSAGE_SUBJECT = "Flask test email, sent from server as " + os.environ.get('MAIL_USERNAME_FLASK')
TEST_BODY="text body"
Norwegian_characters = "æøåÆØÅ"
def valid_account_number(account_number):
divided = account_number.split('.')
if len(divided) != 3 and not contain_allowed_symbols(account_number, whitelist=string.digits + '.'):
return False
if not valid_number(divided[0], min_length=4, max_length=4):
return False
if not valid_number(divided[1], min_length=2, max_length=2):
return False
if not valid_number(divided[2], min_length=5, max_length=5):
return False
return True
def generate_account_numbers(amount=1, base="1337"):
accounts = Account.query.all()
account_numbers = set()
new_account_numbers = []
for account in accounts:
account_numbers.add(account.account_number)
created = 0
while created < amount:
suggestion = generate_account_number(base="1337")
if suggestion not in account_numbers:
new_account_numbers.append(suggestion)
created += 1
return new_account_numbers
def generate_account_number(base="1337"):
two_digit = str(random.randint(1, 99))
five_digit = str(random.randint(1, 99999))
while len(two_digit) < 2:
two_digit = "0" + two_digit
while len(five_digit) < 5:
five_digit = "0" + five_digit
return base + "." + two_digit + "." + five_digit
def generate_QR(fname, id, secret_key=None, save=False):
if secret_key is None:
secret_key = pyotp.random_base32(length=32) # Using 256 bits secret key, see source below
secret_uri = pyotp.totp.TOTP(secret_key).provisioning_uri(name=(str(fname) + ', User ID: ' + str(id)), issuer_name='JAMVP Bank')
qr = qrcode.QRCode(
version=1,
error_correction=qrcode.constants.ERROR_CORRECT_L,
box_size=10,
border=4,
)
qr.add_data(secret_uri)
qr.make(fit=True)
img = qr.make_image(fill_color="black", back_color="white")
width, height = img.size
logo_size = 80
logo = Image.open('static/assets/logo_no_white.png')
xmin = ymin = int((width / 2) - (logo_size / 2))
xmax = ymax = int((width / 2) + (logo_size / 2))
logo = logo.resize((xmax - xmin, ymax - ymin))
img.paste(logo, (xmin, ymin, xmax, ymax))
if save:
img.save('qrcode_1.png')
img_io = BytesIO()
img.save(img_io, 'PNG', quality=100)
img_io.seek(0)
return secret_key, img_io
def is_human(captcha_response):
""" Validating recaptcha response from google server
Returns True captcha test passed for submitted form else returns False.
"""
secret = os.environ.get('SITE_KEY')
payload = {'response':captcha_response, 'secret':secret}
response = requests.post("https://www.google.com/recaptcha/api/siteverify", payload)
response_text = json.loads(response.text)
return response_text['success']
def insertion_sort_transactions(transaction_list):
for element in range(1, len(transaction_list)):
index = element
while transaction_list[index].transfer_time > transaction_list[index-1].transfer_time and index > 0:
temp = transaction_list[index]
transaction_list[index] = transaction_list[index-1]
transaction_list[index-1] = temp
index -= 1
def get_valid_cookie():
for cookie in extract_cookies():
valid = valid_cookie(cookie)
if valid:
return cookie
return None
# Get list of cookies from cookie_header
def extract_cookies():
if 'cookie' in request.headers:
cookie_header = request.headers['cookie']
cookie_header = cookie_header.replace('__Secure-sessionId=','')
cookie_header = cookie_header.replace('sessionId=','')
cookie_list = cookie_header.split('; ')
return cookie_list
return []
# Check if cookie is valid
def valid_cookie(cookie_in_question):
# Kontrollerer om koden inneholder gyldige symboler før vi prøver å søke gjennom databasen med den.
if contain_allowed_symbols(s=cookie_in_question, whitelist=string.ascii_letters + string.digits):
cookie = Cookies.query.filter_by(session_cookie=cookie_in_question).first()
if cookie is None:
return None # Hvis cookien ikke finnes i database
# Cookies er koblet opp mot ip til klienten,
# om cookie blir stjålet av noen og brukt en annen plass så får ikke "tyven" logge seg inn for det om.
# Returnerer False, da vil cookie bli slettet en eller annen gang, og brukeren blir da også logget ut
if 'x-forwarded-for' in request.headers:
ip = request.headers.get('x-forwarded-for')
else:
ip = request.remote_addr
if cookie.ip != ip:
user_object = User.query.filter_by(user_id=cookie.user_id).first()
html_template = render_template('/mails/stolen_cookie.html',
fname=user_object.fname, mname=user_object.mname,
lname=user_object.lname, ip=ip, date=datetime.now())
send_mail(recipients=[user_object.email],
subject="Someone tried to sign in to your account", body="", html=html_template)
return False
# Hent ut dato og klokkeslett denne cookien er gyldig til
valid_to = datetime.strptime(cookie.valid_to, "%Y-%m-%d %H:%M:%S.%f")
if datetime.now() > valid_to:
return False # Hvis cookien er for gammel
return True # Hvis cookien er gyldig
# En ugyldig cookie, den inneholder ugyldige tegn, og kan derfor ikke finnes i databasen, return None
return None
def update_cookie_clientside(cookie_in_question, resp, age=cookie_maxAge + client_maxAge):
if "https://" in request.host_url:
resp.headers.set('Set-Cookie', "__Secure-sessionId=" + cookie_in_question + "; Max-Age=" + str(age) + "; SameSite=Strict; Secure; HttpOnly")
else:
resp.headers.set('Set-Cookie', "sessionId=" + cookie_in_question + "; Max-Age=" + str(age) + "; SameSite=Strict; HttpOnly")
def update_cookie_serverside(cookie_in_question, age=cookie_maxAge + client_maxAge):
cookie = Cookies.query.filter_by(session_cookie=cookie_in_question).first()
if cookie is not None:
cookie.valid_to = str(datetime.now() + timedelta(seconds=cookie_maxAge))
db.session.commit()
return True # Kalrte å oppdatere cookie
return False # Klarte ikke å oppdatere cookie
def update_cookie(cookie_in_question, response, age=cookie_maxAge + client_maxAge):
update_cookie_clientside(cookie_in_question, response, age)
update_cookie_serverside(cookie_in_question, age)
# Må vurdere hvilke symboler vi kan tillate
def contain_allowed_symbols(s, whitelist=string.ascii_letters + string.digits + Norwegian_characters + string.punctuation):
for c in s:
if c not in whitelist:
return False
return True
def valid_date(date, separator='-'):
if date != '':
date = date.split(separator) # Tar bare datoer med formatet dd-mm-yyyy, separator kan endres
# Datoen må bestå av 3 tall
if len(date) == 3 and (is_number(date[0]) and is_number(date[1]) and is_number(date[2])):
try: # Prøver å konvertere datoen fra brukeren til en dato
datetime(year=int(date[2]),month=int(date[1]),day=int(date[0]))
except ValueError:
return False
else:
return False
else:
return False
return True
# Denne kan sjekkes i en if, den gir True når den returnerer text, og False når den returnerer "".
def valid_email(mail_addr, is_num=False, min_length=6, max_length=64, check_len=False, domain='uis.no', check_domain=False):
if mail_addr != '':
email = mail_addr.split('@')
if len(email) == 2 and contain_allowed_symbols(mail_addr, whitelist=string.ascii_letters + string.digits + Norwegian_characters + string.punctuation):
if is_num and not is_number(email[0]): # Sjekk om det forran @ ikke er et tall
return "NaN"
if check_len and (min_length > len(email[0]) or len(email[0]) > max_length): # Sjekk om det forran @ ikke har gyldig lengde
return "invalidLength"
if check_domain and email[1] != domain: # Sjekker om epost addresse ikke har gyldig domene
return "invalid"
else:
return "invalid"
else:
return "empty"
return ""
def valid_id(id, min_length=6, max_length=7):
if id != '':
if is_number(id): # Sjekker om id er et tall
if len(id) < 6 or len(id) > 7: # Sjekker om antall siffer i id er mindre enn 6 eller større enn 7
return "invalidLength"
else:
return "NaN"
else:
return "empty"
return ""
def generate_id():
while True:
suggested_id = int(random_string_generator(size=6, chars=string.digits))
if valid_id(str(suggested_id)) == "" and User.query.filter_by(user_id=suggested_id).first() is None:
return suggested_id
def valid_name(names, whitelist=string.ascii_letters + Norwegian_characters):
if names != '':
# Sjekker om samlingen av navn (du kan ha 2 eller flere fornavn, mellomnavn (kan ha 0), etternavn) består av andre symboler enn bokstaver
for name in names.split(' '):
if not name.isalpha():
if not contain_allowed_symbols(s=name, whitelist=whitelist): # Godtar bindestrek i gatenavn
return "invalid"
else:
return "empty"
return ""
def valid_address(address):
# Har vi fått oppgitt en addresse?
if address != '':
address = address.split(' ')
else:
return "empty"
# Sjekker om alle delene av addressen utenom slutten (gatenummeret), består av bokstaver.
for field in range(len(address) - 1):
if not address[field].isalpha():
if not contain_allowed_symbols(s=address[field], whitelist=string.ascii_letters + '-' + Norwegian_characters): # Godtar bindestrek i gatenavn
return "invalid"
# Kontrollerer at formatet og gatenummeret er gyldig
if len(address) >= 2:
if not is_number(address[-1]): # Sjekker om gatenummer ikke er et tall
# Sjekker om følgende ikke stemmer
# Siste symbol i gatenummeret er en bokstav. For eksempel, du bor i 3b, eller 5a
# Resten av gatenummeret uten siste symbol (bokstaven) er et tall
if not (len(address[-1]) >= 2 and address[-1][-1].isalpha() and is_number(address[-1][:-1])):
return "invalidNum"
else:
return "invalid"
return ""
def valid_number(number, min_length=1, max_length=8):
if is_number(number) and (min_length <= len(number) <= max_length) and '.' not in number and ',' not in number:
return True
return False
def valid_password(password, min_length=PASSWORD_MIN_LENGTH, max_length=PASSWORD_MAX_LENGTH):
if (min_length > len(password) or len(password) > max_length):
# print("password length is invalid")
return False
# Må vurdere hvilke symboler vi kan tillate, og om vi skal se etter ord/kommandoer
elif not contain_allowed_symbols(password):
# print("password contains invalid characters")
return False
# Hvis passorde finnes i databasen over top "10000" passord
elif CommonPasswords.query.filter_by(password=password).first() is not None:
return False
# Passorde er gyldig
return True
# https://stackoverflow.com/questions/2257441/random-string-generation-with-upper-case-letters-and-digits
# https://stackoverflow.com/questions/2257441/random-string-generation-with-upper-case-letters-and-digits/23728630#23728630
def random_string_generator(size=6, chars=string.ascii_letters + string.digits): # string.ascii_letters + string.digits + string.punctuation
return ''.join(random.SystemRandom().choice(chars) for _ in range(size)) # Forgot to use .SystemRandom(). Using this is more cryptographically secure, this mistake was found by running bandit
def is_number(num):
try:
float(num)
return True
except ValueError:
return False
class Message(_Message):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# work around issues with Microsoft Office365 / Outlook.com email servers
# and their inability to handle RFC2047 encoded Message-Id headers.
if len(self.msgId) > 77:
# domain = current_app.config.get('MESSAGE_ID_DOMAIN', DOMAIN_NAME) # Consider using this to get the DOMAIN_NAME
self.msgId = self.msgId.split('@')[0] + DOMAIN_NAME + '>' # Denne korter ned message ID slik at Office365 godtar mailen.
def test_html_constructor():
TEST_HTML = ( '<b>Test mail from the Flask server of ' + DOMAIN_NAME +
' </b><br> <br> This mail is sent from ' + os.environ.get('MAIL_USERNAME_FLASK') +
', who is a representative of ' + DOMAIN_NAME +
'<br><br> This mail was sent ' + str(datetime.datetime.now()))
return TEST_HTML
def send_mail(sender=None, recipients=DEFAULT_RECIPIENTS, subject=DEFAULT_MESSAGE_SUBJECT, body=TEST_BODY, html=None):
# This function is run in a separate thread to send emails.
# This will reduce the load on the main thread/program so that the responsiveness of the server is maintained.
# Sending a mail typically took 1 second for the send_mail function, now it takes 0.001 seconds typically. Used time.time() to find the time spent on sending the email
@copy_current_request_context
def mail_sender_thread(msg):
mail.send(msg)
# Konstruer melding
if sender is None:
msg = Message(subject=subject, recipients=recipients) # Use MAIL_DEFAULT_SENDER as sender
else:
msg = Message(subject=subject, sender=sender, recipients=recipients)
# The following two options (body and html) can also be included within the parenthesis when making the message above
msg.body = body # Don't know what this is
if html is None:
msg.html = test_html_constructor()
else:
msg.html = html # This is what we see when we view the mail using HTML
# Send melding
mail_sending_thread = threading.Thread(target=mail_sender_thread, args=(msg,))
mail_sending_thread.start()