Compare commits
4 Commits
5fd8fada2c
...
88bd79c228
Author | SHA1 | Date | |
---|---|---|---|
88bd79c228 | |||
f6e1d1c385 | |||
85b83ace12 | |||
076dc758df |
15
docs/conf.py
15
docs/conf.py
@ -6,22 +6,21 @@
|
||||
# -- Project information -----------------------------------------------------
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
|
||||
|
||||
project = 'sachet-server'
|
||||
copyright = '2023, dogeystamp'
|
||||
author = 'dogeystamp'
|
||||
project = "sachet-server"
|
||||
copyright = "2023, dogeystamp"
|
||||
author = "dogeystamp"
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
|
||||
|
||||
extensions = ["sphinx_rtd_theme"]
|
||||
|
||||
templates_path = ['_templates']
|
||||
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
|
||||
|
||||
templates_path = ["_templates"]
|
||||
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
|
||||
|
||||
|
||||
# -- Options for HTML output -------------------------------------------------
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
|
||||
|
||||
html_theme = 'sphinx_rtd_theme'
|
||||
html_static_path = ['_static']
|
||||
html_theme = "sphinx_rtd_theme"
|
||||
html_static_path = ["_static"]
|
||||
|
@ -54,6 +54,7 @@ Sachet implements the following endpoints for this API::
|
||||
GET /users/<username>
|
||||
PATCH /users/<username>
|
||||
PUT /users/<username>
|
||||
DELETE /users/<username>
|
||||
|
||||
GET
|
||||
^^^
|
||||
@ -117,6 +118,13 @@ For example:
|
||||
|
||||
Only :ref:`administrators<permissions_table>` can request this method.
|
||||
|
||||
DELETE
|
||||
^^^^^^
|
||||
|
||||
Requesting ``DELETE /users/<username>`` deletes the specified user.
|
||||
|
||||
Only :ref:`administrators<permissions_table>` can request this method.
|
||||
|
||||
.. _user_list_api:
|
||||
|
||||
List API
|
||||
@ -139,3 +147,15 @@ POST
|
||||
|
||||
``POST /users`` creates a new user.
|
||||
The request body must conform to the :ref:`User schema<user_schema>`.
|
||||
|
||||
The server will return a ``201 Created`` code with a similar body to this:
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"status": "success",
|
||||
"url": "/users/user"
|
||||
}
|
||||
|
||||
The ``url`` field is the URL to the new user.
|
||||
It can be used in further requests to manage the user's information, or delete it.
|
||||
|
@ -91,7 +91,7 @@ class User(db.Model):
|
||||
password, current_app.config.get("BCRYPT_LOG_ROUNDS")
|
||||
).decode()
|
||||
self.username = username
|
||||
self.url = url_for("users_blueprint.user_list_api", username=self.username)
|
||||
self.url = url_for("users_blueprint.user_api", username=self.username)
|
||||
self.register_date = datetime.datetime.now()
|
||||
|
||||
def encode_token(self, jti=None):
|
||||
|
@ -109,7 +109,7 @@ users_blueprint.add_url_rule(
|
||||
|
||||
|
||||
class UserAPI(ModelAPI):
|
||||
"""User information API"""
|
||||
"""User information API."""
|
||||
|
||||
@auth_required
|
||||
def get(self, username, auth_user=None):
|
||||
@ -136,11 +136,16 @@ class UserAPI(ModelAPI):
|
||||
put_user = User.query.filter_by(username=username).first()
|
||||
return super().put(put_user)
|
||||
|
||||
@auth_required(required_permissions=(Permissions.ADMIN,))
|
||||
def delete(self, username, auth_user=None):
|
||||
delete_user = User.query.filter_by(username=username).first()
|
||||
return super().delete(delete_user)
|
||||
|
||||
|
||||
users_blueprint.add_url_rule(
|
||||
"/users/<username>",
|
||||
view_func=UserAPI.as_view("user_api"),
|
||||
methods=["GET", "PATCH", "PUT"],
|
||||
methods=["GET", "PATCH", "PUT", "DELETE"],
|
||||
)
|
||||
|
||||
|
||||
|
@ -125,6 +125,16 @@ def users(client):
|
||||
Permissions.READ,
|
||||
),
|
||||
),
|
||||
no_admin_user=dict(
|
||||
password="password",
|
||||
permissions=Bitmask(
|
||||
Permissions.CREATE,
|
||||
Permissions.MODIFY,
|
||||
Permissions.DELETE,
|
||||
Permissions.LOCK,
|
||||
Permissions.READ,
|
||||
),
|
||||
),
|
||||
administrator=dict(password="4321", permissions=Bitmask(Permissions.ADMIN)),
|
||||
)
|
||||
|
||||
|
68
tests/test_user.py
Normal file
68
tests/test_user.py
Normal file
@ -0,0 +1,68 @@
|
||||
import pytest
|
||||
|
||||
|
||||
def test_post(client, users, auth):
|
||||
"""Test registering a user, then logging in to it."""
|
||||
# register without adequate permissions
|
||||
resp = client.post(
|
||||
"/users",
|
||||
headers=auth("no_admin_user"),
|
||||
json={"username": "claire", "permissions": [], "password": "claire123"},
|
||||
)
|
||||
assert resp.status_code == 403
|
||||
# properly register
|
||||
resp = client.post(
|
||||
"/users",
|
||||
headers=auth("administrator"),
|
||||
json={"username": "claire", "permissions": [], "password": "claire123"},
|
||||
)
|
||||
assert resp.status_code == 201
|
||||
data = resp.get_json()
|
||||
url = data.get("url")
|
||||
assert url is not None
|
||||
assert url == "/users/claire"
|
||||
|
||||
# try logging in now
|
||||
resp = client.post(
|
||||
"/users/login", json={"username": "claire", "password": "claire123"}
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
data = resp.get_json()
|
||||
assert data.get("status") == "success"
|
||||
assert data.get("username") == "claire"
|
||||
token = data.get("auth_token")
|
||||
assert token is not None and token != ""
|
||||
|
||||
|
||||
def test_delete(client, users, auth):
|
||||
"""Test registering a user, then deleting it."""
|
||||
resp = client.post(
|
||||
"/users",
|
||||
headers=auth("administrator"),
|
||||
json={"username": "claire", "permissions": [], "password": "claire123"},
|
||||
)
|
||||
assert resp.status_code == 201
|
||||
|
||||
# try logging in now
|
||||
resp = client.post(
|
||||
"/users/login", json={"username": "claire", "password": "claire123"}
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
data = resp.get_json()
|
||||
token = data.get("auth_token")
|
||||
|
||||
# test if the token works
|
||||
resp = client.get("/users/claire", headers={"Authorization": f"bearer {token}"})
|
||||
assert resp.status_code == 200
|
||||
|
||||
# delete without permission
|
||||
resp = client.delete("/users/claire", headers=auth("no_admin_user"))
|
||||
assert resp.status_code == 403
|
||||
|
||||
# delete properly
|
||||
resp = client.delete("/users/claire", headers=auth("administrator"))
|
||||
assert resp.status_code == 200
|
||||
|
||||
# test if the token for a non-existent user works
|
||||
resp = client.get("/users/claire", headers={"Authentication": f"bearer {token}"})
|
||||
assert resp.status_code == 401
|
Loading…
x
Reference in New Issue
Block a user