Skip to content

Commit a9be97c

Browse files
committed
Add user logout and get User API
1 parent bfeccfd commit a9be97c

File tree

3 files changed

+107
-3
lines changed

3 files changed

+107
-3
lines changed

lib/pbench/server/api/__init__.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,22 @@
2222
from pbench.server import PbenchServerConfig
2323
from pbench.common.exceptions import BadConfig
2424
from pbench.server.utils import filesize_bytes
25+
from pbench.server.api.resources.pbench_users import RegisterUser, LoginAPI, LogoutAPI, GetUser
2526

2627
ALLOWED_EXTENSIONS = {"xz"}
2728

2829
app = None
2930
bcrypt = None
3031
db = None
3132

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+
3241
def allowed_file(filename):
3342
"""Check if the file has the correct extension."""
3443
try:
@@ -49,12 +58,16 @@ def register_endpoints(api, app):
4958
api.add_resource(HostInfo, f"{app.config['REST_URI']}/host_info")
5059
api.add_resource(Elasticsearch, f"{app.config['REST_URI']}/elasticsearch")
5160
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")
5265

5366

5467
def create_app():
5568
"""Create Flask app with defined resource endpoints."""
5669

57-
global app, db, bcrypt
70+
global app, db, bcrypt, blacklist
5871

5972
cfg_name = os.environ.get("_PBENCH_SERVER_CONFIG")
6073
if not cfg_name:

lib/pbench/server/api/resources/db_models.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import jwt
22
import datetime
33
from dateutil import parser
4-
from pbench.server.api import app, bcrypt, db
4+
from pbench.server.api import app, bcrypt, db, blacklist
55

66

77
class UserModel(db.Model):
@@ -56,6 +56,8 @@ def decode_auth_token(auth_token):
5656
"""
5757
try:
5858
payload = jwt.decode(auth_token, app.config.get('SECRET_KEY'))
59+
if auth_token in blacklist:
60+
return 'Token blacklisted. Please log in again.'
5961
return payload['sub']
6062
except jwt.ExpiredSignatureError:
6163
return 'Signature expired. Please log in again.'

lib/pbench/server/api/resources/pbench_users.py

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11

22
from flask import request, jsonify, make_response
33
from flask_restful import Resource, abort
4-
from pbench.server.api import app, bcrypt, db
4+
from pbench.server.api import app, bcrypt, db, blacklist
55
from pbench.server.api.resources.db_models import UserModel
6+
from flask_jwt_extended import jwt_required
67

78

89
class RegisterUser(Resource):
@@ -91,3 +92,91 @@ def post(self):
9192
'message': 'Try again'
9293
}
9394
return make_response(jsonify(response_object))
95+
96+
97+
class LogoutAPI(Resource):
98+
"""
99+
Abstracted pbench API for User Logout
100+
"""
101+
@jwt_required
102+
def post(self):
103+
# get auth token
104+
auth_header = request.headers.get('X-Auth-Token')
105+
if auth_header:
106+
auth_token = auth_header.split(" ")[1]
107+
108+
resp = UserModel.decode_auth_token(auth_token)
109+
if not isinstance(resp, str):
110+
# Add the token to the blacklist
111+
blacklist.add(auth_token)
112+
response_object = {
113+
'status': 'success',
114+
'message': 'Successfully logged out.',
115+
'status_code': 200
116+
}
117+
return make_response(jsonify(response_object))
118+
else:
119+
response_object = {
120+
'status': 'fail',
121+
'message': resp,
122+
'status_code': 401
123+
}
124+
return make_response(jsonify(response_object))
125+
else:
126+
response_object = {
127+
'status': 'fail',
128+
'message': 'Provide a valid auth token.',
129+
'status_code': 403
130+
}
131+
return make_response(jsonify(response_object))
132+
133+
134+
class GetUser(Resource):
135+
"""
136+
Abstracted pbench API to get user metadata information
137+
"""
138+
# TODO: We can implement the graphql query to get specific metadata related to a user
139+
# We dont need to pass user_id to get the user, user id will be retrieved from the jwt encoded auth header
140+
@jwt_required
141+
def get(self):
142+
# get the auth token
143+
auth_header = request.headers.get('X-Auth-Token')
144+
auth_token = ''
145+
if auth_header:
146+
try:
147+
auth_token = auth_header.split(" ")[1]
148+
except IndexError:
149+
response_object = {
150+
'status': 'fail',
151+
'message': 'Bearer token malformed.',
152+
'status_code': 401
153+
}
154+
return make_response(jsonify(response_object))
155+
156+
if auth_token:
157+
resp = UserModel.decode_auth_token(auth_token)
158+
if not isinstance(resp, str):
159+
user = UserModel.query.filter_by(id=resp).first()
160+
response_object = {
161+
'status': 'success',
162+
'data': {
163+
'user_id': user.id,
164+
'username': user.username,
165+
'registered_on': user.registered_on
166+
},
167+
'status_code': 200
168+
}
169+
return make_response(jsonify(response_object))
170+
response_object = {
171+
'status': 'fail',
172+
'message': resp,
173+
'status_code': 403
174+
}
175+
return make_response(jsonify(response_object))
176+
else:
177+
response_object = {
178+
'status': 'fail',
179+
'message': 'Provide a valid auth token.',
180+
'status_code': 401
181+
}
182+
return make_response(jsonify(response_object))

0 commit comments

Comments
 (0)