Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feat] Migrating users from config file to database #261

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions tardis/rest/app/crud.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
CREATE_USERS = """CREATE TABLE IF NOT EXISTS Users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_name TEXT NOT NULL UNIQUE,
hashed_password TEXT NOT NULL,
scopes TEXT NOT NULL
)"""
ADD_USER = "INSERT INTO Users (user_name, hashed_password, scopes) VALUES (?, ?, ?)"
GET_USER = "SELECT user_name, hashed_password, scopes FROM Users WHERE user_name = ?"
DELETE_USER = "DELETE FROM Users WHERE user_name = ?"
DUMP_USERS = "SELECT user_name, hashed_password, scopes FROM Users"


async def get_resource_state(sql_registry, drone_uuid: str):
sql_query = """
SELECT R.drone_uuid, RS.state
Expand Down
64 changes: 64 additions & 0 deletions tardis/rest/app/userdb.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import sqlite3
import json
from typing import Tuple

from .crud import ADD_USER, CREATE_USERS, DELETE_USER, DUMP_USERS, GET_USER
from .security import DatabaseUser


def to_db_user(user: tuple) -> DatabaseUser:
return DatabaseUser(
user_name=user[0], hashed_password=user[1], scopes=json.loads(user[2])
)


class UserDB:
def __init__(self, path: str):
self.path = path

def try_create_users(self):
self.execute(CREATE_USERS)

def drop_users(self):
self.execute("DROP TABLE Users")

def add_user(self, user: DatabaseUser):
try:
_, conn = self.execute(
ADD_USER,
(user.user_name, user.hashed_password, json.dumps(user.scopes)),
)
conn.commit()
except sqlite3.IntegrityError as e:
if str(e) == "UNIQUE constraint failed: Users.user_name":
raise Exception("USER EXISTS") from None
else:
raise e

def get_user(self, user_name: str) -> DatabaseUser:
cur, _ = self.execute(
GET_USER,
[user_name],
)
user = cur.fetchone()

if user is None:
raise Exception("USER NOT FOUND") from None

return to_db_user(user)

def dump_users(self):
cur, _ = self.execute(DUMP_USERS)
return cur.fetchall()

def delete_user(self, user_name: str):
_, conn = self.execute(DELETE_USER, [user_name])
conn.commit()

def execute(
self, sql: str, args: list = []
) -> Tuple[sqlite3.Cursor, sqlite3.Connection]:
conn = sqlite3.connect(self.path)
cur = conn.cursor()
cur.execute(sql, args)
return cur, conn
Empty file.
88 changes: 88 additions & 0 deletions tardis/rest/manage_users/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
from importlib.resources import path

Check notice

Code scanning / CodeQL

Unused import

Import of 'path' is not used.
from turtle import st
from typing import List
import typer

from .util import hash_password
from ..app.userdb import UserDB
from ..app.security import DatabaseUser

app = typer.Typer()

"""
Sample usage:
> create-table
> add-user alice password resources:get user:get
> get-user alice
> delete-user alice
"""


@app.command()
def add_user(name: str, password: str, scopes: List[str], path: str = "users.db"):
"""
Add a user to the database. Password gets hashed.
"""
print(f"Adding user {name} with scopes {scopes}")
db = UserDB(path)
dbuser = DatabaseUser(
user_name=name, hashed_password=hash_password(password), scopes=scopes
)
db.add_user(dbuser)


@app.command()
def create_db(path: str = "users.db"):
"""
Create a database file with a Users table.
Doesn't overwrite existing files or tables.
"""
print("Creating db with users table")
db = UserDB(path)
db.try_create_users()


@app.command()
def drop_users(path: str = "users.db"):
"""
Drop the Users table.
"""
print("Dropping users table")
db = UserDB(path)
db.drop_users()


@app.command()
def dump_users(path: str = "users.db"):
"""
Dump all users from the database.
"""
print("Dumping users table")
db = UserDB(path)
for user in db.dump_users():
print(user)


@app.command()
def get_user(user_name: str, path: str = "users.db"):
"""
Print a user from the database.
"""
print("Getting user")
db = UserDB(path)
user = db.get_user(user_name)
print(user)


@app.command()
def delete_user(user_name: str, path: str = "users.db"):
"""
Delete a user from the database.
"""
print("Deleting user")
db = UserDB(path)
db.delete_user(user_name)


if __name__ == "__main__":
app()
6 changes: 6 additions & 0 deletions tardis/rest/manage_users/util.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import bcrypt


def hash_password(password: str) -> bytes:
salt = bcrypt.gensalt()
return bcrypt.hashpw(password.encode(), salt)
24 changes: 16 additions & 8 deletions tardis/rest/service.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from .app.security import DatabaseUser
from .app.security import DatabaseUser, LoginUser

Check notice

Code scanning / CodeQL

Unused import

Import of 'LoginUser' is not used.
from .app.userdb import UserDB
from cobald.daemon import service
from cobald.daemon.plugins import yaml_tag
import threading
Expand All @@ -7,7 +8,7 @@
from uvicorn.server import Server

from functools import lru_cache
from typing import List, Optional
from typing import Optional
import asyncio


Expand All @@ -16,10 +17,12 @@
class RestService(object):
def __init__(
self,
users: List = None,
user_db_file: str = "users.db",
**fast_api_args,
):
self._users = users or []
self._db = UserDB(user_db_file)
# if table already exists, ignore
self._db.try_create_users()

# necessary to avoid that the TARDIS' logger configuration is overwritten!
if "log_config" not in fast_api_args:
Expand All @@ -29,10 +32,15 @@ def __init__(

@lru_cache(maxsize=16)
def get_user(self, user_name: str) -> Optional[DatabaseUser]:
for user in self._users:
if user["user_name"] == user_name:
return DatabaseUser(**user)
return None
try:
return self._db.get_user(user_name)
except Exception as e:
if str(e) == "USER NOT FOUND":
return None
raise e

def add_user(self, user: DatabaseUser):
self._db.add_user(user)

async def run(self) -> None:
server = Server(config=self._config)
Expand Down