Commit eaf5c1f0 authored by Manuel's avatar Manuel

restGateway: added CRUD methods for user

added generation of JWT tokens
parent df1bc7c1
...@@ -13,6 +13,24 @@ basePath: "/api" ...@@ -13,6 +13,24 @@ basePath: "/api"
# Paths supported by the server application # Paths supported by the server application
paths: paths:
/tokens/{token}:
post:
operationId: "rest.user.verify"
tags:
- "User"
summary: "Verifies a user token"
description: "Verifies a user token"
parameters:
- name: "token"
in: "path"
description: "Target token that will be verified"
required: true
type: "string"
responses:
'200':
description: "Verification successful"
'401':
description: "Invalid token"
/tokens: /tokens:
post: post:
operationId: "rest.user.authenticate" operationId: "rest.user.authenticate"
...@@ -40,7 +58,7 @@ paths: ...@@ -40,7 +58,7 @@ paths:
- "User" - "User"
summary: "Deletes a user identified by the username from the database" summary: "Deletes a user identified by the username from the database"
description: "Deletes a user identified by the username from the database" description: "Deletes a user identified by the username from the database"
parameters: parameters:
- name: "username" - name: "username"
in: "path" in: "path"
description: "Username of the user to be deleted" description: "Username of the user to be deleted"
...@@ -61,7 +79,9 @@ paths: ...@@ -61,7 +79,9 @@ paths:
responses: responses:
'200': '200':
description: complete user object including numeric ID description: complete user object including numeric ID
schema: schema:
type: array
items:
$ref: "#/definitions/User" $ref: "#/definitions/User"
'400': '400':
description: wrong username or password description: wrong username or password
......
...@@ -31,61 +31,4 @@ class Repository(MongoRepositoryBase): ...@@ -31,61 +31,4 @@ class Repository(MongoRepositoryBase):
def all(self) -> List[User]: def all(self) -> List[User]:
result = super().get_entries(self._user_collection, projection={'_id': False}) result = super().get_entries(self._user_collection, projection={'_id': False})
return list(result) return list(result)
\ No newline at end of file
# def get_layers(self) -> List[Layer]:
# entries = super().get_entries(self._layer_collection)
# return [Layer(e) for e in entries]
# def get_layer(self, layer_name) -> Layer:
# entries = super().get_entries(self._layer_collection, selection={'layer_name': layer_name})
# entries = [Layer(e) for e in entries]
# if entries is not None and len(entries) > 0:
# return entries[0]
# else:
# return None
# def add_layer_node(self, node: dict):
# super().insert_entry(self._layer_nodes_collection, node)
# def add_layer_nodes(self, nodes:List[dict]):
# super().insert_many(self._layer_nodes_collection, nodes)
# def get_layer_nodes(self, layer_name: str) -> dict:
# '''Returns all nodes for the layer.'''
# entries = super().get_entries(self._layer_nodes_collection, selection={'layer_name': layer_name}, projection={'_id': 0})
# return [e for e in entries]
# #endregion
# #region Clusters
# def add_clusters(self, clusters: List[Cluster]):
# cluster_dicts = [c.to_serializable_dict(for_db=True) for c in clusters]
# super().insert_many(self._clusters_collection, cluster_dicts)
# def get_clusters_for_layer(self, layer_name: str) -> List[Cluster]:
# entries = super().get_entries(self._clusters_collection, selection={'layer_name': layer_name}, projection={'_id': 0})
# return [Cluster(cluster_dict=e, from_db=True) for e in entries]
# #endregion
# #region TimeSlice
# def add_time_slice(self, timeslice: TimeSlice):
# super().insert_entry(self._time_slice_collection, timeslice.to_serializable_dict(for_db=True))
# def get_time_slices(self) -> List[TimeSlice]:
# '''Returns all time slices.'''
# entries = super().get_entries(self._time_slice_collection)
# return [TimeSlice(None, None, time_slice_dict=e, from_db=True) for e in entries]
# def get_time_slices_by_name(self, layer_name) -> List[TimeSlice]:
# '''Returns all time slices with the given layer_name.'''
# entries = super().get_entries(self._time_slice_collection, selection={'layer_name': layer_name})
# return [TimeSlice(None, None, time_slice_dict=e, from_db=True) for e in entries]
# def remove_all_time_slices(self):
# super().drop_collection(self._time_slice_collection)
# #endregion
\ No newline at end of file
# global imports (dont't worry, red is normal) # global imports (dont't worry, red is normal)
from db.repository import Repository
from db.entities.user import User from db.entities.user import User
from services.user_service import UserService
from flask import request, Response from flask import request, Response
import bcrypt import bcrypt
import jwt
from datetime import datetime from datetime import datetime, timedelta
repository = Repository() SIGNING_KEY = "yteNrMy6142WKwp8fKfrHkS5nlFpxtHgOXJh1ZPsOrV_gTcsO9eMY7aB7HUzRbTRO9dmZhCl3FdPtuvMe3K8aBA_wc2MmHRo8IkUIGmvUJGsAxKFClN_6oNW5fEvoeVKiL1krA-qjWbR_em-WksePgPoTsySW7QbKdi4f7cwuyK2_JZ2fQj9hDKlfJ2GzMXkKiWcfyCTr30yC6BviAFeRDD_Bpvg6znsrXr53Tq66hnwDwQ6QU7aHVu-bERblKZTYuvkSxsov6yRMEVWQoiuBITsQtIOcgSWK4Dy3BjSbqoIcKw3WG-s3wx1lTen19QbEu8vJC64e0iGeGDWT6vbtg"
TOKEN_VALIDITY_IN_DAYS = 1
def authenticate(): def generate_token(user: User) -> str:
data = request.json '''
username = data["username"] creates a JWT token for a user which has the following fields:
- username
- created_at
- valid_until
'''
created_at = datetime.now()
valid_until = created_at + timedelta(days=1)
return jwt.encode(
{
'username': user.username,
'created_at': str(created_at),
'valid_until': str(valid_until),
},
SIGNING_KEY, algorithm='HS256'
).decode("utf-8")
def verify(token):
'''
verifies the validity of a JWT token.
performs the following tests (int this order):
- is the JWT token parsable? (it has not been damaged + the signature is valid)
- does the payload contain all necessary fields?
- does the user specified by the payload exist?
- is the expiration/creation date sound?
'''
user = repository.one_by_username(username) try:
payload = jwt.decode(token, SIGNING_KEY, algorithms=['HS256'])
except:
return Response(status = 401, response=f'Invalid JWT token')
# return 400 if the user does not exist # check if all needed fields are in the payload
if len(user) == 0: if not "username" in payload or not "created_at" in payload or not "valid_until" in payload:
return Response(status = 400, response=f'User with username "{username}" does not exist') return Response(status = 401, response=f'Invalid JWT token')
user = User.from_serializable_dict(user[0]) try:
UserService.get_by_username(payload["username"])
except ValueError as e:
# return 400 if the user does not exist
return Response(status = 400, response=str(e))
# check if token has already expired
token_created_at = datetime.strptime(payload["created_at"], '%Y-%m-%d %H:%M:%S.%f')
valid_until = datetime.strptime(payload["valid_until"], '%Y-%m-%d %H:%M:%S.%f')
now = datetime.now()
if now <= token_created_at or now >= valid_until:
return Response(status = 401, response=f'Token expired')
hashed_password = user.password # everthing is fine
actual_password = data["password"] return Response(status = 200)
if bcrypt.checkpw(actual_password.encode("utf-8"), hashed_password.encode("utf-8")): def authenticate():
# TODO token generation '''
return "Nice" takes the credentials from the user and generates a JWT token out of them
'''
return Response(status = 400, response=f'Wrong credentials for user "{username}"') data = request.json
username = data["username"]
try:
user = UserService.get_by_credentials(username, data["password"])
return {"token": generate_token(user)}
except ValueError as e:
# return 400 if the user does not exist or the password is wrong
return Response(status = 400, response=str(e))
def delete(username): def delete(username):
reference_users = repository.one_by_username(username) '''
deletes a user from the DB. should be protected later
'''
try:
UserService.delete(username)
# return 400 if the user does not exist return Response(status = 200)
if len(reference_users) == 0: except ValueError as e:
return Response(status = 400, response=f'User with username "{username}" does not exist') # return 400 if the user already exists
return Response(status = 400, response=str(e))
repository.delete_all_with_username(username)
return Response(status=200)
def add(): def add():
'''
adds a new user to the DB. expects the provided password to be plaintext i.e. it should not
be encrypted, encoded or hashed
'''
data = request.json data = request.json
if not "role" in data: if not "role" in data:
...@@ -48,22 +104,11 @@ def add(): ...@@ -48,22 +104,11 @@ def add():
username = data["username"] username = data["username"]
reference_users = repository.one_by_username(username) try :
UserService.add(username, data["password"], data["role"])
# return 400 if the user already exists except ValueError as e:
if len(reference_users) > 0: # return 400 if the user already exists
return Response(status = 400, response=f'User with username "{username}" already exists') return Response(status = 400, response=str(e))
data["created_at"] = str(datetime.now())
data["last_login"] = "0"
print(data["password"])
data["password"] = bcrypt.hashpw(data["password"].encode("utf-8"), bcrypt.gensalt()).decode("utf-8")
user = User.from_serializable_dict(data)
repository.add(user)
return Response(status=200) return Response(status=200)
...@@ -72,6 +117,6 @@ def all(): ...@@ -72,6 +117,6 @@ def all():
return all users stored in the DB return all users stored in the DB
''' '''
users = repository.all() users = UserService._repository.all()
return str(users) return str(users)
\ No newline at end of file
# global imports (dont't worry, red is normal)
from db.repository import Repository
from db.entities.user import User
from datetime import datetime
import bcrypt
class UserService:
_repository = Repository()
@staticmethod
def get_by_username(username):
'''
fetches the given user from the database
throws a ValueError if the user does not exist
@params:
username - Required : string identifier for the user i.e. an email address
'''
user = UserService._repository.one_by_username(username)
# return 400 if the user does not exist
if len(user) == 0:
raise ValueError(f'User with username "{username}" does not exist')
return User.from_serializable_dict(user[0])
@staticmethod
def get_by_credentials(username, password):
'''
fetches the given user from the database and checks if the password matches the stored one
throws a ValueError if the user does not exist or the password is wrong
@params:
username - Required : string identifier for the user i.e. an email address
password - Required : passphrase used to authenticate later, raw plaintext
'''
user = UserService.get_by_username(username)
hashed_password = user.password
if not bcrypt.checkpw(password.encode("utf-8"), hashed_password.encode("utf-8")):
raise ValueError(f'Wrong credentials for user "{username}"')
return user
@staticmethod
def delete(username):
'''
deletes the given user from the database
throws a ValueError if the user does not exist
@params:
username - Required : string identifier for the user i.e. an email address
'''
reference_users = UserService._repository.one_by_username(username)
# return 400 if the user does not exist
if len(reference_users) == 0:
raise ValueError(f'User with username "{username}" does not exist')
UserService._repository.delete_all_with_username(username)
@staticmethod
def add(username, password, role="u"):
'''
adds the given user to the database
throws a ValueError if the user already exists
@params:
username - Required : string identifier for the user i.e. an email address
password - Required : passphrase used to authenticate later, raw plaintext
role - Optional : user type, one of the following: [u=regular user (default)]
'''
reference_users = UserService._repository.one_by_username(username)
if len(reference_users) > 0:
raise ValueError(f'User with username "{username}" already exists')
created_at = str(datetime.now())
last_login = str(datetime.min)
# hash the password using the BCrypt algorithm, which generates a string that
# contains the algorithm, the salt and the hash
password = bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt()).decode("utf-8")
user_new = User(username, password, role=role)
user_new.created_at = created_at
user_new.last_login = last_login
UserService._repository.add(user_new)
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment