Skip to content

Commit 468798e

Browse files
committed
Refactoring, adding delete endpoints, using email validation pypi
1 parent de62e5b commit 468798e

File tree

4 files changed

+304
-206
lines changed

4 files changed

+304
-206
lines changed

lib/pbench/cli/server/shell.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
#!/bin/env python3
22

3-
from pbench.server.api import create_app
3+
from pbench.server.api import App
44
import subprocess
55

66

77
def main():
8-
app = create_app()
8+
flask_app = App()
9+
app = flask_app.create_app()
910
port = app.config["PORT"]
1011
host = app.config["BIND_HOST"]
1112
workers = app.config["WORKERS"]
@@ -18,6 +19,6 @@ def main():
1819
"/run/pbench-server/gunicorn.pid",
1920
"--bind",
2021
f"{str(host)}:{str(port)}",
21-
"pbench.server.api:create_app()",
22+
"pbench.server.api:App.create_app()",
2223
]
2324
)

lib/pbench/server/api/__init__.py

Lines changed: 123 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import tempfile
1414
from flask_bcrypt import Bcrypt
1515
from flask_sqlalchemy import SQLAlchemy
16+
from flask_migrate import Migrate
1617

1718
from flask import request, jsonify, Flask, make_response
1819
from flask_restful import Resource, abort, Api
@@ -22,113 +23,138 @@
2223
from pbench.server import PbenchServerConfig
2324
from pbench.common.exceptions import BadConfig
2425
from pbench.server.utils import filesize_bytes
25-
from pbench.server.api.resources.pbench_users import RegisterUser, LoginAPI, LogoutAPI, GetUser
26+
from pbench.server.api.resources.pbench_users import (
27+
RegisterUser,
28+
LoginAPI,
29+
LogoutAPI,
30+
GetUser,
31+
DeleteUser,
32+
)
2633

2734
ALLOWED_EXTENSIONS = {"xz"}
2835

29-
app = None
30-
bcrypt = None
31-
db = None
3236

