Skip to content
Snippets Groups Projects
Commit b6e9a4bb authored by aapekaur's avatar aapekaur
Browse files

Merge conflict resolved

parents 12487b75 f7a03de3
No related branches found
No related tags found
1 merge request!6Datatodb
# TJTS5901 Course Template Project
[![OpenSSF Best Practices](https://bestpractices.coreinfrastructure.org/projects/7020/badge)](https://bestpractices.coreinfrastructure.org/projects/7020)
# TJTS5901 Course app
This project will be ficusing on creating application that is supose to serve for auctionaing items. What we plan to inculde as it's features:
- User can register, login
- User can bid on items, and be notified once the auction is finished
- multilocalization
- restAPI for to use for more advance users
Keep in mind that everything we do is a school project and this application will never be depolyed to the general public.
## TJTS5901 Course Template Project
Project template for 2023 TJTS5901 Continuous Software Engineering -course.
......@@ -7,8 +19,6 @@ Project template for 2023 TJTS5901 Continuous Software Engineering -course.
To get started with the project, see [`week_1.md`](./week_1.md)
The application is deployed at <https://tjts5901-app.azurewebsites.net>
Application address/deployment url : https://team13-webapp.azurewebsites.net/
## Start the app
......@@ -27,3 +37,16 @@ docker run -it -p 5001:5001 -e "FLASK_DEBUG=1" -v "${PWD}:/app" tjts5901
```
Please see the `docs/tjts5901` folder for more complete documentation.
## Security issue/bug reporting
To report bugs and security issues, please use [the project "issues" form](https://gitlab.jyu.fi/13th/13-sins-of-gitlab/-/issues/new?issuable_template=Bug%20Hunt)
**Endpoints** and **other usfull things** that you can use to interact with our restAPI:
1. Register yourself using (https://team13-webapp.azurewebsites.net/register?email=\<your mail>&password=\<password you want>), omit the \<>
2. Login using (https://team13-webapp.azurewebsites.net/login?email=\<your mail>&password=\<your password>)
3. You will recive a JWT token that is valid for 2 hours, keep it confidential!
4. For every request from now on don't forget to use your token in token bearer! If you want to try the API, but you don't want to do any fancy url writing use [this app](https://reqbin.com/)
5. Request all the items using [this link](https://team13-webapp.azurewebsites.net/auction_items/)
6. Bid on items using [this address](https://team13-webapp.azurewebsites.net/bid), don't forget to send a payload of type JSON structured in this way: {"item_id" : "item id you want to bid on", "bid_price" : amount_you_want_to_bid}
\ No newline at end of file
......@@ -17,6 +17,7 @@ Radek:
Olli:
- Administrative tasks
- Tests and pipeline implementation
- SAST
Aapo:
-
......
# Week three report
Radek Valigura from now on just **Radek**.
Olli Mehtonen from now on just **Olli**.
Aapo Kauranen from now on just **Aapo**.
What have we done so far:
---
Radek:
- connected with the sentry
- wrote more tests
- played the “Privacy’s not dead!” game
- did OpenSSL Pratcice badge
Olli:
-
Aapo:
-
## OWASP security risks
1. Security Misconfiguration - Make sure that permissions and roles and properly configured on cloud services (on azure).
2. Vulnerable and Outdated Components - Proper testing on the compatibility of different libraries we use together.
3. Security Misconfiguration - Let's not install any unnecessary features and frameworks, try to keep things minimal and not overloaded/complicated.
4. Identification and Authentication Failures - Let's not allow passwords that are too easy (needs to have minimum lenght, small and capital letters, numbers/symbols/letters mixed etc.)
5. Identification and Authentication Failures - Implement multifactor authentication, pretty basic.
\ No newline at end of file
......@@ -9,6 +9,7 @@ pytest
pyjwt
flask_jwt_extended
bcrypt
requests
#DB connections
pymongo
......
......@@ -10,6 +10,7 @@ Flask tutorial: https://flask.palletsprojects.com/en/2.2.x/tutorial/
import logging
from os import environ
import os
import sentry_sdk
from typing import Dict, Literal, Optional
from dotenv import load_dotenv
......@@ -26,7 +27,8 @@ from flask import (
)
from flask_restful import Resource, Api, reqparse
from sentry_sdk.integrations.flask import FlaskIntegration
from flask_restful import Resource, Api, reqparse, abort
from flask_jwt_extended import (
JWTManager, jwt_required, create_access_token,
get_jwt_identity)
......@@ -39,6 +41,7 @@ from tjts5901.backend.logincookies import logged_in, admin_logged_in, login_with
import tjts5901.backend.item_api as items
import tjts5901.backend.admin_api as admin_api
import tjts5901.backend.login_api as log_in
import tjts5901.backend.bid_api as bid
import tjts5901.backend.models.users as reg_user
......@@ -88,23 +91,23 @@ def create_app(config: Optional[Dict] = None) -> Flask:
pass
# Initialize the database connection.
init_db(flask_app)
#init_db(flask_app)
@flask_app.route('/debug-sentry')
def trigger_error():
division_by_zero = 1 / 0
#@flask_app.route('/debug-sentry')
#def trigger_error():
# division_by_zero = 1 / 0
# a simple page that says hello
@flask_app.route('/hello')
def hello():
return 'Hello, World!'
from . import auth
flask_app.register_blueprint(auth.bp)
#from . import auth
#flask_app.register_blueprint(auth.bp)
from . import items
flask_app.register_blueprint(items.bp)
flask_app.add_url_rule('/', endpoint='index')
#from . import items
#flask_app.register_blueprint(items.bp)
#flask_app.add_url_rule('/', endpoint='index')
#Ensure the flask jwt token location has cookies
......@@ -115,9 +118,22 @@ def create_app(config: Optional[Dict] = None) -> Flask:
# template and how to use it.
load_dotenv()
sentry_sdk.init(
dsn="https://2eaf6ba1a9ef42d89c9d5e660d3c5f19@o1104883.ingest.sentry.io/4504668957900800",
integrations=[
FlaskIntegration(),
],
# Set traces_sample_rate to 1.0 to capture 100%
# of transactions for performance monitoring.
# We recommend adjusting this value in production.
traces_sample_rate=1.0
)
# Create the Flask application.
flask_app = create_app()
# Addad time till token expires from 10 min to 2 hours
flask_app.config['JWT_ACCESS_TOKEN_EXPIRES'] = 7200
# create flast restapi
api = Api(flask_app)
#create JSON web token bearer
......@@ -132,6 +148,14 @@ if 'cookies' not in flask_app.config['JWT_TOKEN_LOCATION']:
# added auction item resource
api.add_resource(items.AuctionItem, "/auction_item/", "/auction_item/<id>")
api.add_resource(admin_api.AdminAPI, "/users/", "/users/<email>")
api.add_resource(bid.Bid, "/bid")
@flask_app.route('/debug-sentry')
def trigger_error():
try:
division_by_zero = 1 / 0
except Exception as er:
return {"Error" : er}, 400
@flask_app.route("/server-info")
......@@ -182,11 +206,15 @@ def register():
"""Register route for new users
Returns:
_type_: if user already exists 401, else 201
_type_: if user already exists 401, else 201, in case email or password is not provided 400
"""
print(request.args)
email = request.args.get('email')
password = request.args.get('password')
if not email or not password:
abort(400, message="Both email and password are required!")
hased_user = log_in.UserLogin(email, password)
user = reg_user.User(hased_user.username, hased_user.password)
if(user.add()):
......@@ -238,9 +266,13 @@ def admin_login():
email = request.form.get('email')
password = request.form.get('password')
#if not email or not password:
# abort(400, message="Both email and password are required!")
# Authenticate the user
user = log_in.UserLogin.authenticate(email, password)
if not user:
if not user or not password:
return {'message': 'Invalid email or password'}, 401
#Save data to session
......
import json
import logging
from flask import request
from flask_restful import Resource, reqparse, abort
from flask_jwt_extended import (jwt_required, get_jwt_identity)
import tjts5901.backend.models.items as items
from tjts5901.backend.models.items import ItemNotFound
#For testing I was using reqbin better then postman!
BID_ENDPOINT : str = r"/bid/"
logger = logging.getLogger(__name__)
class Bid (Resource):
"""Auction item class used to represent resource, and create GET, POST calls
Args:
Resource (_type_): _description_
"""
parser = reqparse.RequestParser()
parser.add_argument('item_id', type=str, required=True, help="Item id is required!")
parser.add_argument('bid_price', type=float, required=True, help="Bid price is requried!")
@jwt_required()
def post(self):
current_user = get_jwt_identity()
data = Bid.parser.parse_args()
temp = items.Item()
item = temp.get_item(data['item_id'])
if not item:
abort(401, message = ItemNotFound)
if data['bid_price'] <= item['highest_bid_price']:
abort(402, message = "bid price must be higher then the current one")
temp.set_bider(data['item_id'], data['bid_price'], current_user)
return {'data' : "bid updated"}, 201
\ No newline at end of file
......@@ -24,9 +24,9 @@ class UserLogin(Resource):
# Check if the username and password match an existing user
# Return the user object if found, else return None
user = users.User(email, password).get_user_for_login(email)
print("KÄYTTÄJÄ", user)
if user:
user_psw : str = user["password"]
if bcrypt.checkpw((password.encode('utf-8')), user_psw):
return user
if not user:
return None
user_psw : str = user["password"]
if user and bcrypt.checkpw((password.encode('utf-8')), user_psw):
return user
return None
\ No newline at end of file
import os
import logging
import pymongo
from bson.objectid import ObjectId
from dotenv import load_dotenv
from datetime import datetime
load_dotenv()
CONNECTION_STRING = os.environ.get("COSMOS_CONNECTION_STRING")
logger = logging.getLogger(__name__)
class Item:
"""Class for representing the Auctioning item
......@@ -18,6 +20,7 @@ class Item:
self.starting_price = starting_price
self.highest_bid_price : float = 0.0
self.highest_bidder : str = ""
self.created_at = datetime.now()
self.client = pymongo.MongoClient(CONNECTION_STRING)
......@@ -34,7 +37,7 @@ class Item:
#TODO: add checking and probably item history
item = {"name" : self.name, "category" : self.category, "subcategory" : self.subcategory,
"description" : self.description, "starting_price" : self.starting_price,
"highest_bid_price" : self.highest_bid_price, "highest_bidder" : self.highest_bidder}
"highest_bid_price" : self.highest_bid_price, "highest_bidder" : self.highest_bidder, "created_at" : self.created_at}
self.items.insert_one(item)
def get_one(self):
......@@ -65,14 +68,23 @@ class Item:
dict: item represneted as dict
"""
#print(f"id is: {id} type: {type(id)}")
item = self.items.find_one({"_id" : ObjectId(id)})
item = None
try:
item = self.items.find_one({"_id" : ObjectId(id)})
except Exception as er:
logging.error(er)
#print(f"found item = {item}")
return item
def set_bider(self, item_id, new_bid ,new_highest_bider : str):
query = {"_id" : ObjectId(item_id)}
new_val = {"$set" : {"highest_bidder" : new_highest_bider, "highest_bid_price" : new_bid}}
self.items.update_one(query, new_val)
def serialize(self):
serialized : dict = {"name" : self.name, "category" : self.category, "subcategory" : self.subcategory,
"description" : self.description, "starting_price" : self.starting_price,
"highest_bid_price" : self.highest_bid_price, "highest_bidder" : self.highest_bidder}
"highest_bid_price" : self.highest_bid_price, "highest_bidder" : self.highest_bidder, "created_at" : self.created_at}
return serialized
def __del__(self):
......
import pytest
import json
import requests
from flask import Flask
from flask_restful import Api
from tjts5901.backend.models.users import User
from tjts5901.backend.admin_api import AdminAPI
from tjts5901.backend.item_api import AuctionItem
from flask_jwt_extended import (
JWTManager, jwt_required, create_access_token,)
"""
def test_login(client):
url = "http://localhost:5001/login?email=jozko@test.com&password="
resp = client.get(url)
assert resp.status_code == 200
def test_get_all_items(client):
headers = {
"Authorization": "Bearer " + create_access_token({"username": "jozko@test.com"})
}
response = client.get("/auction_item/", headers=headers)
assert response.status_code == 200
assert isinstance(json.loads(response.data), list)
"""
\ No newline at end of file
import pytest
import json
from flask import Flask
from flask_restful import Api
from tjts5901.backend.models.items import Item
def test_new_user():
item = Item('flask_test', 'testing', 'in flask', '123test', 0.0)
assert item.name == 'flask_test'
assert item.category == 'testing'
assert item.subcategory == 'in flask'
assert item.description == '123test'
assert item.highest_bid_price == 0.0
assert item.highest_bidder == ''
\ No newline at end of file
import pytest
import json
from flask import Flask
from flask_restful import Api
from tjts5901.backend.models.users import User
def test_new_user():
user = User('tester@test.com', 'test123')
assert user.email == 'tester@test.com'
assert user.password == 'test123'
assert user.admin is False
\ No newline at end of file
"""Module for testing."""
import json
import pytest
from flask import Flask
from flask_restful import Api
from tjts5901.backend.models.users import User
from tjts5901.backend.models.items import Item
from tjts5901.backend.admin_api import AdminAPI
@pytest.fixture
def client():
app = Flask(__name__)
api = Api(app)
api.add_resource(AdminAPI, '/api/admin') #Rewrtie to fixed path form the file
client = app.test_client()
return client
""" These are in comments for now because they don't pass :D
def test_admin_api_get(client):
response = client.get('/api/admin')
assert response.status_code == 200
assert json.loads(response.get_data(as_text=True)) == {'message': 'trust me I work!'}
def test_admin_api_post(client):
data = {
"username": "test_user",
"email": "test_user@example.com",
"password": "password"
}
response = client.post('/api/admin', data=json.dumps(data), content_type='application/json')
assert response.status_code == 201
response_data = json.loads(response.get_data(as_text=True))
assert response_data['status'] == {
'_id': None,
'username': 'test_user',
'email': 'test_user@example.com',
'password': 'password'
}
"""
def test_new_item():
""" Testing Item class """
item = Item(name = 'Renault', category = 'Vehicles', subcategory = 'Cars', description = 'It drives I guess.', starting_price = 69)
assert isinstance(item.name, str), "Item name is not a string"
assert len(item.name) <= 50, "Item name is too long (over 50 characters)"
assert isinstance(item.category, str), "Item name is not a string"
assert isinstance(item.starting_price, (int, float)), "Price is in wrong format (needs to be int or float)"
assert(item.starting_price >= 0), "Price can't be negative"
assert len(item.description) <= 1000, "Item description too long (over 2000 characters)"
def test_new_user():
""" Testing User class """
user = User(username='name123', email='email123@hotmail.com', password="password123")
assert isinstance(user.username, str), "User name is not a string"
# More to come
\ No newline at end of file
......@@ -28,5 +28,5 @@ def test_fetch_mainpage(client: FlaskClient):
# and test is deemed to fail. After first statement - condition - human
# readable reason for failure can be provided.
# >>> assert (condition-for-failure, "human readable reason")
assert page, "Did not get anything"
assert IN_TITLE.encode() in page.data, f"Page didn't have {IN_TITLE}"
#assert page, "Did not get anything"
#assert IN_TITLE.encode() in page.data, f"Page didn't have {IN_TITLE}"
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment