Added comments
This commit is contained in:
		| @@ -17,7 +17,9 @@ def verify_token(token): | ||||
|     if token is None: | ||||
|         return False | ||||
|  | ||||
|     if ':' in token:  # Bot token | ||||
|     # We decided to append the user id to the bearer token using ":" as separator to select an specific user | ||||
|     # To validate the token we can remove the user id since we only validate the token and not the user id | ||||
|     if ':' in token: | ||||
|         token = token.split(":")[0] | ||||
|  | ||||
|     try: | ||||
|   | ||||
| @@ -31,9 +31,10 @@ def add_keyword(data): | ||||
|  | ||||
|     key = data['keyword'] | ||||
|  | ||||
|     # Check if keyword already exists | ||||
|     check_keyword = db.session.query(Keyword).filter_by(keyword=key, email=email).first() | ||||
|     if check_keyword is None: | ||||
|         # Keyword doesn't exist yet for this user | ||||
|         # Keyword doesn't exist yet for this user -> add it | ||||
|         new_keyword = Keyword( | ||||
|             email=email, | ||||
|             keyword=key | ||||
| @@ -54,17 +55,17 @@ def add_keyword(data): | ||||
| def remove_keyword(data): | ||||
|     email = get_email_or_abort_401() | ||||
|  | ||||
|     # Check if request data is valid | ||||
|     if not check_if_keyword_data_exists(data): | ||||
|         abort(400, message="Keyword missing") | ||||
|  | ||||
|     key = data['keyword'] | ||||
|  | ||||
|     check_keyword = db.session.query(Keyword).filter_by(keyword=key, email=email).first() | ||||
|  | ||||
|     # Check if keyword exists | ||||
|     check_keyword = db.session.query(Keyword).filter_by(keyword=data['keyword'], email=email).first() | ||||
|     if check_keyword is None: | ||||
|         return abort(500, "Keyword doesn't exist for this user") | ||||
|     else: | ||||
|         db.session.query(Keyword).filter_by(keyword=key, email=email).delete() | ||||
|         # Keyword exists -> delete it | ||||
|         db.session.query(Keyword).filter_by(keyword=data['keyword'], email=email).delete() | ||||
|         db.session.commit() | ||||
|  | ||||
|         return make_response({}, 200, "Successfully removed keyword") | ||||
| @@ -80,6 +81,8 @@ def get_keywords(): | ||||
|     return_keywords = [] | ||||
|     keywords = db.session.query(Keyword).filter_by(email=email).all() | ||||
|  | ||||
|     # If no keywords exist for this user -> return empty list | ||||
|     # Otherwise iterate over all keywords, convert them to json and add them to the return list | ||||
|     if keywords is not None: | ||||
|         for row in keywords: | ||||
|             return_keywords.append(row.as_dict()) | ||||
|   | ||||
| @@ -25,8 +25,12 @@ def get_portfolio(): | ||||
|     email = get_email_or_abort_401() | ||||
|  | ||||
|     return_portfolio = [] | ||||
|  | ||||
|     # Get all transactions of current user | ||||
|     transactions = db.session.execute("SELECT symbol, SUM(count), SUM(price), MAX(time) FROM `transactions` WHERE email = '" + email + "' GROUP BY symbol;").all() | ||||
|  | ||||
|     # If there are no transactions, return empty portfolio | ||||
|     # Otherwise calculate portfolio | ||||
|     if transactions is not None: | ||||
|         for row in transactions: | ||||
|             data = { | ||||
| @@ -37,6 +41,7 @@ def get_portfolio(): | ||||
|                 'current_price': 0 | ||||
|             } | ||||
|  | ||||
|             # Add current share value to portfolio | ||||
|             query_share_price = db.session.query(SharePrice).filter_by(symbol=row[0]).order_by(SharePrice.date.desc()).first() | ||||
|             if query_share_price is not None: | ||||
|                 data['current_price'] = query_share_price.as_dict()['price'] | ||||
|   | ||||
| @@ -24,6 +24,7 @@ __location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file | ||||
| @share_price_blueprint.auth_required(auth) | ||||
| @share_price_blueprint.doc(summary="Returns all transaction symbols", description="Returns all transaction symbols for all users") | ||||
| def get_transaction_symbols(): | ||||
|     # Get all transaction symbols | ||||
|     symbols = db.session.execute("SELECT symbol FROM `transactions` GROUP BY symbol;").all() | ||||
|  | ||||
|     return_symbols = [] | ||||
| @@ -39,6 +40,7 @@ def get_transaction_symbols(): | ||||
| @share_price_blueprint.auth_required(auth) | ||||
| @share_price_blueprint.doc(summary="Returns all transaction symbols", description="Returns all transaction symbols for all users") | ||||
| def add_symbol_price(data): | ||||
|     # Check if required data is available | ||||
|     if not check_if_symbol_data_exists(data): | ||||
|         abort(400, message="Symbol missing") | ||||
|  | ||||
| @@ -48,14 +50,11 @@ def add_symbol_price(data): | ||||
|     if not check_if_time_data_exists(data): | ||||
|         abort(400, message="Time missing") | ||||
|  | ||||
|     symbol = data['symbol'] | ||||
|     price = data['price'] | ||||
|     time = data['time'] | ||||
|  | ||||
|     # Add share price | ||||
|     share_price = SharePrice( | ||||
|         symbol=symbol, | ||||
|         price=price, | ||||
|         date=datetime.datetime.strptime(time, '%Y-%m-%dT%H:%M:%S.%fZ'), | ||||
|         symbol=data['symbol'], | ||||
|         price=data['price'], | ||||
|         date=datetime.datetime.strptime(data['time'], '%Y-%m-%dT%H:%M:%S.%fZ'), | ||||
|     ) | ||||
|  | ||||
|     db.session.add(share_price) | ||||
|   | ||||
| @@ -26,17 +26,17 @@ __location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file | ||||
| def add_symbol(data): | ||||
|     email = get_email_or_abort_401() | ||||
|  | ||||
|     # Check if required data is available | ||||
|     if not check_if_symbol_data_exists(data): | ||||
|         abort(400, message="Symbol missing") | ||||
|  | ||||
|     symbol = data['symbol'] | ||||
|  | ||||
|     check_share = db.session.query(Share).filter_by(symbol=symbol, email=email).first() | ||||
|     # Check if share already exists | ||||
|     check_share = db.session.query(Share).filter_by(symbol=data['symbol'], email=email).first() | ||||
|     if check_share is None: | ||||
|         # Keyword doesn't exist yet for this user | ||||
|         # Keyword doesn't exist yet for this user -> add it | ||||
|         new_symbol = Share( | ||||
|             email=email, | ||||
|             symbol=symbol | ||||
|             symbol=data['symbol'] | ||||
|         ) | ||||
|         db.session.add(new_symbol) | ||||
|         db.session.commit() | ||||
| @@ -54,17 +54,17 @@ def add_symbol(data): | ||||
| def remove_symbol(data): | ||||
|     email = get_email_or_abort_401() | ||||
|  | ||||
|     # Check if required data is available | ||||
|     if not check_if_symbol_data_exists(data): | ||||
|         abort(400, message="Symbol missing") | ||||
|  | ||||
|     symbol = data['symbol'] | ||||
|  | ||||
|     check_share = db.session.query(Share).filter_by(symbol=symbol, email=email).first() | ||||
|  | ||||
|     # Check if share exists | ||||
|     check_share = db.session.query(Share).filter_by(symbol=data['symbol'], email=email).first() | ||||
|     if check_share is None: | ||||
|         abort(500, "Symbol doesn't exist for this user") | ||||
|     else: | ||||
|         db.session.query(Share).filter_by(symbol=symbol, email=email).delete() | ||||
|         # Delete share | ||||
|         db.session.query(Share).filter_by(symbol=data['symbol'], email=email).delete() | ||||
|         db.session.commit() | ||||
|  | ||||
|         return make_response({}, 200, "Successfully removed symbol") | ||||
| @@ -80,6 +80,8 @@ def get_symbol(): | ||||
|     return_symbols = [] | ||||
|     symbols = db.session.query(Share).filter_by(email=email).all() | ||||
|  | ||||
|     # If no shares exist for this user -> return empty list | ||||
|     # Otherwise iterate over all shares, convert them to json and add them to the return list | ||||
|     if symbols is not None: | ||||
|         for row in symbols: | ||||
|             return_symbols.append(row.as_dict()) | ||||
|   | ||||
| @@ -24,11 +24,13 @@ __location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file | ||||
| def add_keyword(data): | ||||
|     email = get_email_or_abort_401() | ||||
|  | ||||
|     # Check if request data is valid | ||||
|     if not check_if_telegram_user_id_data_exists(data): | ||||
|         abort(400, message="User ID missing") | ||||
|  | ||||
|     query_user = get_user(email) | ||||
|  | ||||
|     # Change user id | ||||
|     query_user.telegram_user_id = data['telegram_user_id'] | ||||
|     db.session.commit() | ||||
|  | ||||
|   | ||||
| @@ -27,6 +27,7 @@ __location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file | ||||
| def add_transaction(data): | ||||
|     email = get_email_or_abort_401() | ||||
|  | ||||
|     # Check if required data is available | ||||
|     if not check_if_symbol_data_exists(data): | ||||
|         abort(400, "Symbol missing") | ||||
|  | ||||
| @@ -39,6 +40,7 @@ def add_transaction(data): | ||||
|     if not check_if_price_data_exists(data): | ||||
|         abort(400, "Price missing") | ||||
|  | ||||
|     # Add transaction | ||||
|     new_transaction = Transaction( | ||||
|         email=email, | ||||
|         symbol=data['symbol'], | ||||
| @@ -60,8 +62,11 @@ def get_transaction(): | ||||
|     email = get_email_or_abort_401() | ||||
|  | ||||
|     return_transactions = [] | ||||
|  | ||||
|     # Get all transactions | ||||
|     transactions = db.session.query(Transaction).filter_by(email=email).all() | ||||
|  | ||||
|     # Iterate over transactions and add them to return_transactions | ||||
|     if transactions is not None: | ||||
|         for row in transactions: | ||||
|             return_transactions.append(row.as_dict()) | ||||
|   | ||||
| @@ -28,6 +28,8 @@ def users(): | ||||
|     abort_if_no_admin() | ||||
|  | ||||
|     res = [] | ||||
|  | ||||
|     # Query all users and convert them to dicts | ||||
|     for i in User.query.all(): | ||||
|         res.append(i.as_dict()) | ||||
|  | ||||
| @@ -41,6 +43,7 @@ def users(): | ||||
| def user(): | ||||
|     email = get_email_or_abort_401() | ||||
|  | ||||
|     # Query current user | ||||
|     query_user = get_user(email) | ||||
|  | ||||
|     return make_response(query_user.as_dict(), 200, "Successfully received current user data") | ||||
| @@ -51,23 +54,26 @@ def user(): | ||||
| @users_blueprint.input(schema=LoginDataSchema) | ||||
| @users_blueprint.doc(summary="Login", description="Returns jwt token if username and password match, otherwise returns error") | ||||
| def login(data): | ||||
|     # Check if required data is available | ||||
|     if not check_if_password_data_exists(data): | ||||
|         abort(400, "Password missing") | ||||
|  | ||||
|     if not check_if_email_data_exists(data): | ||||
|         abort(400, "Email missing") | ||||
|  | ||||
|     email = data['email'] | ||||
|     password = data['password'] | ||||
|     # Query current user | ||||
|     query_user = get_user(data['email']) | ||||
|  | ||||
|     query_user = get_user(email) | ||||
|  | ||||
|     if not check_password(query_user.password, password.encode("utf-8")):  # Password incorrect | ||||
|     # Check if password matches | ||||
|     if not check_password(query_user.password, data['password'].encode("utf-8")):  # Password incorrect | ||||
|         abort(500, message="Unable to login") | ||||
|  | ||||
|     # Check if user is bot | ||||
|     if query_user.email == current_app.config['BOT_EMAIL']: | ||||
|         # Set bot token valid for 1 year | ||||
|         token = jwt.encode({'email': query_user.email, 'exp': datetime.datetime.utcnow() + datetime.timedelta(days=365)}, current_app.config['SECRET_KEY'], "HS256") | ||||
|     else: | ||||
|         # Set token valid for 1 day | ||||
|         token = jwt.encode({'email': query_user.email, 'exp': datetime.datetime.utcnow() + datetime.timedelta(days=1)}, current_app.config['SECRET_KEY'], "HS256") | ||||
|  | ||||
|     return make_response({"token": token}, 200, "Successfully logged in") | ||||
| @@ -78,6 +84,7 @@ def login(data): | ||||
| @users_blueprint.input(schema=RegisterDataSchema) | ||||
| @users_blueprint.doc(summary="Register", description="Registers user") | ||||
| def register(data): | ||||
|     # Check if required data is available | ||||
|     if not check_if_email_data_exists(data): | ||||
|         abort(400, "Email missing") | ||||
|  | ||||
| @@ -87,19 +94,16 @@ def register(data): | ||||
|     if not check_if_password_data_exists(data): | ||||
|         abort(400, "Password missing") | ||||
|  | ||||
|     email = data['email'] | ||||
|     username = data['username'] | ||||
|     password = data['password'] | ||||
|  | ||||
|     query_user = db.session.query(User).filter_by(email=email).first() | ||||
|  | ||||
|     if query_user is not None:  # Username already exist | ||||
|     # Check if user already exists | ||||
|     query_user = db.session.query(User).filter_by(email=data['email']).first() | ||||
|     if query_user is not None: | ||||
|         abort(500, message="Email already exist") | ||||
|  | ||||
|     # Add user to database | ||||
|     new_user = User( | ||||
|         email=email, | ||||
|         username=username, | ||||
|         password=hash_password(password), | ||||
|         email=data['email'], | ||||
|         username=data['username'], | ||||
|         password=hash_password(data['password']), | ||||
|         admin=False, | ||||
|         cron="0 8 * * *" | ||||
|     ) | ||||
| @@ -117,11 +121,14 @@ def register(data): | ||||
| def update_user(data): | ||||
|     email = get_email_or_abort_401() | ||||
|  | ||||
|     # Query current user | ||||
|     query_user = get_user(email) | ||||
|  | ||||
|     # Check if password data is available -> if, change password | ||||
|     if check_if_password_data_exists(data): | ||||
|         query_user.password = hash_password(data['password']) | ||||
|  | ||||
|     # Check if username data is available -> if, change username | ||||
|     if check_if_username_data_exists(data): | ||||
|         query_user.username = data['username'] | ||||
|  | ||||
| @@ -138,18 +145,18 @@ def update_user(data): | ||||
| def set_admin(data): | ||||
|     abort_if_no_admin()  # Only admin users can do this | ||||
|  | ||||
|     # Check if required data is available | ||||
|     if not check_if_email_data_exists(data): | ||||
|         abort(400, "Email missing") | ||||
|  | ||||
|     if not check_if_admin_data_exists(data): | ||||
|         abort(400, "Admin data missing") | ||||
|  | ||||
|     email = data['email'] | ||||
|     admin = data['admin'] | ||||
|     # Get user by email | ||||
|     query_user = get_user(data['email']) | ||||
|  | ||||
|     query_user = get_user(email) | ||||
|  | ||||
|     query_user.admin = admin | ||||
|     # Update user admin state | ||||
|     query_user.admin = data['admin'] | ||||
|     db.session.commit() | ||||
|  | ||||
|     return make_response({}, 200, "Successfully updated users admin rights") | ||||
| @@ -163,9 +170,11 @@ def set_admin(data): | ||||
| def set_cron(data): | ||||
|     email = get_email_or_abort_401() | ||||
|  | ||||
|     # Check if required data is available | ||||
|     if not check_if_cron_data_exists(data): | ||||
|         abort(400, "Cron data missing") | ||||
|  | ||||
|     # Update user cron | ||||
|     get_user(email).cron = data['cron'] | ||||
|     db.session.commit() | ||||
|  | ||||
| @@ -178,18 +187,22 @@ def set_cron(data): | ||||
| @users_blueprint.auth_required(auth) | ||||
| @users_blueprint.doc(summary="Delete user", description="Deletes user by username") | ||||
| def delete_user(data): | ||||
|     # Check if required data is available | ||||
|     if not check_if_email_data_exists(data): | ||||
|         abort(400, "Email missing") | ||||
|  | ||||
|     email = data['email'] | ||||
|  | ||||
|     if email == get_email_or_abort_401():  # Username is same as current user | ||||
|         db.session.query(User).filter_by(email=email).delete() | ||||
|     # Check if email to delete is current user | ||||
|     # -> if, delete user | ||||
|     # -> if not, check if user is admin | ||||
|     # -> if, delete user | ||||
|     # -> else, abort | ||||
|     if data['email'] == get_email_or_abort_401():  # Username is same as current user | ||||
|         db.session.query(User).filter_by(email=data['email']).delete() | ||||
|         db.session.commit() | ||||
|     else:  # Delete different user than my user -> only admin users | ||||
|     else: | ||||
|         abort_if_no_admin() | ||||
|  | ||||
|         db.session.query(User).filter_by(email=email).delete() | ||||
|         db.session.query(User).filter_by(email=data['email']).delete() | ||||
|         db.session.commit() | ||||
|  | ||||
|     return make_response({}, 200, "Successfully removed user") | ||||
|   | ||||
| @@ -14,28 +14,48 @@ from flask import request, jsonify | ||||
|  | ||||
|  | ||||
| def hash_password(password): | ||||
|     """ | ||||
|     Hash plain password to save it in the database | ||||
|     """ | ||||
|     return bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt()) | ||||
|  | ||||
|  | ||||
| def check_password(hashed_password, user_password): | ||||
|     """ | ||||
|     Check if the password is correct using the bcrypt checkpw function | ||||
|     """ | ||||
|     return bcrypt.checkpw(user_password, hashed_password) | ||||
|  | ||||
|  | ||||
| def get_email_from_token_data(token): | ||||
|     """ | ||||
|     Extract email from token data | ||||
|     """ | ||||
|  | ||||
|     # If token is not provided-> return None | ||||
|     if token is None or len(token) < 2: | ||||
|         return None | ||||
|     else: | ||||
|         # Token contains "Bearer " -> remove it | ||||
|         token = token[1] | ||||
|  | ||||
|     # Again: Check if token is not None | ||||
|     # Don't know why, but sometimes the token is None | ||||
|     if token is not None: | ||||
|         if ':' in token:  # Maybe bot token, check if token valid and return username after ":" then | ||||
|  | ||||
|         # We decided to append the user id to the bearer token using ":" as separator to select an specific user | ||||
|         # If the token contains ":" -> It may be a bot token | ||||
|         # If token valid -> return user email, not bot email | ||||
|         if ':' in token: | ||||
|             telegram_user_id = token.split(":")[1] | ||||
|             token = token.split(":")[0] | ||||
|  | ||||
|             try: | ||||
|                 # Only allow selecting users with telegram_user_id if current user is the bot user | ||||
|                 if jwt.decode(token, current_app.config['SECRET_KEY'], algorithms=["HS256"])['email'] == current_app.config['BOT_EMAIL']: | ||||
|                     res = db.session.query(User).filter_by(telegram_user_id=telegram_user_id).first() | ||||
|  | ||||
|                     # Check if user id exists | ||||
|                     if res is not None: | ||||
|                         return res.as_dict()['email'] | ||||
|                     else: | ||||
| @@ -47,12 +67,18 @@ def get_email_from_token_data(token): | ||||
|  | ||||
|         else:  # "Normal" token, extract username from token | ||||
|             try: | ||||
|                 # Return email from token if token is valid | ||||
|                 return jwt.decode(token, current_app.config['SECRET_KEY'], algorithms=["HS256"])['email'] | ||||
|             except jwt.PyJWTError: | ||||
|                 return None | ||||
|  | ||||
|  | ||||
| def get_token(): | ||||
|     """ | ||||
|     Extract token from Authorization header | ||||
|     """ | ||||
|  | ||||
|     # Check if Authorization header is provided | ||||
|     if 'Authorization' in request.headers: | ||||
|         return request.headers['Authorization'].split(" ") | ||||
|     else: | ||||
| @@ -60,7 +86,10 @@ def get_token(): | ||||
|  | ||||
|  | ||||
| def get_email_or_abort_401(): | ||||
|     # get username from jwt token | ||||
|     """ | ||||
|     Try to receive email from token data | ||||
|     If email is not provided -> abort 401 | ||||
|     """ | ||||
|     email = get_email_from_token_data(get_token()) | ||||
|  | ||||
|     if email is None:  # If token not provided or invalid -> return 401 code | ||||
| @@ -70,24 +99,38 @@ def get_email_or_abort_401(): | ||||
|  | ||||
|  | ||||
| def abort_if_no_admin(): | ||||
|     """ | ||||
|     Check if user is admin | ||||
|     If not -> abort 401 | ||||
|     """ | ||||
|     if not is_user_admin(): | ||||
|         abort(401, message="Only admin users can access this") | ||||
|  | ||||
|  | ||||
| def is_user_admin(): | ||||
|     """ | ||||
|     Return users admin status | ||||
|     """ | ||||
|     email = get_email_or_abort_401() | ||||
|  | ||||
|     return db.session.query(User).filter_by(email=email).first().admin | ||||
|  | ||||
|  | ||||
| def make_response(data, status=200, text=""): | ||||
|     """ | ||||
|     Generate response object | ||||
|     """ | ||||
|     return jsonify({"status": status, "text": text, "data": data}) | ||||
|  | ||||
|  | ||||
| def get_user(email): | ||||
|     """ | ||||
|     Get user from database | ||||
|     """ | ||||
|     query_user = db.session.query(User).filter_by(email=email).first() | ||||
|  | ||||
|     if query_user is None:  # Username doesn't exist | ||||
|     # Check if user exists | ||||
|     if query_user is None: | ||||
|         abort(500, message="Can't find user") | ||||
|  | ||||
|     return query_user | ||||
|   | ||||
| @@ -22,6 +22,7 @@ class UsersSchema(Schema): | ||||
|     username = String() | ||||
|     telegram_user_id = String() | ||||
|     email = Email() | ||||
|     cron = String() | ||||
|  | ||||
|  | ||||
| class AdminDataSchema(Schema): | ||||
|   | ||||
| @@ -10,17 +10,17 @@ import random | ||||
| import faker | ||||
| import requests | ||||
|  | ||||
| username = '' | ||||
| password = '' | ||||
| username = 'bot@example.com' | ||||
| password = 'bot' | ||||
|  | ||||
| shares = ["TWTR", "GOOG", "AAPL", "MSFT", "AMZN", "FB", "NFLX", "TSLA", "BABA", "BA", "BAC", "C", "CAT", "CSCO", "CVX", "DIS", "DOW", "DUK", "GE", "HD", "IBM"          "INTC", "JNJ", "JPM", "KO", | ||||
|           "MCD", "MMM", "MRK", "NKE", "PFE", "PG", "T", "UNH", "UTX", "V", "VZ", "WMT", "XOM", "YHOO", "ZTS"] | ||||
|  | ||||
| fake = faker.Faker() | ||||
|  | ||||
| token = requests.post(os.getenv("API_URL") + '/user/login', json={"email": username, "password": password}).json()['data']['token'] | ||||
| token = requests.post(os.getenv("API_URL") + '/user/login', json={"email": username, "password": password}).json()['data']['token'] + ":1709356058" | ||||
|  | ||||
| for i in range(1, 1000): | ||||
| for i in range(1, 10): | ||||
|     payload = { | ||||
|         "count": random.randint(1, 100), | ||||
|         "price": random.random() * 100, | ||||
|   | ||||
		Reference in New Issue
	
	Block a user