33-
""""
34-
A storage engine to save revoked tokens. if speed is the primary concern, redis is a good option. but if data
35-
persistence is more important, postgres can be another good option too. Here I am using an in memory store to get
36-
something working.
37-
"""
38-
blacklist = set()
39-
40-
41-
def allowed_file(filename):
42-
"""Check if the file has the correct extension."""
43-
try:
44-
fn = filename.rsplit(".", 1)[1].lower()
45-
except IndexError:
46-
return False
47-
allowed = "." in filename and fn in ALLOWED_EXTENSIONS
48-
return allowed
49-
50-
51-
def register_endpoints(api, app):
52-
"""Register flask endpoints with the corresponding resource classes
53-
to make the APIs active."""
54-
55-
api.add_resource(
56-
Upload, f"{app.config['REST_URI']}/upload/ctrl/<string:controller>"
57-
)
58-
api.add_resource(HostInfo, f"{app.config['REST_URI']}/host_info")
59-
api.add_resource(Elasticsearch, f"{app.config['REST_URI']}/elasticsearch")
60-
api.add_resource(GraphQL, f"{app.config['REST_URI']}/graphql")
61-
api.add_resource(RegisterUser, f"{app.config['REST_URI']}/register")
62-
api.add_resource(LoginAPI, f"{app.config['REST_URI']}/login")
63-
api.add_resource(LogoutAPI, f"{app.config['REST_URI']}/logout")
64-
api.add_resource(GetUser, f"{app.config['REST_URI']}/user")
65-
66-
67-
def create_app():
68-
"""Create Flask app with defined resource endpoints."""
69-
70-
global app, db, bcrypt, blacklist
71-
72-
cfg_name = os.environ.get("_PBENCH_SERVER_CONFIG")
73-
if not cfg_name:
74-
print(
75-
f"{__name__}: ERROR: No config file specified; set"
76-
" _PBENCH_SERVER_CONFIG",
77-
file=sys.stderr,
37+
class App:
38+
app = None
39+
bcrypt = None
40+
db = None
41+
blacklist = None
42+
43+
@staticmethod
44+
def allowed_file(filename):
45+
"""Check if the file has the correct extension."""
46+
try:
47+
fn = filename.rsplit(".", 1)[1].lower()
48+
except IndexError:
49+
return False
50+
allowed = "." in filename and fn in ALLOWED_EXTENSIONS
51+
return allowed
52+
53+
@staticmethod
54+
def register_endpoints(api, app, db, bcrypt, blacklist):
55+
"""Register flask endpoints with the corresponding resource classes
56+
to make the APIs active."""
57+
58+
api.add_resource(
59+
Upload, f"{app.config['REST_URI']}/upload/ctrl/<string:controller>"
60+
)
61+
api.add_resource(HostInfo, f"{app.config['REST_URI']}/host_info")
62+
api.add_resource(Elasticsearch, f"{app.config['REST_URI']}/elasticsearch")
63+
api.add_resource(GraphQL, f"{app.config['REST_URI']}/graphql")
64+
api.add_resource(
65+
RegisterUser, f"{app.config['REST_URI']}/user", resource_class_args=(app, db)
66+
)
67+
api.add_resource(
68+
LoginAPI, f"{app.config['REST_URI']}/session", resource_class_args=(app, bcrypt)
7869
)
79-
sys.exit(1)
80-
81-
try:
82-
config = PbenchServerConfig(cfg_name)
83-
except BadConfig as e:
84-
print(f"{__name__}: {e} (config file {cfg_name})", file=sys.stderr)
85-
sys.exit(1)
86-
87-
app = Flask(__name__)
88-
api = Api(app)
89-
90-
app.logger = get_pbench_logger(__name__, config)
91-
app.config_server = config.conf["pbench-server"]
92-
app.config_elasticsearch = config.conf["elasticsearch"]
93-
app.config_graphql = config.conf["graphql"]
94-
95-
prdp = app.config_server.get("pbench-receive-dir-prefix")
96-
if not prdp:
97-
app.logger.error("Missing config variable for pbench-receive-dir-prefix")
98-
sys.exit(1)
99-
try:
100-
upload_directory = Path(f"{prdp}-002").resolve(strict=True)
101-
except FileNotFoundError:
102-
app.logger.exception("pbench-receive-dir-prefix does not exist on the host")
103-
sys.exit(1)
104-
except Exception:
105-
app.logger.exception(
106-
"Exception occurred during setting up the upload directory on the host"
70+
api.add_resource(
71+
LogoutAPI,
72+
f"{app.config['REST_URI']}/session",
73+
resource_class_args=(app, blacklist),
74+
)
75+
api.add_resource(
76+
GetUser,
77+
f"{app.config['REST_URI']}/user/<string:username>",
78+
resource_class_args=(app),
79+
)
80+
api.add_resource(
81+
DeleteUser,
82+
f"{app.config['REST_URI']}/user/<string:username>",
83+
resource_class_args=(app, db),
84+
)
85+
86+
def create_app(self):
87+
"""Create Flask app with defined resource endpoints."""
88+
89+
cfg_name = os.environ.get("_PBENCH_SERVER_CONFIG")
90+
if not cfg_name:
91+
print(
92+
f"{__name__}: ERROR: No config file specified; set"
93+
" _PBENCH_SERVER_CONFIG",
94+
file=sys.stderr,
95+
)
96+
sys.exit(1)
97+
98+
try:
99+
config = PbenchServerConfig(cfg_name)
100+
except BadConfig as e:
101+
print(f"{__name__}: {e} (config file {cfg_name})", file=sys.stderr)
102+
sys.exit(1)
103+
104+
app = Flask(__name__)
105+
api = Api(app)
106+
107+
app.logger = get_pbench_logger(__name__, config)
108+
app.config_server = config.conf["pbench-server"]
109+
app.config_elasticsearch = config.conf["elasticsearch"]
110+
app.config_graphql = config.conf["graphql"]
111+
112+
prdp = app.config_server.get("pbench-receive-dir-prefix")
113+
if not prdp:
114+
app.logger.error("Missing config variable for pbench-receive-dir-prefix")
115+
sys.exit(1)
116+
try:
117+
upload_directory = Path(f"{prdp}-002").resolve(strict=True)
118+
except FileNotFoundError:
119+
app.logger.exception("pbench-receive-dir-prefix does not exist on the host")
120+
sys.exit(1)
121+
except Exception:
122+
app.logger.exception(
123+
"Exception occurred during setting up the upload directory on the host"
124+
)
125+
sys.exit(1)
126+
else:
127+
app.upload_directory = upload_directory
128+
129+
app.config["PORT"] = app.config_server.get("rest_port")
130+
app.config["VERSION"] = app.config_server.get("rest_version")
131+
app.config["MAX_CONTENT_LENGTH"] = filesize_bytes(
132+
app.config_server.get("rest_max_content_length")
107133
)
108-
sys.exit(1)
109-
else:
110-
app.upload_directory = upload_directory
134+
app.config["REST_URI"] = app.config_server.get("rest_uri")
135+
app.config["LOG"] = app.config_server.get("rest_log")
136+
app.config["BIND_HOST"] = app.config_server.get("bind_host")
137+
app.config["WORKERS"] = app.config_server.get("workers")
111138

112-
app.config["PORT"] = app.config_server.get("rest_port")
113-
app.config["VERSION"] = app.config_server.get("rest_version")
114-
app.config["MAX_CONTENT_LENGTH"] = filesize_bytes(
115-
app.config_server.get("rest_max_content_length")
116-
)
117-
app.config["REST_URI"] = app.config_server.get("rest_uri")
118-
app.config["BIND_HOST"] = app.config_server.get("bind_host")
119-
app.config["WORKERS"] = app.config_server.get("workers")
139+
# postgresql specific configuration
140+
app.config["DB_SECRET_KEY"] = os.getenv("SECRET_KEY", "my_precious")
141+
app.config["BCRYPT_LOG_ROUNDS"] = app.config_server.get("bycrypt_log_rounds")
142+
app.config["POSTGRES_DB_URI"] = app.config_server.get("postgres_db_uri")
120143

121-
# postgresql specific configuration
122-
app.config["DB_SECRET_KEY"] = os.getenv("SECRET_KEY", "my_precious")
123-
app.config["BCRYPT_LOG_ROUNDS"] = app.config_server.get("bycrypt_log_rounds")
124-
app.config["POSTGRES_DB_URI"] = app.config_server.get("postgres_db_uri")
144+
bcrypt = Bcrypt(app)
145+
db = SQLAlchemy(app)
146+
migrate = Migrate(db)
125147

126-
bcrypt = Bcrypt(app)
127-
db = SQLAlchemy(app)
148+
""""
149+
A storage engine to save revoked tokens. if speed is the primary concern, redis is a good option. but if data
150+
persistence is more important, postgres can be another good option too. Here I am using an in memory store to get
151+
something working.
152+
"""
153+
blacklist = set()
128154

129-
register_endpoints(api, app)
155+
self.register_endpoints(api, app, db, bcrypt, blacklist)
130156

131-
return app
157+
return app
132158

133159

134160
def _build_cors_preflight_response():
@@ -327,7 +353,7 @@ def put(self, controller):
327353
md5sum = request.headers.get("Content-MD5")
328354

329355
app.logger.debug("Receiving file: {}", filename)
330-
if not allowed_file(filename):
356+
if not App.allowed_file(filename):
331357
app.logger.debug(
332358
f"Tarfile upload: Bad file extension received for file {filename}"
333359
)
Lines changed: 36 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,52 @@
11
import jwt
22
import datetime
33
from dateutil import parser
4-
from pbench.server.api import app, bcrypt, db, blacklist
4+
from pbench.server.api import App
55

66

7-
class UserModel(db.Model):
7+
class UserModel(App.db.Model):
88
""" User Model for storing user related details """
9+
910
__tablename__ = "users"
1011

11-
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
12-
username = db.Column(db.String(255), unique=True, nullable=False)
13-
firstName = db.Column(db.String(255), unique=False, nullable=False)
14-
lastName = db.Column(db.String(255), unique=False, nullable=False)
15-
password = db.Column(db.String(255), nullable=False)
16-
registered_on = db.Column(db.DateTime, nullable=False)
17-
email = db.Column(db.String(255), unique=False, nullable=False)
12+
id = App.db.Column(App.db.Integer, primary_key=True, autoincrement=True)
13+
username = App.db.Column(App.db.String(255), unique=True, nullable=False)
14+
firstName = App.db.Column(App.db.String(255), unique=False, nullable=False)
15+
lastName = App.db.Column(App.db.String(255), unique=False, nullable=False)
16+
password = App.db.Column(App.db.String(255), nullable=False)
17+
registered_on = App.db.Column(App.db.DateTime, nullable=False)
18+
email = App.db.Column(App.db.String(255), unique=False, nullable=False)
1819

1920
def __init__(self, username, firstName, lastName, email, password):
2021
self.username = username
2122
self.firstName = firstName
2223
self.lastName = lastName
23-
self.password = bcrypt.generate_password_hash(
24-
password, app.config.get('BCRYPT_LOG_ROUNDS')
24+
self.password = App.bcrypt.generate_password_hash(
25+
password, App.app.config.get("BCRYPT_LOG_ROUNDS")
2526
).decode()
2627
self.email = email
2728
self.registered_on = datetime.datetime.now()
2829

2930
def __str__(self):
30-
return f"New user, id: {self.id}, username: {self.username}"
31+
return f"User, id: {self.id}, username: {self.username}"
3132

32-
def encode_auth_token(self, user_id):
33+
def encode_auth_token(self, app, user_id):
3334
"""
3435
Generates the Auth Token
3536
:return: string
3637
"""
3738
try:
3839
payload = {
39-
'iat': datetime.datetime.utcnow(),
40-
'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=5),
41-
'sub': user_id
40+
"iat": datetime.datetime.utcnow(),
41+
"exp": datetime.datetime.utcnow() + datetime.timedelta(minutes=5),
42+
"sub": user_id,
4243
}
43-
return jwt.encode(
44-
payload,
45-
app.config.get('SECRET_KEY'),
46-
algorithm='HS256'
47-
)
44+
return jwt.encode(payload, app.config.get("SECRET_KEY"), algorithm="HS256")
4845
except Exception as e:
49-
return e
46+
app.logger.info(
47+
f"Some exception occurred while encoding the auth token for user_id {user_id}"
48+
)
49+
return None
5050

5151
@staticmethod
5252
def decode_auth_token(auth_token):
@@ -56,27 +56,28 @@ def decode_auth_token(auth_token):
5656
:return: integer|string
5757
"""
5858
try:
59-
payload = jwt.decode(auth_token, app.config.get('SECRET_KEY'))
60-
if auth_token in blacklist:
61-
return 'Token blacklisted. Please log in again.'
62-
return payload['sub']
59+
payload = jwt.decode(auth_token, App.app.config.get("SECRET_KEY"))
60+
if auth_token in App.blacklist:
61+
return "Token blacklisted. Please log in again."
62+
return payload["sub"]
6363
except jwt.ExpiredSignatureError:
64-
return 'Signature expired. Please log in again.'
64+
return "Signature expired. Please log in again."
6565
except jwt.InvalidTokenError:
66-
return 'Invalid token. Please log in again.'
66+
return "Invalid token. Please log in again."
6767

6868
# TODO: Add password recovery mechanism
6969

7070

71-
class URLModel(db.Model):
71+
class URLModel(App.db.Model):
7272
""" User Model for storing user related details """
73+
7374
__tablename__ = "urls"
7475

75-
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
76-
created = db.Column(db.DateTime, nullable=False)
77-
updated = db.Column(db.DateTime, nullable=False)
78-
config = db.Column(db.String(255), unique=False, nullable=False)
79-
description = db.Column(db.String(255), nullable=False)
76+
id = App.db.Column(App.db.Integer, primary_key=True, autoincrement=True)
77+
created = App.db.Column(App.db.DateTime, nullable=False)
78+
updated = App.db.Column(App.db.DateTime, nullable=False)
79+
config = App.db.Column(App.db.String(255), unique=False, nullable=False)
80+
description = App.db.Column(App.db.String(255), nullable=False)
8081

8182
def __init__(self, created, config, description):
8283
self.created = parser.parse(created)
@@ -85,4 +86,4 @@ def __init__(self, created, config, description):
8586
self.description = description
8687

8788
def __str__(self):
88-
return f"New user, id: {self.id}, username: {self.username}"
89+
return f"New user, id: {self.id}, username: {self.username}"

0 commit comments

Comments
 (0)