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 -----------------------------------------------------
|
# -- Project information -----------------------------------------------------
|
||||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
|
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
|
||||||
|
|
||||||
project = 'sachet-server'
|
project = "sachet-server"
|
||||||
copyright = '2023, dogeystamp'
|
copyright = "2023, dogeystamp"
|
||||||
author = 'dogeystamp'
|
author = "dogeystamp"
|
||||||
|
|
||||||
# -- General configuration ---------------------------------------------------
|
# -- General configuration ---------------------------------------------------
|
||||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
|
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
|
||||||
|
|
||||||
extensions = ["sphinx_rtd_theme"]
|
extensions = ["sphinx_rtd_theme"]
|
||||||
|
|
||||||
templates_path = ['_templates']
|
templates_path = ["_templates"]
|
||||||
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
|
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# -- Options for HTML output -------------------------------------------------
|
# -- Options for HTML output -------------------------------------------------
|
||||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
|
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
|
||||||
|
|
||||||
html_theme = 'sphinx_rtd_theme'
|
html_theme = "sphinx_rtd_theme"
|
||||||
html_static_path = ['_static']
|
html_static_path = ["_static"]
|
||||||
|
@ -54,6 +54,7 @@ Sachet implements the following endpoints for this API::
|
|||||||
GET /users/<username>
|
GET /users/<username>
|
||||||
PATCH /users/<username>
|
PATCH /users/<username>
|
||||||
PUT /users/<username>
|
PUT /users/<username>
|
||||||
|
DELETE /users/<username>
|
||||||
|
|
||||||
GET
|
GET
|
||||||
^^^
|
^^^
|
||||||
@ -117,6 +118,13 @@ For example:
|
|||||||
|
|
||||||
Only :ref:`administrators<permissions_table>` can request this method.
|
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:
|
.. _user_list_api:
|
||||||
|
|
||||||
List API
|
List API
|
||||||
@ -139,3 +147,15 @@ POST
|
|||||||
|
|
||||||
``POST /users`` creates a new user.
|
``POST /users`` creates a new user.
|
||||||
The request body must conform to the :ref:`User schema<user_schema>`.
|
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")
|
password, current_app.config.get("BCRYPT_LOG_ROUNDS")
|
||||||
).decode()
|
).decode()
|
||||||
self.username = username
|
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()
|
self.register_date = datetime.datetime.now()
|
||||||
|
|
||||||
def encode_token(self, jti=None):
|
def encode_token(self, jti=None):
|
||||||
|
@ -109,7 +109,7 @@ users_blueprint.add_url_rule(
|
|||||||
|
|
||||||
|
|
||||||
class UserAPI(ModelAPI):
|
class UserAPI(ModelAPI):
|
||||||
"""User information API"""
|
"""User information API."""
|
||||||
|
|
||||||
@auth_required
|
@auth_required
|
||||||
def get(self, username, auth_user=None):
|
def get(self, username, auth_user=None):
|
||||||
@ -136,11 +136,16 @@ class UserAPI(ModelAPI):
|
|||||||
put_user = User.query.filter_by(username=username).first()
|
put_user = User.query.filter_by(username=username).first()
|
||||||
return super().put(put_user)
|
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_blueprint.add_url_rule(
|
||||||
"/users/<username>",
|
"/users/<username>",
|
||||||
view_func=UserAPI.as_view("user_api"),
|
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,
|
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)),
|
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