diff --git a/.dockerignore b/.dockerignore
index 21c27454042679e6b3c941a02ece9b8b253af969..2aa6ac1d51755c39b73d5208cda1bd5effdb28e7 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -11,7 +11,6 @@ README.md
 htmlcov
 app/tests
 figures/
-oidc_dev_example
-oidc_dev/
-traefik_dev
-ceph
+.gitlab-ci.yml
+.coverage
+.git
diff --git a/.flake8 b/.flake8
deleted file mode 100644
index db234bdd12f196f109da823525b07fd5afe967d1..0000000000000000000000000000000000000000
--- a/.flake8
+++ /dev/null
@@ -1,4 +0,0 @@
-[flake8]
-max-line-length = 120
-exclude = .git,__pycache__,__init__.py,.mypy_cache,.pytest_cache
-extend-ignore = E203
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 94e00b948fca934313529e240866160e6128e544..116aac5012e51e7586f1712e19180f38b5ac43a6 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,39 +1,40 @@
-image: python:3.10-slim
+image: ${CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX}/python:3.11-slim
 
 variables:
   PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
   PYTHONPATH: "$CI_PROJECT_DIR"
   OBJECT_GATEWAY_URI: "http://127.0.0.1:8000"
-  CEPH_ACCESS_KEY: ""
-  CEPH_SECRET_KEY: ""
-  CEPH_USERNAME: ""
-  OIDC_CLIENT_SECRET: ""
-  OIDC_CLIENT_ID: ""
-  OIDC_BASE_URI: "http://127.0.0.1:8000"
+  BUCKET_CEPH_ACCESS_KEY: ""
+  BUCKET_CEPH_SECRET_KEY: ""
+  USER_CEPH_ACCESS_KEY: ""
+  USER_CEPH_SECRET_KEY: ""
+  BUCKET_CEPH_USERNAME: ""
+  FF_NETWORK_PER_BUILD: 1
+  PUBLIC_KEY_VALUE: "empty"
+  OPA_URI: "http://127.0.0.1:8181"
+  DB_PASSWORD: "random"
+  DB_USER: "random"
+  DB_DATABASE: "random"
+  DB_HOST: "random"
 
 cache:
   paths:
     - .cache/pip
     - venv/
 
-before_script:
-  - python --version  # For debugging
-  - pip install virtualenv
-  - virtualenv venv
-  - source venv/bin/activate
-  - python -m pip install -r requirements.txt
-  - python -m pip install -r requirements-dev.txt
+default:
+  tags:
+    - docker
+  before_script:
+    - python --version  # For debugging
+    - pip install virtualenv
+    - virtualenv venv
+    - source venv/bin/activate
+    - python -m pip install --upgrade -r requirements.txt -r requirements-dev.txt
 
 stages: # List of stages for jobs, and their order of execution
-#  - build
   - test
-#  - deploy
-
-#build-job:       # This job runs in the build stage, which runs first.
-#  stage: build
-#  script:
-#    - echo "Compiling the code..."
-#    - echo "Compile complete."
+  - deploy
 
 integration-test-job: # Runs integration tests with the database
   stage: test
@@ -43,17 +44,17 @@ integration-test-job: # Runs integration tests with the database
     DB_DATABASE: "integration-test-db"
     DB_HOST: "integration-test-db"
   services:
-    - name: mysql:8
+    - name: ${CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX}/mysql:8
       alias: integration-test-db
       variables:
         MYSQL_RANDOM_ROOT_PASSWORD: "yes"
         MYSQL_DATABASE: "$DB_DATABASE"
         MYSQL_USER: "$DB_USER"
         MYSQL_PASSWORD: "$DB_PASSWORD"
+    - name: $CI_REGISTRY/cmg/clowm/clowm-database:v2.3
+      alias: upgrade-db
   script:
     - python app/check_database_connection.py
-    - alembic downgrade base
-    - alembic upgrade head
     - pytest --junitxml=integration-report.xml --cov=app --cov-report=term-missing app/tests/crud
     - mkdir coverage-integration
     - mv .coverage coverage-integration
@@ -70,27 +71,18 @@ e2e-test-job: # Runs e2e tests on the API endpoints
     DB_USER: "test_api_user"
     DB_DATABASE: "e2e-test-db"
     DB_HOST: "e2e-test-db"
-    OIDC_CLIENT_SECRET: "$TEST_OIDC_CLIENT_SECRET"
-    OIDC_CLIENT_ID: "$TEST_OIDC_CLIENT_ID"
-    OIDC_BASE_URI: "http://mock-oidc-server"
-    CLIENTS_CONFIGURATION_INLINE: "$TEST_OIDC_CLIENT_CONFIG"
   services:
-    - name: mysql:8
+    - name: ${CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX}/mysql:8
       alias: e2e-test-db
       variables:
         MYSQL_RANDOM_ROOT_PASSWORD: "yes"
         MYSQL_DATABASE: "$DB_DATABASE"
         MYSQL_USER: "$DB_USER"
         MYSQL_PASSWORD: "$DB_PASSWORD"
-    - name: ghcr.io/soluto/oidc-server-mock:latest
-      alias: mock-oidc-server
-      variables:
-        ASPNETCORE_ENVIRONMENT: "Development"
+    - name: $CI_REGISTRY/cmg/clowm/clowm-database:v2.3
+      alias: upgrade-db
   script:
     - python app/check_database_connection.py
-    - python app/check_oidc_connection.py
-    - alembic downgrade base
-    - alembic upgrade head
     - pytest --junitxml=e2e-report.xml --cov=app --cov-report=term-missing app/tests/api
     - mkdir coverage-e2e
     - mv .coverage coverage-e2e
@@ -137,8 +129,51 @@ lint-test-job: # Runs linters checks on code
   script:
     - ./scripts/lint.sh
 
-#deploy-job:      # This job runs in the deploy stage.
-#  stage: deploy  # It only runs when *both* jobs in the test stage complete successfully.
-#  script:
-#    - echo "Deploying application..."
-#    - echo "Application successfully deployed."
+build-publish-dev-docker-container-job:
+  stage: deploy
+  image:
+    name: gcr.io/kaniko-project/executor:v1.17.0-debug
+    entrypoint: [""]
+  dependencies: []
+  only:
+    refs:
+      - development
+  before_script:
+    - echo "{\"auths\":{\"${CI_REGISTRY}\":{\"auth\":\"$(printf "%s:%s" "${CI_REGISTRY_USER}" "${CI_REGISTRY_PASSWORD}" | base64 | tr -d '\n')\"},\"$CI_DEPENDENCY_PROXY_SERVER\":{\"auth\":\"$(printf "%s:%s" ${CI_DEPENDENCY_PROXY_USER} "${CI_DEPENDENCY_PROXY_PASSWORD}" | base64 | tr -d '\n')\"}}}" > /kaniko/.docker/config.json
+  script:
+    - /kaniko/executor
+      --context "${CI_PROJECT_DIR}"
+      --dockerfile "${CI_PROJECT_DIR}/Dockerfile"
+      --destination "${CI_REGISTRY_IMAGE}:dev-${CI_COMMIT_SHA}"
+      --destination "${CI_REGISTRY_IMAGE}:dev-latest"
+    - /kaniko/executor
+      --context "${CI_PROJECT_DIR}"
+      --dockerfile "${CI_PROJECT_DIR}/Dockerfile-Gunicorn"
+      --destination "${CI_REGISTRY_IMAGE}:dev-${CI_COMMIT_SHA}-gunicorn"
+      --destination "${CI_REGISTRY_IMAGE}:dev-latest-gunicorn"
+
+publish-docker-container-job:
+  stage: deploy
+  image:
+    name: gcr.io/kaniko-project/executor:v1.17.0-debug
+    entrypoint: [""]
+  dependencies: []
+  only:
+    - tags
+  before_script:
+    - echo "{\"auths\":{\"${CI_REGISTRY}\":{\"auth\":\"$(printf "%s:%s" "${CI_REGISTRY_USER}" "${CI_REGISTRY_PASSWORD}" | base64 | tr -d '\n')\"},\"$CI_DEPENDENCY_PROXY_SERVER\":{\"auth\":\"$(printf "%s:%s" ${CI_DEPENDENCY_PROXY_USER} "${CI_DEPENDENCY_PROXY_PASSWORD}" | base64 | tr -d '\n')\"}}}" > /kaniko/.docker/config.json
+  script:
+    - /kaniko/executor
+      --context "${CI_PROJECT_DIR}"
+      --dockerfile "${CI_PROJECT_DIR}/Dockerfile"
+      --destination "${CI_REGISTRY_IMAGE}:${CI_COMMIT_TAG}"
+      --destination "${CI_REGISTRY_IMAGE}:$(echo ${CI_COMMIT_TAG} | cut -d'.' -f1-2)"
+      --destination "${CI_REGISTRY_IMAGE}:$(echo ${CI_COMMIT_TAG} | cut -d'.' -f1)"
+      --destination "${CI_REGISTRY_IMAGE}:latest"
+    - /kaniko/executor
+      --context "${CI_PROJECT_DIR}"
+      --dockerfile "${CI_PROJECT_DIR}/Dockerfile-Gunicorn"
+      --destination "${CI_REGISTRY_IMAGE}:${CI_COMMIT_TAG}-gunicorn"
+      --destination "${CI_REGISTRY_IMAGE}:$(echo ${CI_COMMIT_TAG} | cut -d'.' -f1-2)-gunicorn"
+      --destination "${CI_REGISTRY_IMAGE}:$(echo ${CI_COMMIT_TAG} | cut -d'.' -f1)-gunicorn"
+      --destination "${CI_REGISTRY_IMAGE}:latest-gunicorn"
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 6e0e5b0a031b90ec800490693c465de4f2b5ffe6..9bd76cd0124f00bc62a9bc88620c3edf21ad14ac 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -2,7 +2,7 @@
 # See https://pre-commit.com/hooks.html for more hooks
 repos:
 -   repo: https://github.com/pre-commit/pre-commit-hooks
-    rev: v4.2.0
+    rev: v4.5.0
     hooks:
     -   id: end-of-file-fixer
     -   id: check-added-large-files
@@ -15,32 +15,29 @@ repos:
     -   id: check-merge-conflict
     -   id: check-ast
 -   repo: https://github.com/psf/black
-    rev: 22.3.0
+    rev: 23.11.0
     hooks:
     -   id: black
         files: app
         args: [--check]
--   repo: https://github.com/PyCQA/flake8
-    rev: 4.0.1
+-   repo: https://github.com/charliermarsh/ruff-pre-commit
+    rev: 'v0.1.6'
     hooks:
-    -   id: flake8
+    -   id: ruff
+-   repo: https://github.com/PyCQA/isort
+    rev: 5.12.0
+    hooks:
+    -   id: isort
         files: app
-        args: [--config=.flake8]
+        args: [-c]
 -   repo: https://github.com/pre-commit/mirrors-mypy
-    rev: v0.960
+    rev: v1.7.1
     hooks:
     -   id: mypy
         files: app
         args: [--config=pyproject.toml]
         additional_dependencies:
-            - sqlalchemy2-stubs
-            - boto3-stubs-lite[s3]
-            - sqlalchemy<2.0.0
-            - pydantic
+            - boto3-stubs-lite[s3]>=1.29.0,<1.30.0
+            - sqlalchemy>=2.0.0,<2.1.0
+            - pydantic>=2.5.0,<2.6.0
             - types-requests
--   repo: https://github.com/PyCQA/isort
-    rev: 5.10.1
-    hooks:
-    -   id: isort
-        files: app
-        args: [-c]
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 81c327a317f8fcb74da56cbe8385db759ede2de0..db4ef25e13a0cd01ed3d9f37831f44ab29f97494 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,11 @@
+## Version 1.1.2
+
+### General
+* When requesting a list of buckets, all buckets, including WRITE-only buckets, are returned #34
+
+### Fixes
+* User with WRITE/READWRITE permission can now delete multiple Objects with a single request to the S3 endpoint #34
+
 ## Version 1.1.1
 
 ### General
diff --git a/DEVELOPING.md b/DEVELOPING.md
deleted file mode 100644
index 72c11d907c14507ade41e8c2a6f575acf8c01cd4..0000000000000000000000000000000000000000
--- a/DEVELOPING.md
+++ /dev/null
@@ -1,155 +0,0 @@
-## Development Setup
-
-### Python Setup 馃悕
-Currently, only Python version `>=3.10` is supported because it uses its new features for type annotations
-to write more compact code. Since FastAPI relies on these type annotations and `mypy` is integrated into
-this project, we make heavy use of this feature.
-
-Write
-```python
-var1: list[int] = [1,2,3]
-var2: str | None = None
-```
-instead of
-```python
-from typing import List, Optional
-
-var1: List[int] = [1,2,3]
-var2: Optional[str] = None
-```
-### Environment Setup
-Create a virtual environment, install the dependencies and install the [pre-commit](https://pre-commit.com/) hooks.<br>
-The linters can prevent a commit of the code quality doesn't meet the standard.
-```shell
-python -m venv venv
-source venv/bin/activate
-python -m pip install -r requirements.txt
-python -m pip install -r requirements-dev.txt
-pre-commit install
-```
-
-### Ceph Setup
-For how to set up a ceph cluster or how to connect to an existing one see
-the [documentation in the ceph folder](ceph/README.md).
-
-A user with `user` capabilities should be created, e.g.<br>
-`radosgw-admin user create --uid=myadmin --caps="users=*"`
-
-### Database Setup
-#### Dev database
-The easiest solution is [Docker](https://docs.docker.com/get-docker/) with an attached volume
-to set up a MySQL database.
-```shell
-docker volume create proxyapi_dev_db
-docker run --name proxyapi_devdb \
-  -e MYSQL_RANDOM_ROOT_PASSWORD=yes \
-  -e MYSQL_DATABASE=<database_name> \
-  -e MYSQL_USER=<database_user> \
-  -e MYSQL_PASSWORD=<database_password> \
-  -p 127.0.0.1:3306:3306 \
-  -v proxyapi_dev_db:/var/lib/mysql \
-  -d \
-  mysql:8
-```
-When the container stopped just restart it with
-```shell
-docker start proxyapi_devdb
-```
-Look at the [Environment Variables](#environment-variables) section to see which env variables have to be set.
-#### Test database
-Set up a second database on a different port for the integration tests. This database doesn't have to be persistent
-because all data will be purged after each test run.
-```shell
-docker run --name proxyapi_testdb \
-  -e MYSQL_RANDOM_ROOT_PASSWORD=yes \
-  -e MYSQL_DATABASE=<database_name> \
-  -e MYSQL_USER=<database_user> \
-  -e MYSQL_PASSWORD=<database_password> \
-  -p 127.0.0.1:8001:3306 \
-  -d \
-  mysql:8
-```
-
-### Dev OIDC Provider Setup
-To avoid the complex process of connecting the local machine with the LifeScience AAI Test server, a simple [OIDC provider](https://github.com/Soluto/oidc-server-mock)
-can be setup with Docker.<br>
-Copy the `oidc_dev_example` directory to `oidc_dev`
-```shell
-cp -r oidc_dev_example oidc_dev
-```
-In the file `oidc_dev/clients_config.json` add a random value to `ClientId` and `ClientSecrets`. These can be generated for example with `openssl`.
-```shell
-openssl rand -hex 10
-```
-You can add/delete users in the file `oidc_dev/users_config.json` according the schema that is provided there.<br>
-Adjust the volume path and start the docker container
-```shell
-docker run --name proxyapi_oidc_provider \
-  -e CLIENTS_CONFIGURATION_PATH=/tmp/config/clients_config.json \
-  -e IDENTITY_RESOURCES_PATH=/tmp/config/identity_resources.json \
-  -e USERS_CONFIGURATION_PATH=/tmp/config/users_config.json \
-  -e SERVER_OPTIONS_PATH=/tmp/config/server_options.json \
-  -e ASPNETCORE_ENVIRONMENT=Development \
-  -p 127.0.0.1:8002:80 \
-  -v /path/to/folder/oidc_dev:/tmp/config:ro \
-  -d \
-  ghcr.io/soluto/oidc-server-mock:latest
-```
-Set the env variables `OIDC_BASE_URI` to `http://localhost:8002` and `OIDC_CLIENT_SECRET` / `OIDC_CLIENT_ID` to their appropriate value.
-
-### Reverse Proxy Setup
-The `API_PREFIX` is handles on the level of the reverse proxy. This simplifies the routing in the code and the cooperation with the [Frontend](https://gitlab.ub.uni-bielefeld.de/denbi/object-storage-access-ui).
-An simple Traefik reverse proxy configuration is stored in the repository.
-
-[Traefik](https://traefik.io/) is a reverse Proxy written in Go.
-To use it, download the [`traefik`](https://github.com/traefik/traefik/releases) binary and start it with
-```shell
-cd traefik_dev
-/path/to/binary/traefik --configFile=traefik.toml
-```
-The provided configuration does the following things
- * It forwards all request to http://localhost:9999/api/* to http://localhost:8080 (this backend)
- * It strips the prefix `/api` before it forwards the request to the backend
- * All other request will be forwarded to http://localhost:5173, the corresponding dev [Frontend](https://gitlab.ub.uni-bielefeld.de/denbi/object-storage-access-ui)
- * Hides all the RADOS Gateways behind http://localhost:9998 and distributes all requests equally to the Gateways
- * Takes care of the CORS header for the RADOS Gateway
-
-You don't have to use Traefik for that. You can use any reverse proxy for this task, like [Caddy](https://caddyserver.com/), [HAProxy](https://www.haproxy.org/) or [nginx](https://nginx.org/en/).<br>
-
-### Run Dev Server
-Export all necessary environment variables or create a `.env` file.<br>
-Run the dev server with live reload after changes
-```shell
-python app/check_ceph_connection.py && \
- python app/check_oidc_connection.py && \
- python app/check_database_connection.py && \
- alembic upgrade head && \
- uvicorn app.main:app --reload
-```
-You can check your code with linters or even automatically reformat files based on these rules
-```shell
-./scripts/lint.sh      # check code
-./scripts/format.sh    # reformat code
-```
-
-### Run Tests
-Export the port and other variables of the database and then start the test script
-```shell
-export DB_PORT=8001
-./tests-start.sh
-```
-
-### Common Problems
-Q: When I start the server I get the error `ModuleNotFoundError: No module named 'app'`<br>
-A: export the `PYTHONPATH` variable with the current working directory
-```shell
-export PYTHONPATH=$(pwd)
-```
-
-Q: When I start the linters `isort`, `black`, etc. cannot be found<br>
-A: Prepend every call with `python -m`
-```shell
-python -m isort
-python -m black
-...
-```
diff --git a/Dockerfile b/Dockerfile
index e4434f1cfbdbb6027d619b745932f519d8220583..b791fde634f0c5dcc5dbbec9286cf43a47feb1f4 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,18 +1,23 @@
-FROM python:3.10-slim
-WORKDIR /code
-ENV PYTHONPATH=/code
-EXPOSE 80
+FROM python:3.11-slim
+EXPOSE 8000
 
 # dumb-init forwards the kill signal to the python process
 RUN apt-get update && apt-get -y install dumb-init curl
+RUN apt-get clean
 ENTRYPOINT ["/usr/bin/dumb-init", "--"]
 
-HEALTHCHECK --interval=35s --timeout=4s CMD curl -f http://localhost/health || exit 1
+HEALTHCHECK --interval=30s --timeout=2s CMD curl -f http://localhost:8000/health || exit 1
 
-COPY requirements.txt ./requirements.txt
+RUN useradd -m worker
+USER worker
+WORKDIR /home/worker/code
+ENV PYTHONPATH=/home/worker/code
+ENV PATH="/home/worker/.local/bin:${PATH}"
 
-RUN pip install --no-cache-dir --upgrade -r requirements.txt
+COPY --chown=worker:worker requirements.txt ./requirements.txt
 
-COPY . .
+RUN pip install --user --no-cache-dir --upgrade -r requirements.txt
+
+COPY --chown=worker:worker . .
 
 CMD ["./start_service.sh"]
diff --git a/Dockerfile-Gunicorn b/Dockerfile-Gunicorn
new file mode 100644
index 0000000000000000000000000000000000000000..79f82be9b4fa45d10d66ed9256a5d9f97337a34a
--- /dev/null
+++ b/Dockerfile-Gunicorn
@@ -0,0 +1,14 @@
+FROM tiangolo/uvicorn-gunicorn-fastapi:python3.11-slim
+EXPOSE 8000
+ENV PORT=8000
+
+RUN pip install --no-cache-dir httpx[cli]
+
+HEALTHCHECK --interval=30s --timeout=4s CMD httpx http://localhost:$PORT/health || exit 1
+
+COPY ./scripts/prestart.sh /app/prestart.sh
+COPY ./requirements.txt /app/requirements.txt
+
+RUN pip install --no-cache-dir --upgrade -r requirements.txt
+
+COPY ./app /app/app
diff --git a/README.md b/README.md
index 7491ae1bd41e92358d6d3cb920bb2e925dffee58..977c63b0e00418ecd318e5c99a750bdca203a953 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# S3 Proxy API
+# CloWM S3Proxy Service
 
 ## Description
 Openstack is shipping with an integrated UI to access the Object Store provided by Ceph. Unfortunately, this UI does not allow
@@ -21,34 +21,28 @@ user-friendly manner. 馃憤
 
 ### Mandatory / Recommended Variables
 
-| Variable             | Default | Value                 | Description                           |
-|----------------------|---------|-----------------------|---------------------------------------|
-| `SECRET_KEY`         | random  | \<random key>         | Secret key to sign JWT                |
-| `DB_HOST`            | unset   | <db hostname / IP>    | IP or Hostname Adress of DB           |
-| `DB_PORT`            | 3306    | Number                | Port of the database                  |
-| `DB_USER`            | unset   | \<db username>        | Username of the database user         |
-| `DB_PASSWORD`        | unset   | \<db password>        | Password of the database user         |
-| `DB_DATABASE`        | unset   | \<db name>            | Name of the database                  |
-| `OBJECT_GATEWAY_URI` | unset   | HTTP URL              | HTTP URL of the Ceph Object Gateway   |
-| `CEPH_ACCESS_KEY`    | unset   | \<access key>         | Ceph access key with admin privileges |
-| `CEPH_SECRET_KEY`    | unset   | \<secret key>         | Ceph secret key with admin privileges |
-| `CEPH_USERNAME`      | unset   | \<ceph username>      | Username in Ceph of the backend user  |
-| `OIDC_CLIENT_ID`     | unset   | \<OIDC client id>     | Client ID from the OIDC provider      |
-| `OIDC_CLIENT_SECRET` | unset   | \<OIDC client secret> | Client Secret from the OIDC provider  |
-| `OIDC_BASE_URI`      | unset   | HTTP URL              | HTTP URL of the OIDC Provider         |
+| Variable                               | Default                 | Value                           | Description                                                                        |
+|----------------------------------------|-------------------------|---------------------------------|------------------------------------------------------------------------------------|
+| `DB_HOST`                              | unset                   | <db hostname / IP>              | IP or Hostname Address of DB                                                       |
+| `DB_PORT`                              | 3306                    | Number                          | Port of the database                                                               |
+| `DB_USER`                              | unset                   | \<db username>                  | Username of the database user                                                      |
+| `DB_PASSWORD`                          | unset                   | \<db password>                  | Password of the database user                                                      |
+| `DB_DATABASE`                          | unset                   | \<db name>                      | Name of the database                                                               |
+| `OBJECT_GATEWAY_URI`                   | unset                   | HTTP URL                        | HTTP URL of the Ceph Object Gateway                                                |
+| `BUCKET_CEPH_ACCESS_KEY`               | unset                   | \<access key>                   | Access key for the Ceph Object Gateway user with unlimited buckets.                |
+| `BUCKET_CEPH_SECRET_KEY`               | unset                   | \<secret key>                   | Secret key for the Ceph Object Gateway user with unlimited buckets.                |
+| `BUCKET_CEPH_USERNAME`                 | unset                   | \<ceph username>                | ID of the user in ceph who owns all the buckets. Owner of `BUCKET_CEPH_ACCESS_KEY` |
+| `USER_CEPH_ACCESS_KEY`                 | unset                   | \<access key>                   | Access key for the Ceph Object Gateway user with `user:*` privileges               |
+| `USER_CEPH_SECRET_KEY`                 | unset                   | \<secret key>                   | Secret key for the Ceph Object Gateway user with `user:*` privileges.              |
+| `PUBLIC_KEY_VALUE` / `PUBLIC_KEY_FILE` | randomly generated      | Public Key / Path to Public Key | Public part of RSA Key in PEM format to verify JWTs                                |
+| `OPA_URI`                              | unset                   | HTTP URL                        | HTTP URL of the OPA service                                                        |
+| `CLOWM_URL`                            | `http://localhost:8080` | HTTP URL                        | HTTP URL of the CloWM website                                                      |
 
 ### Optional Variables
 
-| Variable                    | Default                             | Value                       | Description                                                                           |
-|-----------------------------|-------------------------------------|-----------------------------|---------------------------------------------------------------------------------------|
-| `DOMAIN`                    | `localhost`                         | string                      | Domain under which the service will be hosted.                                        |
-| `SSL_TERMINATION`           | `false`                             | `<"true"&#x7c;"false">`     | Flag if the service runs behind a SSL termination proxy                               |
-| `API_PREFIX`                | `/api`                              | URL path                    | Prefix before every URL path                                                          |
-| `JWT_TOKEN_EXPIRE_MINUTES`  | 8 days                              | number                      | Minutes till a JWT expires                                                            |
-| `BACKEND_CORS_ORIGINS`      | `[]`                                | json formatted list of urls | List of valid CORS origins                                                            |
-| `SQLALCHEMY_VERBOSE_LOGGER` | `false`                             | `<"true"&#x7c;"false">`     | Enables verbose SQL output.<br>Should be `false` in production                        |
-| `OIDC_META_INFO_PATH`       | `/.well-known/openid-configuration` | URL path                    | Path to the OIDC configuration file<br> Will be concatenated with the `OIDC_BASE_URI` |
-
-## Getting started
-This service depends on multiple other services. See [DEVELOPING.md](DEVELOPING.md) how to set these up for developing
-on your local machine.
+| Variable                    | Default              | Value                       | Description                                                    |
+|-----------------------------|----------------------|-----------------------------|----------------------------------------------------------------|
+| `API_PREFIX`                | `/api`               | URL path                    | Prefix before every URL path                                   |
+| `SQLALCHEMY_VERBOSE_LOGGER` | `false`              | `<"true"&#x7c;"false">`     | Enables verbose SQL output.<br>Should be `false` in production |
+| `OPA_POLICY_PATH`           | `/clowm/authz/allow` | URL path                    | Path to the OPA Policy for Authorization                       |
+| `OTLP_GRPC_ENDPOINT`        | unset                | <hostname / IP>             | OTLP compatible endpoint to send traces via gRPC, e.g. Jaeger  |
diff --git a/alembic.ini b/alembic.ini
deleted file mode 100644
index 4140727b52ff0b493b64ebb929d898d41ae4fa86..0000000000000000000000000000000000000000
--- a/alembic.ini
+++ /dev/null
@@ -1,100 +0,0 @@
-# A generic, single database configuration.
-
-[alembic]
-# path to migration scripts
-script_location = alembic
-
-# template used to generate migration files
-# file_template = %%(rev)s_%%(slug)s
-
-# sys.path path, will be prepended to sys.path if present.
-# defaults to the current working directory.
-prepend_sys_path = .
-
-# timezone to use when rendering the date within the migration file
-# as well as the filename.
-# If specified, requires the python-dateutil library that can be
-# installed by adding `alembic[tz]` to the pip requirements
-# string value is passed to dateutil.tz.gettz()
-# leave blank for localtime
-# timezone =
-
-# max length of characters to apply to the
-# "slug" field
-# truncate_slug_length = 40
-
-# set to 'true' to run the environment during
-# the 'revision' command, regardless of autogenerate
-# revision_environment = false
-
-# set to 'true' to allow .pyc and .pyo files without
-# a source .py file to be detected as revisions in the
-# versions/ directory
-# sourceless = false
-
-# version location specification; This defaults
-# to alembic/versions.  When using multiple version
-# directories, initial revisions must be specified with --version-path.
-# The path separator used here should be the separator specified by "version_path_separator" below.
-# version_locations = %(here)s/bar:%(here)s/bat:alembic/versions
-
-# version path separator; As mentioned above, this is the character used to split
-# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep.
-# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas.
-# Valid values for version_path_separator are:
-#
-# version_path_separator = :
-# version_path_separator = ;
-# version_path_separator = space
-version_path_separator = os  # Use os.pathsep. Default configuration used for new projects.
-
-# the output encoding used when revision files
-# are written from script.py.mako
-# output_encoding = utf-8
-
-
-[post_write_hooks]
-# post_write_hooks defines scripts or Python functions that are run
-# on newly generated revision scripts.  See the documentation for further
-# detail and examples
-
-# format using "black" - use the console_scripts runner, against the "black" entrypoint
-# hooks = black
-# black.type = console_scripts
-# black.entrypoint = black
-# black.options = -l 79 REVISION_SCRIPT_FILENAME
-
-# Logging configuration
-[loggers]
-keys = root,sqlalchemy,alembic
-
-[handlers]
-keys = console
-
-[formatters]
-keys = generic
-
-[logger_root]
-level = WARN
-handlers = console
-qualname =
-
-[logger_sqlalchemy]
-level = WARN
-handlers =
-qualname = sqlalchemy.engine
-
-[logger_alembic]
-level = INFO
-handlers =
-qualname = alembic
-
-[handler_console]
-class = StreamHandler
-args = (sys.stderr,)
-level = NOTSET
-formatter = generic
-
-[formatter_generic]
-format = %(levelname)-5.5s [%(name)s] %(message)s
-datefmt = %H:%M:%S
diff --git a/alembic/README b/alembic/README
deleted file mode 100644
index a23d4fb519d3329160c17e5573c1382ef8337e6b..0000000000000000000000000000000000000000
--- a/alembic/README
+++ /dev/null
@@ -1 +0,0 @@
-Generic single-database configuration with an async dbapi.
diff --git a/alembic/env.py b/alembic/env.py
deleted file mode 100644
index 6d91f4d45408643589b4df5bb07370423bc579a1..0000000000000000000000000000000000000000
--- a/alembic/env.py
+++ /dev/null
@@ -1,93 +0,0 @@
-import asyncio
-from logging.config import fileConfig
-
-from sqlalchemy import engine_from_config, pool
-from sqlalchemy.ext.asyncio import AsyncEngine
-
-from alembic import context
-from app.core.config import settings
-from app.db.base import Base
-
-# this is the Alembic Config object, which provides
-# access to the values within the .ini file in use.
-config = context.config
-
-# Interpret the config file for Python logging.
-# This line sets up loggers basically.
-if config.config_file_name is not None:
-    fileConfig(config.config_file_name)
-
-# add your model's MetaData object here
-# for 'autogenerate' support
-target_metadata = Base.metadata
-
-
-# other values from the config, defined by the needs of env.py,
-# can be acquired:
-# my_important_option = config.get_main_option("my_important_option")
-# ... etc.
-
-
-def get_url() -> str:
-    return str(settings.SQLALCHEMY_DATABASE_ASYNC_URI)
-
-
-def run_migrations_offline():
-    """Run migrations in 'offline' mode.
-
-    This configures the context with just a URL
-    and not an Engine, though an Engine is acceptable
-    here as well.  By skipping the Engine creation
-    we don't even need a DBAPI to be available.
-
-    Calls to context.execute() here emit the given string to the
-    script output.
-
-    """
-    url = get_url()
-    context.configure(
-        url=url,
-        target_metadata=target_metadata,
-        literal_binds=True,
-        dialect_opts={"paramstyle": "named"},
-    )
-
-    with context.begin_transaction():
-        context.run_migrations()
-
-
-def do_run_migrations(connection):
-    context.configure(connection=connection, target_metadata=target_metadata)
-
-    with context.begin_transaction():
-        context.run_migrations()
-
-
-async def run_migrations_online():
-    """Run migrations in 'online' mode.
-
-    In this scenario we need to create an Engine
-    and associate a connection with the context.
-
-    """
-    url = get_url()
-    connectable = AsyncEngine(
-        engine_from_config(
-            config.get_section(config.config_ini_section),
-            prefix="sqlalchemy.",
-            poolclass=pool.NullPool,
-            future=True,
-            url=url,
-        )
-    )
-
-    async with connectable.connect() as connection:
-        await connection.run_sync(do_run_migrations)
-
-    await connectable.dispose()
-
-
-if context.is_offline_mode():
-    run_migrations_offline()
-else:
-    asyncio.run(run_migrations_online())
diff --git a/alembic/script.py.mako b/alembic/script.py.mako
deleted file mode 100644
index 2c0156303a8df3ffdc9de87765bf801bf6bea4a5..0000000000000000000000000000000000000000
--- a/alembic/script.py.mako
+++ /dev/null
@@ -1,24 +0,0 @@
-"""${message}
-
-Revision ID: ${up_revision}
-Revises: ${down_revision | comma,n}
-Create Date: ${create_date}
-
-"""
-from alembic import op
-import sqlalchemy as sa
-${imports if imports else ""}
-
-# revision identifiers, used by Alembic.
-revision = ${repr(up_revision)}
-down_revision = ${repr(down_revision)}
-branch_labels = ${repr(branch_labels)}
-depends_on = ${repr(depends_on)}
-
-
-def upgrade():
-    ${upgrades if upgrades else "pass"}
-
-
-def downgrade():
-    ${downgrades if downgrades else "pass"}
diff --git a/alembic/versions/5521b5759004_create_user_and_bucket_table.py b/alembic/versions/5521b5759004_create_user_and_bucket_table.py
deleted file mode 100644
index 5fa91f9575bffe0df65087cd6d5d8406024a9db0..0000000000000000000000000000000000000000
--- a/alembic/versions/5521b5759004_create_user_and_bucket_table.py
+++ /dev/null
@@ -1,51 +0,0 @@
-"""Create user and bucket table
-
-Revision ID: 5521b5759004
-Revises:
-Create Date: 2022-05-03 14:01:22.154984
-
-"""
-import sqlalchemy as sa
-from sqlalchemy.dialects import mysql
-
-from alembic import op
-
-# revision identifiers, used by Alembic.
-revision = "5521b5759004"
-down_revision = None
-branch_labels = None
-depends_on = None
-
-
-def upgrade():
-    # ### commands auto generated by Alembic - please adjust! ###
-    op.create_table(
-        "user",
-        sa.Column("uid", sa.String(length=64), nullable=False),
-        sa.Column("name", sa.String(length=256), nullable=False),
-        sa.PrimaryKeyConstraint("uid"),
-    )
-    op.create_index(op.f("ix_user_uid"), "user", ["uid"], unique=True)
-    op.create_table(
-        "bucket",
-        sa.Column("name", sa.String(length=63), nullable=False),
-        sa.Column("description", mysql.TEXT(), nullable=False),
-        sa.Column("public", sa.Boolean(), server_default="0", nullable=True),
-        sa.Column("owner_id", sa.String(length=64), nullable=True),
-        sa.ForeignKeyConstraint(
-            ["owner_id"],
-            ["user.uid"],
-        ),
-        sa.PrimaryKeyConstraint("name"),
-    )
-    op.create_index(op.f("ix_bucket_name"), "bucket", ["name"], unique=True)
-    # ### end Alembic commands ###
-
-
-def downgrade():
-    # ### commands auto generated by Alembic - please adjust! ###
-    op.drop_index(op.f("ix_bucket_name"), table_name="bucket")
-    op.drop_table("bucket")
-    op.drop_index(op.f("ix_user_uid"), table_name="user")
-    op.drop_table("user")
-    # ### end Alembic commands ###
diff --git a/alembic/versions/6c64f020818b_make_display_name_for_users_mandatory.py b/alembic/versions/6c64f020818b_make_display_name_for_users_mandatory.py
deleted file mode 100644
index 9b70f67ffa093ba5f172e2c4aff61ceb5211f6e1..0000000000000000000000000000000000000000
--- a/alembic/versions/6c64f020818b_make_display_name_for_users_mandatory.py
+++ /dev/null
@@ -1,28 +0,0 @@
-"""Make display_name for users mandatory
-
-Revision ID: 6c64f020818b
-Revises: 9fa582febebe
-Create Date: 2022-10-21 13:53:44.446799
-
-"""
-from sqlalchemy.dialects import mysql
-
-from alembic import op
-
-# revision identifiers, used by Alembic.
-revision = "6c64f020818b"
-down_revision = "9fa582febebe"
-branch_labels = None
-depends_on = None
-
-
-def upgrade():
-    # ### commands auto generated by Alembic - please adjust! ###
-    op.alter_column("user", "display_name", existing_type=mysql.VARCHAR(length=256), nullable=False)
-    # ### end Alembic commands ###
-
-
-def downgrade():
-    # ### commands auto generated by Alembic - please adjust! ###
-    op.alter_column("user", "display_name", existing_type=mysql.VARCHAR(length=256), nullable=True)
-    # ### end Alembic commands ###
diff --git a/alembic/versions/83a3a47a6351_add_username_and_display_name_and_drop_.py b/alembic/versions/83a3a47a6351_add_username_and_display_name_and_drop_.py
deleted file mode 100644
index 0e0064f62d1f7f68405f992b3022f8248c0dcc67..0000000000000000000000000000000000000000
--- a/alembic/versions/83a3a47a6351_add_username_and_display_name_and_drop_.py
+++ /dev/null
@@ -1,35 +0,0 @@
-"""Add username and display_name and drop name for user table
-
-Revision ID: 83a3a47a6351
-Revises: cafa1e01b782
-Create Date: 2022-05-04 13:22:46.317796
-
-"""
-import sqlalchemy as sa
-from sqlalchemy.dialects import mysql
-
-from alembic import op
-
-# revision identifiers, used by Alembic.
-revision = "83a3a47a6351"
-down_revision = "cafa1e01b782"
-branch_labels = None
-depends_on = None
-
-
-def upgrade():
-    # ### commands auto generated by Alembic - please adjust! ###
-    op.add_column("user", sa.Column("display_name", sa.String(length=256), nullable=True))
-    op.add_column("user", sa.Column("username", sa.String(length=256), nullable=False))
-    op.drop_column("user", "name")
-    op.create_index(op.f("ix_user_username"), "user", ["username"], unique=True)
-    # ### end Alembic commands ###
-
-
-def downgrade():
-    # ### commands auto generated by Alembic - please adjust! ###
-    op.add_column("user", sa.Column("name", mysql.VARCHAR(length=256), nullable=False))
-    op.drop_index(op.f("ix_user_username"), table_name="user")
-    op.drop_column("user", "username")
-    op.drop_column("user", "display_name")
-    # ### end Alembic commands ###
diff --git a/alembic/versions/9fa582febebe_delete_username_from_user.py b/alembic/versions/9fa582febebe_delete_username_from_user.py
deleted file mode 100644
index 76e72f0c1a0010d7902f01a8a0a93ba455f1badf..0000000000000000000000000000000000000000
--- a/alembic/versions/9fa582febebe_delete_username_from_user.py
+++ /dev/null
@@ -1,31 +0,0 @@
-"""Delete username from user
-
-Revision ID: 9fa582febebe
-Revises: 83a3a47a6351
-Create Date: 2022-07-27 11:10:53.440935
-
-"""
-import sqlalchemy as sa
-from sqlalchemy.dialects import mysql
-
-from alembic import op
-
-# revision identifiers, used by Alembic.
-revision = "9fa582febebe"
-down_revision = "83a3a47a6351"
-branch_labels = None
-depends_on = None
-
-
-def upgrade():
-    # ### commands auto generated by Alembic - please adjust! ###
-    op.drop_index("ix_user_username", table_name="user")
-    op.drop_column("user", "username")
-    # ### end Alembic commands ###
-
-
-def downgrade():
-    # ### commands auto generated by Alembic - please adjust! ###
-    op.add_column("user", sa.Column("username", mysql.VARCHAR(length=256), nullable=False))
-    op.create_index("ix_user_username", "user", ["username"], unique=False)
-    # ### end Alembic commands ###
diff --git a/alembic/versions/cafa1e01b782_create_permission_table.py b/alembic/versions/cafa1e01b782_create_permission_table.py
deleted file mode 100644
index 424203ff4b3f68240ac640abda047123e9013e46..0000000000000000000000000000000000000000
--- a/alembic/versions/cafa1e01b782_create_permission_table.py
+++ /dev/null
@@ -1,54 +0,0 @@
-"""Create Permission table
-
-Revision ID: cafa1e01b782
-Revises: 5521b5759004
-Create Date: 2022-05-04 11:41:54.470870
-
-"""
-import sqlalchemy as sa
-from sqlalchemy.dialects import mysql
-
-from alembic import op
-
-# revision identifiers, used by Alembic.
-revision = "cafa1e01b782"
-down_revision = "5521b5759004"
-branch_labels = None
-depends_on = None
-
-
-def upgrade():
-    # ### commands auto generated by Alembic - please adjust! ###
-    op.create_table(
-        "bucketpermission",
-        sa.Column("user_id", sa.String(length=64), nullable=False),
-        sa.Column("bucket_name", sa.String(length=63), nullable=False),
-        sa.Column("from", mysql.TIMESTAMP(), nullable=True),
-        sa.Column("to", mysql.TIMESTAMP(), nullable=True),
-        sa.Column("file_prefix", sa.String(length=512), nullable=True),
-        sa.Column("permissions", mysql.ENUM("READ", "WRITE", "READWRITE"), nullable=False),
-        sa.ForeignKeyConstraint(["bucket_name"], ["bucket.name"], ondelete="CASCADE"),
-        sa.ForeignKeyConstraint(["user_id"], ["user.uid"], ondelete="CASCADE"),
-        sa.PrimaryKeyConstraint("user_id", "bucket_name"),
-    )
-    op.alter_column(
-        "bucket",
-        "public",
-        existing_type=mysql.TINYINT(display_width=1),
-        nullable=False,
-        existing_server_default=sa.text("'0'"),
-    )
-    # ### end Alembic commands ###
-
-
-def downgrade():
-    # ### commands auto generated by Alembic - please adjust! ###
-    op.alter_column(
-        "bucket",
-        "public",
-        existing_type=mysql.TINYINT(display_width=1),
-        nullable=True,
-        existing_server_default=sa.text("'0'"),
-    )
-    op.drop_table("bucketpermission")
-    # ### end Alembic commands ###
diff --git a/app/api/api.py b/app/api/api.py
index 133c1c2d1bcb637ea7c8a2dcf45cf377167c2bf5..3ba0060f85d9dc0c78a3ea8ff2d2afded3f9e462 100644
--- a/app/api/api.py
+++ b/app/api/api.py
@@ -1,12 +1,12 @@
-from typing import Any
+from typing import Any, Dict, Union
 
 from fastapi import APIRouter, Depends, status
 
 from app.api.dependencies import decode_bearer_token
-from app.api.endpoints import bucket_permissions, buckets, login, users
+from app.api.endpoints import bucket_permissions, buckets, s3key
 from app.schemas.security import ErrorDetail
 
-alternative_responses: dict[int | str, dict[str, Any]] = {
+alternative_responses: Dict[Union[int, str], Dict[str, Any]] = {
     status.HTTP_400_BAD_REQUEST: {
         "model": ErrorDetail,
         "description": "Error decoding JWT Token",
@@ -25,14 +25,13 @@ alternative_responses: dict[int | str, dict[str, Any]] = {
 }
 
 api_router = APIRouter()
-api_router.include_router(login.router)
 api_router.include_router(
     buckets.router,
     dependencies=[Depends(decode_bearer_token)],
     responses=alternative_responses,
 )
 api_router.include_router(
-    users.router,
+    s3key.router,
     dependencies=[Depends(decode_bearer_token)],
     responses=alternative_responses,
 )
diff --git a/app/api/dependencies.py b/app/api/dependencies.py
index 8423802c6b86dde94c8e22d499fbd07eb15f6798..eae25ba0d8e6deb20454739e4f1df5938333a50c 100644
--- a/app/api/dependencies.py
+++ b/app/api/dependencies.py
@@ -1,46 +1,49 @@
-from typing import TYPE_CHECKING, Any, AsyncGenerator
+from typing import TYPE_CHECKING, Annotated, AsyncGenerator, Awaitable, Callable, Dict
 
-from authlib.integrations.base_client.errors import OAuthError
 from authlib.jose.errors import BadSignatureError, DecodeError, ExpiredTokenError
-from fastapi import Depends, HTTPException, Path, status
-from fastapi.requests import Request
+from clowmdb.db.session import get_async_session
+from clowmdb.models import Bucket, User
+from fastapi import Depends, HTTPException, Path, Request, status
 from fastapi.security import HTTPBearer
 from fastapi.security.http import HTTPAuthorizationCredentials
+from httpx import AsyncClient
+from opentelemetry import trace
 from rgwadmin import RGWAdmin
 from sqlalchemy.ext.asyncio import AsyncSession
 
-from app.ceph.rgw import rgw, s3_resource
-from app.core.security import decode_token, oauth
+from app.ceph.rgw import rgw
+from app.ceph.s3 import s3_resource
+from app.core.config import settings
+from app.core.security import decode_token, request_authorization
 from app.crud.crud_bucket import CRUDBucket
-from app.crud.crud_bucket_permission import CRUDBucketPermission
 from app.crud.crud_user import CRUDUser
-from app.db.session import SessionAsync as Session
-from app.models.bucket import Bucket
-from app.models.user import User
-from app.schemas.security import JWTToken
+from app.otlp import start_as_current_span_async
+from app.schemas.security import JWT, AuthzRequest, AuthzResponse
 
 if TYPE_CHECKING:
-    from boto3.resources.base import ServiceResource
+    from mypy_boto3_s3.service_resource import S3ServiceResource
 else:
-    ServiceResource = object
+    S3ServiceResource = object
 
-bearer_token = HTTPBearer(description="JWT Token")
+tracer = trace.get_tracer_provider().get_tracer(__name__)
+bearer_token = HTTPBearer(description="JWT Header")
 
 
-class LoginException(Exception):
-    def __init__(self, error_source: str):
-        self.error_source = error_source
+def get_rgw_admin() -> RGWAdmin:  # pragma: no cover
+    return rgw
 
 
-def get_rgw_admin() -> RGWAdmin:
-    return rgw  # pragma: no cover
+RGWAdminResource = Annotated[RGWAdmin, Depends(get_rgw_admin)]
 
 
-def get_s3_resource() -> ServiceResource:
+def get_s3_resource() -> S3ServiceResource:
     return s3_resource  # pragma: no cover
 
 
-async def get_db() -> AsyncGenerator:
+S3Resource = Annotated[S3ServiceResource, Depends(get_s3_resource)]
+
+
+async def get_db() -> AsyncGenerator[AsyncSession, None]:
     """
     Get a Session with the database.
 
@@ -48,16 +51,47 @@ async def get_db() -> AsyncGenerator:
 
     Returns
     -------
-    db : AsyncGenerator
+    db : AsyncGenerator[AsyncSession, None]
         Async session object with the database
     """
-    async with Session() as db:
+    async with get_async_session(
+        str(settings.SQLALCHEMY_DATABASE_ASYNC_URI), verbose=settings.SQLALCHEMY_VERBOSE_LOGGER
+    ) as db:
         yield db
 
 
-def decode_bearer_token(
+DBSession = Annotated[AsyncSession, Depends(get_db)]
+
+
+async def get_httpx_client(request: Request) -> AsyncClient:  # pragma: no cover
+    # Fetch open http client from the app
+    return request.app.requests_client
+
+
+HTTPXClient = Annotated[AsyncClient, Depends(get_httpx_client)]
+
+
+def get_decode_jwt_function() -> Callable[[str], Dict[str, str]]:  # pragma: no cover
+    """
+    Get function to decode and verify the JWT.
+
+    This will be injected into the function which will handle the JWT. With this approach, the function to decode and
+    verify the JWT can be overwritten during tests.
+
+    Returns
+    -------
+    decode : Callable[[str], Dict[str, str]]
+        Function to decode & verify the token. raw_token -> claims. Dependency Injection
+    """
+    return decode_token
+
+
+@start_as_current_span_async("decode_jwt", tracer=tracer)
+async def decode_bearer_token(
     token: HTTPAuthorizationCredentials = Depends(bearer_token),
-) -> JWTToken:
+    decode: Callable[[str], Dict[str, str]] = Depends(get_decode_jwt_function),
+    db: AsyncSession = Depends(get_db),
+) -> JWT:
     """
     Get the decoded JWT or reject request if it is not valid.
 
@@ -67,21 +101,69 @@ def decode_bearer_token(
     ----------
     token : fastapi.security.http.HTTPAuthorizationCredentials
         Bearer token sent with the HTTP request. Dependency Injection.
+    decode : Callable[[str], Dict[str, str]]
+        Function to decode & verify the token. raw_token -> claims. Dependency Injection
+    db : sqlalchemy.ext.asyncio.AsyncSession.
+        Async database session to perform query on. Dependency Injection.
 
     Returns
     -------
-    token : app.schemas.security.JWTToken
+    token : app.schemas.security.JWT
         The verified and decoded JWT.
     """
     try:
-        return JWTToken(**decode_token(token.credentials))
+        jwt = JWT(**decode(token.credentials), raw_token=token.credentials)
+        await get_current_user(jwt, db)  # make sure the user exists
+        return jwt
     except ExpiredTokenError:  # pragma: no cover
         raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="JWT Signature has expired")
     except (DecodeError, BadSignatureError):
         raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Malformed JWT")
 
 
-async def get_current_user(token: JWTToken = Depends(decode_bearer_token), db: AsyncSession = Depends(get_db)) -> User:
+class AuthorizationDependency:
+    """
+    Class to parameterize the authorization request with the resource to perform an operation on.
+    """
+
+    def __init__(self, resource: str):
+        """
+        Parameters
+        ----------
+        resource : str
+            Resource parameter for the authorization requests
+        """
+        self.resource = resource
+
+    def __call__(
+        self,
+        token: JWT = Depends(decode_bearer_token),
+        client: AsyncClient = Depends(get_httpx_client),
+    ) -> Callable[[str], Awaitable[AuthzResponse]]:
+        """
+        Get the function to request the authorization service with the resource, JWT and HTTP Client already injected.
+
+        Parameters
+        ----------
+        token : app.schemas.security.JWT
+            The verified and decoded JWT. Dependency Injection.
+        client : httpx.AsyncClient
+            HTTP Client with an open connection. Dependency Injection.
+
+        Returns
+        -------
+        authorization_function : Callable[[str], Awaitable[app.schemas.security.AuthzResponse]]
+            Async function which ask the Auth service for authorization. It takes the operation as the only input.
+        """
+
+        async def authorization_wrapper(operation: str) -> AuthzResponse:
+            params = AuthzRequest(operation=operation, resource=self.resource, uid=token.sub)
+            return await request_authorization(request_params=params, client=client)
+
+        return authorization_wrapper
+
+
+async def get_current_user(token: JWT = Depends(decode_bearer_token), db: AsyncSession = Depends(get_db)) -> User:
     """
     Get the current user from the database based on the JWT.
 
@@ -89,14 +171,14 @@ async def get_current_user(token: JWTToken = Depends(decode_bearer_token), db: A
 
     Parameters
     ----------
-    token : app.schemas.security.JWTToken
+    token : app.schemas.security.JWT
         The verified and decoded JWT.
     db : sqlalchemy.ext.asyncio.AsyncSession.
         Async database session to perform query on. Dependency Injection.
 
     Returns
     -------
-    user : app.models.user.User
+    user : clowmdb.models.User
         User associated with the JWT sent with the HTTP request.
     """
     user = await CRUDUser.get(db, token.sub)
@@ -105,12 +187,14 @@ async def get_current_user(token: JWTToken = Depends(decode_bearer_token), db: A
     raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found")
 
 
+CurrentUser = Annotated[User, Depends(get_current_user)]
+
+
 async def get_user_by_path_uid(
     uid: str = Path(
-        default=..., description="UID of a user", example="28c5353b8bb34984a8bd4169ba94c606", max_length=64
+        default=..., description="UID of a user", examples=["28c5353b8bb34984a8bd4169ba94c606"], max_length=64
     ),
     db: AsyncSession = Depends(get_db),
-    token: JWTToken = Depends(decode_bearer_token),
 ) -> User:
     """
     Get the user from the database with the given uid.
@@ -124,35 +208,29 @@ async def get_user_by_path_uid(
         The uid of a user. URL path parameter.
     db : sqlalchemy.ext.asyncio.AsyncSession.
         Async database session to perform query on. Dependency Injection.
-    token : app.schemas.security.JWTToken
-        Decoded JWT sent with the HTTP request.
 
     Returns
     -------
-    user : app.models.user.User
+    user : clowmdb.models.User
         User with the given uid.
 
     """
     user = await CRUDUser.get(db, uid)
     if user:
-        if user.uid == token.sub:
-            return user
-        else:
-            raise HTTPException(
-                status_code=status.HTTP_403_FORBIDDEN,
-                detail="A user can only access himself",
-            )
+        return user
     raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found")
 
 
+PathUser = Annotated[User, Depends(get_user_by_path_uid)]
+
+
 async def get_current_bucket(
-    bucket_name: str = Path(..., description="Name of bucket", example="test-bucket", max_length=63, min_length=3),
+    bucket_name: str = Path(..., description="Name of bucket", examples=["test-bucket"], max_length=63, min_length=3),
     db: AsyncSession = Depends(get_db),
-    current_user: User = Depends(get_current_user),
 ) -> Bucket:
     """
     Get the Bucket from the database based on the name in the path.
-    Reject the request if user has no READ permission for this bucket.
+    Reject the request if user has no permission for this bucket.
 
     FastAPI Dependency Injection Function
 
@@ -162,81 +240,16 @@ async def get_current_bucket(
         Name of a bucket. URL Path Parameter.
     db : sqlalchemy.ext.asyncio.AsyncSession.
         Async database session to perform query on. Dependency Injection.
-    current_user : app.models.user.User
-        User associated with the JWT sent with the HTTP request. Dependency Injection
 
     Returns
     -------
-    bucket : app.models.bucket.Bucket
+    bucket : clowmdb.models.Bucket
         Bucket with the given name.
     """
     bucket = await CRUDBucket.get(db, bucket_name)
     if bucket is None:
         raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Bucket not found")
-    elif not bucket.public and not await CRUDBucketPermission.check_permission(db, bucket_name, current_user.uid):
-        raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="No rights for this bucket")
     return bucket
 
 
-async def get_authorized_user_for_permission(
-    bucket: Bucket = Depends(get_current_bucket),
-    uid: str = Path(
-        default=..., description="UID of a user", example="28c5353b8bb34984a8bd4169ba94c606", max_length=64
-    ),
-    db: AsyncSession = Depends(get_db),
-    current_user: User = Depends(get_current_user),
-) -> User:
-    """
-    Get the user for viewing and deleting bucket permissions.\n
-    Only the owner of a bucket and grantee can do this.
-
-    Parameters
-    ----------
-    bucket : app.models.bucket.Bucket
-        Bucket with the name provided in the URL path. Dependency Injection.
-    uid : str
-        The uid of a user. URL path parameter.
-    db : sqlalchemy.ext.asyncio.AsyncSession.
-        Async database session to perform query on. Dependency Injection.
-    current_user : app.models.user.User
-        Current user. Dependency Injection.
-
-    Returns
-    -------
-    user : app.models.user.User
-        Authorized user for bucket permission. Dependency Injection.
-    """
-    user = await CRUDUser.get(db, uid)
-    if user is None:
-        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found")
-    elif current_user != user and current_user.uid != bucket.owner_id:
-        raise HTTPException(
-            status.HTTP_403_FORBIDDEN, detail="Only the owner or the grantee can access a bucket permission"
-        )
-    return user
-
-
-async def get_userinfo_from_access_token(request: Request) -> dict[str, Any]:  # pragma: no cover
-    """
-    Get the userinfo from the OAuth2 userinfo endpoint with the access token.
-
-    Parameters
-    ----------
-    request : starlette.requests.Request
-        Raw Request object.
-
-    Returns
-    -------
-    userinfo : dict[str, Any]
-        Info about the corresponding user.
-    """
-    try:
-        if "error" in request.query_params.keys():
-            # if there is an error in the login flow, like a canceld login request, then notify the client
-            raise LoginException(error_source=request.query_params["error"])
-        claims = await oauth.lifescience.authorize_access_token(request)
-        # ID token doesn't have all necessary information, call userinfo endpoint
-        return await oauth.lifescience.userinfo(token=claims)
-    except OAuthError:
-        # if there is an error in the oauth flow, like an expired token, then notify the client
-        raise LoginException(error_source="oidc")
+CurrentBucket = Annotated[Bucket, Depends(get_current_bucket)]
diff --git a/app/api/endpoints/bucket_permissions.py b/app/api/endpoints/bucket_permissions.py
index 176647598f6325da8ee48c0327735df7763c3ce9..7ab46c1823bf622c8dcee433a5e8689c684832c1 100644
--- a/app/api/endpoints/bucket_permissions.py
+++ b/app/api/endpoints/bucket_permissions.py
@@ -1,31 +1,32 @@
 import json
-from typing import TYPE_CHECKING
+from typing import Annotated, Any, Awaitable, Callable, List, Optional
 
-from fastapi import APIRouter, Body, Depends, HTTPException, status
-from sqlalchemy.ext.asyncio import AsyncSession
+from clowmdb.models import BucketPermission
+from fastapi import APIRouter, Body, Depends, HTTPException, Query, status
+from opentelemetry import trace
 
 from app.api.dependencies import (
-    get_authorized_user_for_permission,
+    AuthorizationDependency,
+    CurrentBucket,
+    CurrentUser,
+    DBSession,
+    PathUser,
+    S3Resource,
     get_current_bucket,
-    get_current_user,
-    get_db,
-    get_s3_resource,
     get_user_by_path_uid,
 )
-from app.crud.crud_bucket_permission import CRUDBucketPermission, DuplicateError
-from app.crud.crud_user import CRUDUser
-from app.models.bucket import Bucket as BucketDB
-from app.models.user import User as UserDB
+from app.ceph.s3 import get_s3_bucket_policy, put_s3_bucket_policy
+from app.crud import DuplicateError
+from app.crud.crud_bucket_permission import CRUDBucketPermission
+from app.otlp import start_as_current_span_async
 from app.schemas.bucket_permission import BucketPermissionIn as PermissionSchemaIn
 from app.schemas.bucket_permission import BucketPermissionOut as PermissionSchemaOut
 from app.schemas.bucket_permission import BucketPermissionParameters as PermissionParametersSchema
 
-router = APIRouter(prefix="/permissions", tags=["BucketPermissions"])
-
-if TYPE_CHECKING:
-    from mypy_boto3_s3.service_resource import S3ServiceResource
-else:
-    S3ServiceResource = object
+router = APIRouter(prefix="/permissions", tags=["BucketPermission"])
+permission_authorization = AuthorizationDependency(resource="bucket_permission")
+Authorization = Annotated[Callable[[str], Awaitable[Any]], Depends(permission_authorization)]
+tracer = trace.get_tracer_provider().get_tracer(__name__)
 
 
 @router.get(
@@ -34,29 +35,41 @@ else:
     summary="Get permission for bucket and user combination.",
     response_model_exclude_none=True,
 )
+@start_as_current_span_async("api_get_bucket_permission", tracer=tracer)
 async def get_permission_for_bucket(
-    bucket: BucketDB = Depends(get_current_bucket),
-    db: AsyncSession = Depends(get_db),
-    user: UserDB = Depends(get_authorized_user_for_permission),
+    bucket: CurrentBucket,
+    db: DBSession,
+    current_user: CurrentUser,
+    authorization: Authorization,
+    user: PathUser,
 ) -> PermissionSchemaOut:
     """
     Get the bucket permissions for the specific combination of bucket and user.\n
-    The owner of the bucket and the grantee of the permission can view it.
+    The owner of the bucket and the grantee of the permission can view it.\n
+    Permission "bucket_permission:read" required if current user is the target or owner of the bucket permission,
+    otherwise "bucket_permission:read_any" required.
     \f
     Parameters
     ----------
-    bucket : app.models.bucket.Bucket
+    bucket : clowmdb.models.Bucket
         Bucket with the name provided in the URL path. Dependency Injection.
     db : sqlalchemy.ext.asyncio.AsyncSession.
         Async database session to perform query on. Dependency Injection.
-    user : app.models.user.User
+    user : clowmdb.models.User
         User with the uid in the URL. Dependency Injection.
+    authorization : Callable[[str], Awaitable[Any]]
+        Async function to ask the auth service for authorization. Dependency Injection.
+    current_user : clowmdb.models.User
+        Current user who will be the owner of the newly created bucket. Dependency Injection.
 
     Returns
     -------
     permissions : app.schemas.bucket_permission.BucketPermissionOut
         Permission for this bucket and user combination.
     """
+    trace.get_current_span().set_attributes({"bucket_name": bucket.name, "uid": user.uid})
+    rbac_operation = "read" if user == current_user or current_user.uid == bucket.owner_id else "read_any"
+    await authorization(rbac_operation)
     bucket_permission = await CRUDBucketPermission.get(db, bucket.name, user.uid)
     if bucket_permission:
         return PermissionSchemaOut.from_db_model(
@@ -73,107 +86,173 @@ async def get_permission_for_bucket(
     status_code=status.HTTP_204_NO_CONTENT,
     summary="Delete a bucket permission",
 )
-async def delete_permission_for_bucket(
-    bucket: BucketDB = Depends(get_current_bucket),
-    db: AsyncSession = Depends(get_db),
-    user: UserDB = Depends(get_authorized_user_for_permission),
-    s3: S3ServiceResource = Depends(get_s3_resource),
+@start_as_current_span_async("api_delete_bucket_permission", tracer=tracer)
+async def delete_permission(
+    bucket: CurrentBucket,
+    db: DBSession,
+    s3: S3Resource,
+    current_user: CurrentUser,
+    authorization: Authorization,
+    user: PathUser,
 ) -> None:
     """
     Delete the bucket permissions for the specific combination of bucket and user.\n
-    The owner of the bucket and the grantee of the permission can delete it.
+    The owner of the bucket and the grantee of the permission can delete it.\n
+    Permission "bucket_permission:delete" required if current user is the target or owner of the bucket permission,
+    otherwise "bucket_permission:delete_any" required.
     \f
     Parameters
     ----------
-    bucket : app.models.bucket.Bucket
+    bucket : clowmdb.models.Bucket
         Bucket with the name provided in the URL path. Dependency Injection.
     db : sqlalchemy.ext.asyncio.AsyncSession.
         Async database session to perform query on. Dependency Injection.
-    user : app.models.user.User
+    user : clowmdb.models.User
         User with the uid in the URL. Dependency Injection.
     s3 : boto3_type_annotations.s3.ServiceResource
         S3 Service to perform operations on buckets in Ceph. Dependency Injection.
+    authorization : Callable[[str], Awaitable[Any]]
+        Async function to ask the auth service for authorization. Dependency Injection.
+    current_user : clowmdb.models.User
+        Current user who will be the owner of the newly created bucket. Dependency Injection.
 
     Returns
     -------
     permissions : app.schemas.bucket_permission.BucketPermissionOut
         Permission for this bucket and user combination.
     """
+    trace.get_current_span().set_attributes({"bucket_name": bucket.name, "uid": user.uid})
+    rbac_operation = "delete" if user == current_user or current_user.uid == bucket.owner_id else "delete_any"
+    await authorization(rbac_operation)
     bucket_permission = await CRUDBucketPermission.get(db, bucket.name, user.uid)
     if bucket_permission is None:
         raise HTTPException(
             status.HTTP_404_NOT_FOUND,
             detail=f"Permission for combination of bucket={bucket.name} and user={user.uid} doesn't exists",
         )
-    await CRUDBucketPermission.delete(db, bucket_permission)
+    await CRUDBucketPermission.delete(db, bucket_name=bucket_permission.bucket_name, uid=bucket_permission.user_id)
     bucket_permission_schema = PermissionSchemaOut.from_db_model(bucket_permission, user.uid, user.display_name)
-    s3_policy = s3.Bucket(bucket_permission_schema.bucket_name).Policy()
+    s3_policy = get_s3_bucket_policy(s3, bucket_name=bucket_permission_schema.bucket_name)
     policy = json.loads(s3_policy.policy)
     policy["Statement"] = [
         stmt for stmt in policy["Statement"] if stmt["Sid"] != bucket_permission_schema.to_hash(user.uid)
     ]
-    s3_policy.put(Policy=json.dumps(policy))
+    put_s3_bucket_policy(s3, bucket_name=bucket_permission_schema.bucket_name, policy=json.dumps(policy))
 
 
 @router.get(
     "/bucket/{bucket_name}",
-    response_model=list[PermissionSchemaOut],
+    response_model=List[PermissionSchemaOut],
     summary="Get all permissions for a bucket.",
     response_model_exclude_none=True,
 )
+@start_as_current_span_async("api_list_bucket_permission_for_bucket", tracer=tracer)
 async def list_permissions_per_bucket(
-    bucket: BucketDB = Depends(get_current_bucket),
-    db: AsyncSession = Depends(get_db),
-    current_user: UserDB = Depends(get_current_user),
-) -> list[PermissionSchemaOut]:
+    bucket: CurrentBucket,
+    db: DBSession,
+    current_user: CurrentUser,
+    authorization: Authorization,
+    permission_types: Optional[List[BucketPermission.Permission]] = Query(
+        None, description="Type of Bucket Permissions to fetch"
+    ),
+    permission_status: Optional[CRUDBucketPermission.PermissionStatus] = Query(
+        None, description="Status of Bucket Permissions to fetch"
+    ),
+) -> List[PermissionSchemaOut]:
     """
-    List all the bucket permissions for the given bucket.
+    List all the bucket permissions for the given bucket.\n
+    Permission "bucket_permission:read" required if current user is owner of the bucket,
+    otherwise "bucket_permission:read_any" required.
     \f
     Parameters
     ----------
-    bucket : app.models.bucket.Bucket
+    permission_types : List[clowmdb.models.BucketPermission.Permission] | None, default None
+        Type of Bucket Permissions to fetch. Query Parameter
+    permission_status : app.crud.crud_bucket_permission.CRUDBucketPermission.PermissionStatus | None, default None
+        Status of Bucket Permissions to fetch. Query Parameter.
+    bucket : clowmdb.models.Bucket
         Bucket with the name provided in the URL path. Dependency Injection.
     db : sqlalchemy.ext.asyncio.AsyncSession.
         Async database session to perform query on. Dependency Injection.
-    current_user : app.models.user.User
-        Current user. Dependency Injection.
+    authorization : Callable[[str], Awaitable[Any]]
+        Async function to ask the auth service for authorization. Dependency Injection.
+    current_user : clowmdb.models.User
+        Current user who will be the owner of the newly created bucket. Dependency Injection.
 
     Returns
     -------
-    permissions : list[app.schemas.bucket_permission.BucketPermissionOut]
+    permissions : List[app.schemas.bucket_permission.BucketPermissionOut]
         List of all permissions for this bucket.
     """
-    if not await CRUDBucketPermission.check_permission(db, bucket.name, current_user.uid, only_own=True):
-        raise HTTPException(status.HTTP_403_FORBIDDEN, "You can only view your own bucket permissions")
-    bucket_permissions = await CRUDBucketPermission.get_permissions_for_bucket(db, bucket.name)
+    current_span = trace.get_current_span()
+    current_span.set_attribute("bucket_name", bucket.name)
+    if permission_types is not None and len(permission_types) > 0:  # pragma: no cover
+        current_span.set_attribute("permission_types", [ptype.name for ptype in permission_types])
+    if permission_status is not None:  # pragma: no cover
+        current_span.set_attribute("permission_status", permission_status.name)
+    rbac_operation = "list_bucket" if bucket.owner_id == current_user.uid else "list_all"
+    await authorization(rbac_operation)
+    bucket_permissions = await CRUDBucketPermission.list(
+        db, bucket_name=bucket.name, permission_types=permission_types, permission_status=permission_status
+    )
     return [PermissionSchemaOut.from_db_model(p) for p in bucket_permissions]
 
 
 @router.get(
     "/user/{uid}",
-    response_model=list[PermissionSchemaOut],
+    response_model=List[PermissionSchemaOut],
     summary="Get all permissions for a user.",
     response_model_exclude_none=True,
 )
+@start_as_current_span_async("api_list_bucket_permission_for_user", tracer=tracer)
 async def list_permissions_per_user(
-    user: UserDB = Depends(get_user_by_path_uid), db: AsyncSession = Depends(get_db)
-) -> list[PermissionSchemaOut]:
+    db: DBSession,
+    current_user: CurrentUser,
+    authorization: Authorization,
+    user: PathUser,
+    permission_types: Optional[List[BucketPermission.Permission]] = Query(
+        None, description="Type of Bucket Permissions to fetch"
+    ),
+    permission_status: Optional[CRUDBucketPermission.PermissionStatus] = Query(
+        None, description="Status of Bucket Permissions to fetch"
+    ),
+) -> List[PermissionSchemaOut]:
     """
-    List all the bucket permissions for the given user.
+    List all the bucket permissions for the given user.\n
+    Permission "bucket_permission:read" required if current user is the target the bucket permission,
+    otherwise "bucket_permission:read_any" required.
     \f
     Parameters
     ----------
-    user :  : app.models.user.User
+    permission_types : List[clowmdb.models.BucketPermission.Permission] | None, default None
+        Type of Bucket Permissions to fetch. Query Parameter
+    permission_status : app.crud.crud_bucket_permission.CRUDBucketPermission.PermissionStatus | None, default None
+        Status of Bucket Permissions to fetch. Query Parameter.
+    user : clowmdb.models.User
         User with given uid. Dependency Injection.
     db : sqlalchemy.ext.asyncio.AsyncSession.
         Async database session to perform query on. Dependency Injection.
+    authorization : Callable[[str], Awaitable[Any]]
+        Async function to ask the auth service for authorization. Dependency Injection.
+    current_user : clowmdb.models.User
+        Current user who will be the owner of the newly created bucket. Dependency Injection.
 
     Returns
     -------
-    permissions : list[app.schemas.bucket_permission.BucketPermissionOut]
+    permissions : List[app.schemas.bucket_permission.BucketPermissionOut]
         List of all permissions for this user.
     """
-    bucket_permissions = await CRUDBucketPermission.get_permissions_for_user(db, user.uid)
+    current_span = trace.get_current_span()
+    current_span.set_attribute("uid", user.uid)
+    if permission_types is not None and len(permission_types) > 0:  # pragma: no cover
+        current_span.set_attribute("permission_types", [ptype.name for ptype in permission_types])
+    if permission_status is not None:  # pragma: no cover
+        current_span.set_attribute("permission_status", permission_status.name)
+    rbac_operation = "list_user" if user == current_user else "list_all"
+    await authorization(rbac_operation)
+    bucket_permissions = await CRUDBucketPermission.list(
+        db, uid=user.uid, permission_types=permission_types, permission_status=permission_status
+    )
     return [
         PermissionSchemaOut.from_db_model(p, uid=user.uid, grantee_display_name=user.display_name)
         for p in bucket_permissions
@@ -181,20 +260,23 @@ async def list_permissions_per_user(
 
 
 @router.post(
-    "/",
+    "",
     response_model=PermissionSchemaOut,
     status_code=status.HTTP_201_CREATED,
     summary="Create a permission.",
     response_model_exclude_none=True,
 )
+@start_as_current_span_async("api_create_bucket_permission", tracer=tracer)
 async def create_permission(
+    db: DBSession,
+    current_user: CurrentUser,
+    s3: S3Resource,
+    authorization: Authorization,
     permission: PermissionSchemaIn = Body(..., description="Permission to create"),
-    db: AsyncSession = Depends(get_db),
-    current_user: UserDB = Depends(get_current_user),
-    s3: S3ServiceResource = Depends(get_s3_resource),
 ) -> PermissionSchemaOut:
     """
-    Create a permission for a bucket and user.
+    Create a permission for a bucket and user.\n
+    Permission "bucket_permission:create" required.
     \f
     Parameters
     ----------
@@ -202,39 +284,44 @@ async def create_permission(
         Information about the permission which should be created. HTTP Body parameter.
     db : sqlalchemy.ext.asyncio.AsyncSession.
         Async database session to perform query on. Dependency Injection.
-    current_user : app.models.user.User
+    current_user : clowmdb.models.User
         Current user. Dependency Injection.
     s3 : boto3_type_annotations.s3.ServiceResource
         S3 Service to perform operations on buckets in Ceph. Dependency Injection.
+    authorization : Callable[[str], Awaitable[Any]]
+        Async function to ask the auth service for authorization. Dependency Injection.
 
     Returns
     -------
     permissions : app.schemas.bucket_permission.BucketPermissionOut
         Newly created permission.
     """
-    await get_current_bucket(permission.bucket_name, db=db, current_user=current_user)
+    current_span = trace.get_current_span()
+    current_span.set_attributes({"uid": permission.uid, "bucket_name": permission.bucket_name})
+    await authorization("create")
+    target_bucket = await get_current_bucket(permission.bucket_name, db=db)  # Check if the target bucket exists
+    if target_bucket.owner_id != current_user.uid:
+        raise HTTPException(status.HTTP_403_FORBIDDEN, detail="Action forbidden.")
+    if target_bucket.owner_constraint is not None:
+        raise HTTPException(status.HTTP_403_FORBIDDEN, detail="Initial Buckets can be target of Bucket Permissions.")
+    grantee = await get_user_by_path_uid(permission.uid, db)  # Check if target user exists
     try:
         permission_db = await CRUDBucketPermission.create(db, permission)
-        grantee = await CRUDUser.get(db, permission.uid)
-        if grantee is None:  # pragma: no cover
-            raise KeyError()
-    except ValueError:
+    except ValueError as e:
+        current_span.record_exception(e)
         raise HTTPException(
             status.HTTP_400_BAD_REQUEST, detail="The owner of the bucket can't get any more permissions"
         )
-    except DuplicateError:
+    except DuplicateError as e:
+        current_span.record_exception(e)
         raise HTTPException(
             status.HTTP_400_BAD_REQUEST,
             detail=f"Permission for combination of bucket={permission.bucket_name} and user={permission.uid} already exists",  # noqa:E501
         )
-    except KeyError:
-        raise HTTPException(status.HTTP_404_NOT_FOUND, detail=f"User with uid={permission.uid} not found")
-    s3_policy = s3.Bucket(permission.bucket_name).Policy()
-    old_policy = s3_policy.policy
-    json_policy = json.loads(old_policy)
+    s3_policy = get_s3_bucket_policy(s3, bucket_name=permission.bucket_name)
+    json_policy = json.loads(s3_policy.policy)
     json_policy["Statement"] += permission.map_to_bucket_policy_statement(permission_db.user_id)
-    new_policy = json.dumps(json_policy)
-    s3_policy.put(Policy=new_policy)
+    put_s3_bucket_policy(s3, bucket_name=permission.bucket_name, policy=json.dumps(json_policy))
 
     return PermissionSchemaOut.from_db_model(permission_db, uid=grantee.uid, grantee_display_name=grantee.display_name)
 
@@ -246,39 +333,47 @@ async def create_permission(
     summary="Update a bucket permission",
     response_model_exclude_none=True,
 )
+@start_as_current_span_async("api_create_bucket_permission", tracer=tracer)
 async def update_permission(
+    bucket: CurrentBucket,
+    db: DBSession,
+    current_user: CurrentUser,
+    s3: S3Resource,
+    authorization: Authorization,
+    user: PathUser,
     permission_parameters: PermissionParametersSchema = Body(..., description="Permission to create"),
-    user: UserDB = Depends(get_authorized_user_for_permission),
-    bucket: BucketDB = Depends(get_current_bucket),
-    db: AsyncSession = Depends(get_db),
-    current_user: UserDB = Depends(get_current_user),
-    s3: S3ServiceResource = Depends(get_s3_resource),
 ) -> PermissionSchemaOut:
     """
-    Update a permission for a bucket and user.
+    Update a permission for a bucket and user.\n
+    Permission "bucket_permission:read" required if current user is the target the bucket permission,
+    otherwise "bucket_permission:update" required.
     \f
     Parameters
     ----------
     permission_parameters : app.schemas.bucket_permission.BucketPermissionOut
         Information about the permission which should be updated. HTTP Body parameter.
-    user : app.models.user.User
+    user : clowmdb.models.User
         User with the uid in the URL. Dependency Injection.
-    bucket : app.models.bucket.Bucket
+    bucket : clowmdb.models.Bucket
         Bucket with the name provided in the URL path. Dependency Injection.
     db : sqlalchemy.ext.asyncio.AsyncSession.
         Async database session to perform query on. Dependency Injection.
-    current_user : app.models.user.User
+    current_user : clowmdb.models.User
         Current user. Dependency Injection.
     s3 : boto3_type_annotations.s3.ServiceResource
         S3 Service to perform operations on buckets in Ceph. Dependency Injection.
+    authorization : Callable[[str], Awaitable[Any]]
+        Async function to ask the auth service for authorization. Dependency Injection.
 
     Returns
     -------
     permissions : app.schemas.bucket_permission.BucketPermissionOut
         Updated permission.
     """
-    if not await CRUDBucketPermission.check_permission(db, bucket.name, current_user.uid, only_own=True):
-        raise HTTPException(status.HTTP_403_FORBIDDEN, "You can only modify permissions on your own bucket")
+    trace.get_current_span().set_attributes({"uid": user.uid, "bucket_name": bucket.name})
+    await authorization("update")
+    if bucket.owner_id != current_user.uid:
+        raise HTTPException(status.HTTP_403_FORBIDDEN, "Action forbidden")
     bucket_permission = await CRUDBucketPermission.get(db, bucket.name, user.uid)
 
     if bucket_permission is None:
@@ -288,11 +383,11 @@ async def update_permission(
         )
     updated_permission = await CRUDBucketPermission.update_permission(db, bucket_permission, permission_parameters)
     updated_permission_schema = PermissionSchemaOut.from_db_model(updated_permission)
-    s3_policy = s3.Bucket(updated_permission_schema.bucket_name).Policy()
+    s3_policy = get_s3_bucket_policy(s3, bucket_name=bucket.name)
     policy = json.loads(s3_policy.policy)
     policy["Statement"] = [
         stmt for stmt in policy["Statement"] if stmt["Sid"] != updated_permission_schema.to_hash(user.uid)
     ]
     policy["Statement"] += updated_permission_schema.map_to_bucket_policy_statement(updated_permission.user_id)
-    s3_policy.put(Policy=json.dumps(policy))
+    put_s3_bucket_policy(s3, bucket_name=bucket.name, policy=json.dumps(policy))
     return updated_permission_schema
diff --git a/app/api/endpoints/buckets.py b/app/api/endpoints/buckets.py
index 3de9e27ed164038d71fa05553211e39c197ef577..eaad0ef07c1eb9e2f4e7d19432a3e7b4849961fc 100644
--- a/app/api/endpoints/buckets.py
+++ b/app/api/endpoints/buckets.py
@@ -1,114 +1,178 @@
 import json
 from functools import reduce
-from typing import TYPE_CHECKING
+from typing import TYPE_CHECKING, Annotated, Any, Awaitable, Callable, List, Optional
 
 from botocore.exceptions import ClientError
-from fastapi import APIRouter, Depends, HTTPException, Path, Query, status
-from sqlalchemy.ext.asyncio import AsyncSession
+from clowmdb.models import Bucket
+from fastapi import APIRouter, Depends, HTTPException, Query, status
+from opentelemetry import trace
 
-from app.api.dependencies import get_current_bucket, get_current_user, get_db, get_s3_resource
+from app.api.dependencies import AuthorizationDependency, CurrentBucket, CurrentUser, DBSession, S3Resource
+from app.ceph.s3 import get_s3_bucket_objects, put_s3_bucket_policy
 from app.core.config import settings
+from app.crud import DuplicateError
 from app.crud.crud_bucket import CRUDBucket
 from app.crud.crud_bucket_permission import CRUDBucketPermission
-from app.models.bucket import Bucket as BucketDB
-from app.models.user import User
+from app.otlp import start_as_current_span_async
 from app.schemas.bucket import BucketIn as BucketInSchema
 from app.schemas.bucket import BucketOut as BucketOutSchema
-from app.schemas.bucket import S3ObjectMetaInformation
 
 if TYPE_CHECKING:
-    from mypy_boto3_s3.service_resource import ObjectSummary, S3ServiceResource
+    from mypy_boto3_s3.service_resource import ObjectSummary
 else:
-    S3ServiceResource = object
     ObjectSummary = object
 
 router = APIRouter(prefix="/buckets", tags=["Bucket"])
+bucket_authorization = AuthorizationDependency(resource="bucket")
+Authorization = Annotated[Callable[[str], Awaitable[Any]], Depends(bucket_authorization)]
+tracer = trace.get_tracer_provider().get_tracer(__name__)
+
+cors_rule = {
+    "CORSRules": [
+        {
+            "ID": "websiteaccess",
+            "AllowedHeaders": [
+                "amz-sdk-invocation-id",
+                "amz-sdk-request",
+                "authorization",
+                "content-type",
+                "x-amz-content-sha256",
+                "x-amz-copy-source",
+                "x-amz-date",
+                "x-amz-user-agent",
+                "content-md5",
+            ],
+            "AllowedMethods": ["GET", "PUT", "POST", "DELETE", "HEAD"],
+            "AllowedOrigins": [str(settings.CLOWM_URL)[:-1]],
+            "ExposeHeaders": [
+                "Etag",
+            ],
+            "MaxAgeSeconds": 120,
+        },
+    ]
+}
 
 
-@router.get("/", response_model=list[BucketOutSchema], summary="List buckets of user")
+@router.get("", response_model=List[BucketOutSchema], summary="List buckets of user")
+@start_as_current_span_async("api_list_buckets", tracer=tracer)
 async def list_buckets(
-    user: User = Depends(get_current_user),
-    db: AsyncSession = Depends(get_db),
-    s3: S3ServiceResource = Depends(get_s3_resource),
-) -> list[BucketOutSchema]:
+    db: DBSession,
+    s3: S3Resource,
+    current_user: CurrentUser,
+    authorization: Authorization,
+    user: Optional[str] = Query(
+        None,
+        description="UID of the user for whom to fetch the buckets for. Permission 'bucket:read_any' required if current user is not the target.",  # noqa:E501
+    ),
+    bucket_type: CRUDBucket.BucketType = Query(
+        CRUDBucket.BucketType.ALL, description="Type of the bucket to get. Ignored when `user` parameter not set"
+    ),
+) -> List[BucketOutSchema]:
     """
-    List the buckets of the current user where the user has READ permissions for.
+    List all the buckets in the system or of the desired user where the user has READ permissions for.\n
+    Permission "bucket:read" required.
     \f
     Parameters
     ----------
-    user : app.models.user.User
+    user : clowmdb.models.User
         User for which to retrieve the buckets. Dependency Injection.
+    bucket_type : app.crud.crud_bucket.CRUDBucket.BucketType, default BucketType.ALL
+        Type of the bucket to get. Query Parameter.
     db : sqlalchemy.ext.asyncio.AsyncSession.
         Async database session to perform query on. Dependency Injection.
+    current_user : clowmdb.models.User
+        Current user who will be the owner of the newly created bucket. Dependency Injection.
     s3 : boto3_type_annotations.s3.ServiceResource
         S3 Service to perform operations on buckets in Ceph. Dependency Injection.
-
+    authorization : Callable[[str], Awaitable[Any]]
+        Async function to ask the auth service for authorization. Dependency Injection.
     Returns
     -------
-    buckets : list[app.schemas.bucket.BucketOut]
+    buckets : List[app.schemas.bucket.BucketOut]
         All the buckets for which the user has READ permissions.
     """
-    buckets = [
-        BucketOutSchema(
+    current_span = trace.get_current_span()
+    if user is not None:  # pragma: no cover
+        current_span.set_attribute("uid", user)
+    current_span.set_attribute("bucket_type", bucket_type.name)
+    await authorization("list_all" if user is None or current_user.uid != user else "list")
+    if user is None:
+        buckets = await CRUDBucket.get_all(db)
+    else:
+        buckets = await CRUDBucket.get_for_user(db, user, bucket_type)
+
+    def map_buckets(bucket: Bucket) -> BucketOutSchema:
+        # define function to fetch objects only one time for each bucket
+        objects = list(get_s3_bucket_objects(s3, bucket.name))
+        return BucketOutSchema(
             **{
                 "description": bucket.description,
                 "name": bucket.name,
-                "created_at": s3.Bucket(name=bucket.name).creation_date,
-                "owner": bucket.owner.uid,
-                "num_objects": sum(
-                    1 for obj in s3.Bucket(name=bucket.name).objects.all() if not obj.key.endswith(".s3keep")
-                ),
-                "size": reduce(lambda x, y: x + y.size, list(s3.Bucket(name=bucket.name).objects.all()), 0),
+                "created_at": bucket.created_at,
+                "owner": bucket.owner_id,
+                "num_objects": sum(1 for obj in objects if not obj.key.endswith("/")),
+                "size": reduce(lambda x, y: x + y.size, objects, 0),
+                "owner_constraint": bucket.owner_constraint,
             }
         )
-        for bucket in await CRUDBucket.get_for_user(db, user.uid)
-    ]
 
-    return buckets
+    return list(map(map_buckets, buckets))
 
 
 @router.post(
-    "/",
+    "",
     response_model=BucketOutSchema,
     status_code=status.HTTP_201_CREATED,
     summary="Create a bucket for the current user",
 )
+@start_as_current_span_async("api_create_bucket", tracer=tracer)
 async def create_bucket(
     bucket: BucketInSchema,
-    user: User = Depends(get_current_user),
-    db: AsyncSession = Depends(get_db),
-    s3: S3ServiceResource = Depends(get_s3_resource),
+    current_user: CurrentUser,
+    db: DBSession,
+    s3: S3Resource,
+    authorization: Authorization,
 ) -> BucketOutSchema:
     """
     Create a bucket for the current user.\n
     The name of the bucket has some constraints.
-    For more information see the [Ceph documentation](https://docs.ceph.com/en/quincy/radosgw/s3/bucketops/#constraints)
+    For more information see the
+    [Ceph documentation](https://docs.ceph.com/en/quincy/radosgw/s3/bucketops/#constraints)\n
+    Permission "bucket:create" required.
     \f
     Parameters
     ----------
     bucket : app.schemas.bucket.BucketIn
         Information about the bucket to create. HTTP Body
-    user : app.models.user.User
+    current_user : clowmdb.models.User
         Current user who will be the owner of the newly created bucket. Dependency Injection.
     db : sqlalchemy.ext.asyncio.AsyncSession.
         Async database session to perform query on. Dependency Injection.
     s3 : boto3_type_annotations.s3.ServiceResource
         S3 Service to perform operations on buckets in Ceph. Dependency Injection.
+    authorization : Callable[[str], Awaitable[Any]]
+        Async function to ask the auth service for authorization. Dependency Injection.
 
     Returns
     -------
     bucket : app.schemas.bucket.BucketOut
         The newly created bucket.
     """
-
-    db_bucket = await CRUDBucket.create(db, bucket, user.uid)
-    if db_bucket is None:
+    current_span = trace.get_current_span()
+    current_span.set_attribute("bucket_name", bucket.name)
+    await authorization("create")
+    try:
+        db_bucket = await CRUDBucket.create(db, bucket, current_user.uid)
+    except DuplicateError as e:
+        current_span.record_exception(e)
         raise HTTPException(
             status_code=status.HTTP_400_BAD_REQUEST,
             detail="Bucket name is already taken",
         )
     s3_bucket = s3.Bucket(db_bucket.name)
-    s3_bucket.create()
+    with tracer.start_as_current_span("s3_create_bucket") as span:
+        span.set_attribute("bucket_name", db_bucket.name)
+        s3_bucket.create()
     # Add basic permission to the user for getting, creating and deleting objects in the bucket.
     bucket_policy = json.dumps(
         {
@@ -117,26 +181,29 @@ async def create_bucket(
                 {
                     "Sid": "ProxyOwnerPerm",
                     "Effect": "Allow",
-                    "Principal": {"AWS": [f"arn:aws:iam:::user/{settings.CEPH_USERNAME}"]},
+                    "Principal": {"AWS": [f"arn:aws:iam:::user/{settings.BUCKET_CEPH_USERNAME}"]},
                     "Action": ["s3:GetObject"],
                     "Resource": [f"arn:aws:s3:::{db_bucket.name}/*"],
                 },
                 {
                     "Sid": "PseudoOwnerPerm",
                     "Effect": "Allow",
-                    "Principal": {"AWS": [f"arn:aws:iam:::user/{user.uid}"]},
+                    "Principal": {"AWS": [f"arn:aws:iam:::user/{current_user.uid}"]},
                     "Action": ["s3:GetObject", "s3:DeleteObject", "s3:PutObject", "s3:ListBucket"],
                     "Resource": [f"arn:aws:s3:::{db_bucket.name}/*", f"arn:aws:s3:::{db_bucket.name}"],
                 },
             ],
         }
     )
-    s3_bucket.Policy().put(Policy=bucket_policy)
+    put_s3_bucket_policy(s3, bucket_name=bucket.name, policy=bucket_policy)
+    with tracer.start_as_current_span("s3_put_bucket_cors_rules") as span:
+        span.set_attribute("bucket_name", db_bucket.name)
+        s3_bucket.Cors().put(CORSConfiguration=cors_rule)  # type: ignore[arg-type]
     return BucketOutSchema(
         **{
             "description": db_bucket.description,
             "name": db_bucket.name,
-            "created_at": s3.Bucket(name=db_bucket.name).creation_date,
+            "created_at": db_bucket.created_at,
             "owner": db_bucket.owner.uid,
             "num_objects": 0,
             "size": 0,
@@ -145,169 +212,101 @@ async def create_bucket(
 
 
 @router.get("/{bucket_name}", response_model=BucketOutSchema, summary="Get a bucket by its name")
+@start_as_current_span_async("api_get_bucket", tracer=tracer)
 async def get_bucket(
-    bucket: BucketDB = Depends(get_current_bucket), s3: S3ServiceResource = Depends(get_s3_resource)
+    bucket: CurrentBucket,
+    s3: S3Resource,
+    current_user: CurrentUser,
+    authorization: Authorization,
+    db: DBSession,
 ) -> BucketOutSchema:
     """
-    Get a bucket by its name if the current user has READ permissions for the bucket.
+    Get a bucket by its name if the current user has READ permissions for the bucket.\n
+    Permission "bucket:read" required if the current user is the owner of the bucket,
+    otherwise "bucket:read_any" required.
     \f
     Parameters
     ----------
-    bucket : app.models.bucket.Bucket
+    bucket : clowmdb.models.Bucket
         Bucket with the name provided in the URL path. Dependency Injection.
     s3 : boto3_type_annotations.s3.ServiceResource
         S3 Service to perform operations on buckets in Ceph. Dependency Injection.
+    authorization : Callable[[str], Awaitable[Any]]
+        Async function to ask the auth service for authorization. Dependency Injection.
+    db : sqlalchemy.ext.asyncio.AsyncSession.
+        Async database session to perform query on. Dependency Injection.
+    current_user : clowmdb.models.User
+        Current user. Dependency Injection.
 
     Returns
     -------
     bucket : app.schemas.bucket.BucketOut
         Bucket with the provided name.
     """
-    s3bucket = s3.Bucket(name=bucket.name)
-    objects: list[ObjectSummary] = list(s3bucket.objects.all())
+    trace.get_current_span().set_attribute("bucket_name", bucket.name)
+    rbac_operation = (
+        "read_any"
+        if not bucket.public and not await CRUDBucketPermission.check_permission(db, bucket.name, current_user.uid)
+        else "read"
+    )
+    await authorization(rbac_operation)
+    objects: List[ObjectSummary] = list(get_s3_bucket_objects(s3, bucket.name))
     return BucketOutSchema(
         **{
             "description": bucket.description,
             "name": bucket.name,
-            "created_at": s3bucket.creation_date,
-            "owner": bucket.owner.uid,
-            "num_objects": sum(1 for obj in objects if not obj.key.endswith(".s3keep")),
+            "created_at": bucket.created_at,
+            "owner": bucket.owner_id,
+            "num_objects": sum(1 for obj in objects if not obj.key.endswith("/")),
             "size": reduce(lambda x, y: x + y.size, objects, 0),
+            "owner_constraint": bucket.owner_constraint,
         }
     )
 
 
 @router.delete("/{bucket_name}", status_code=status.HTTP_204_NO_CONTENT, summary="Delete a bucket")
+@start_as_current_span_async("api_delete_bucket", tracer=tracer)
 async def delete_bucket(
+    bucket: CurrentBucket,
+    db: DBSession,
+    current_user: CurrentUser,
+    authorization: Authorization,
+    s3: S3Resource,
     force_delete: bool = Query(False, description="Delete even non-empty bucket"),
-    bucket: BucketDB = Depends(get_current_bucket),
-    user: User = Depends(get_current_user),
-    db: AsyncSession = Depends(get_db),
-    s3: S3ServiceResource = Depends(get_s3_resource),
 ) -> None:
     """
-    Delete a bucket by its name. Only the owner of the bucket can delete the bucket.
+    Delete a bucket by its name. Only the owner of the bucket can delete the bucket.\n
+    Permission "bucket:delete" required if the current user is the owner of the bucket,
+    otherwise "bucket:delete_any" required.
     \f
     Parameters
     ----------
     force_delete : bool, default False
         Flag for deleting a non-empty bucket. Query parameter.
-    bucket : app.models.bucket.Bucket
+    bucket : clowmdb.models.Bucket
         Bucket with the name provided in the URL path. Dependency Injection.
-    user : app.models.user.User
+    current_user : clowmdb.models.User
         Current user. Dependency Injection.
     db : sqlalchemy.ext.asyncio.AsyncSession.
         Async database session to perform query on. Dependency Injection.
     s3 : boto3_type_annotations.s3.ServiceResource
         S3 Service to perform operations on buckets in Ceph. Dependency Injection.
+    authorization : Callable[[str], Awaitable[Any]]
+        Async function to ask the auth service for authorization. Dependency Injection.
     """
-    if not await CRUDBucketPermission.check_permission(db, bucket.name, user.uid, only_own=True):
-        raise HTTPException(status.HTTP_403_FORBIDDEN, "You can only delete your own buckets")
+    trace.get_current_span().set_attributes({"bucket_name": bucket.name, "force_delete": force_delete})
+    rbac_operation = "delete_any" if bucket.owner_id != current_user.uid else "delete"
+    await authorization(rbac_operation)
     if force_delete:
-        objs = [{"Key": obj.key} for obj in s3.Bucket(bucket.name).objects.all()]
+        objs = [{"Key": obj.key} for obj in get_s3_bucket_objects(s3, bucket_name=bucket.name)]
         if len(objs) > 0:
-            s3.Bucket(bucket.name).delete_objects(Delete={"Objects": objs})  # type: ignore
+            with tracer.start_as_current_span("s3_delete_objects") as span:
+                span.set_attribute("bucket_name", bucket.name)
+                s3.Bucket(bucket.name).delete_objects(Delete={"Objects": objs})  # type: ignore
     try:
-        s3.Bucket(name=bucket.name).delete()
-        await CRUDBucket.delete(db, bucket)
+        with tracer.start_as_current_span("s3_delete_bucket") as span:
+            span.set_attribute("bucket_name", bucket.name)
+            s3.Bucket(name=bucket.name).delete()
+        await CRUDBucket.delete(db, bucket.name)
     except ClientError:
         raise HTTPException(status.HTTP_400_BAD_REQUEST, detail="Bucket not empty")
-
-
-@router.get(
-    "/{bucket_name}/objects",
-    response_model=list[S3ObjectMetaInformation],
-    tags=["Object"],
-    summary="Get the metadata of the objects in the bucket",
-)
-async def get_bucket_objects(
-    bucket: BucketDB = Depends(get_current_bucket),
-    s3: S3ServiceResource = Depends(get_s3_resource),
-    current_user: User = Depends(get_current_user),
-    db: AsyncSession = Depends(get_db),
-) -> list[S3ObjectMetaInformation]:
-    """
-    Get the metadata of the objects in the bucket.
-    \f
-    Parameters
-    ----------
-    bucket : app.models.bucket.Bucket
-        Bucket with the name provided in the URL path. Dependency Injection.
-    s3 : boto3_type_annotations.s3.ServiceResource
-        S3 Service to perform operations on buckets in Ceph. Dependency Injection.
-    current_user : app.models.user.User
-        Current user. Dependency Injection.
-    db : sqlalchemy.ext.asyncio.AsyncSession.
-        Async database session to perform query on. Dependency Injection.
-
-    Returns
-    -------
-    objs : list[app.schemas.bucket.S3ObjectMetaInformation]
-        Meta information about all objects in the bucket.
-    """
-    permission = await CRUDBucketPermission.get(db, bucket.name, current_user.uid)
-    if permission is not None and permission.file_prefix is not None:
-        return [
-            S3ObjectMetaInformation.from_native_s3_object(obj)
-            for obj in s3.Bucket(bucket.name).objects.filter(Prefix=permission.file_prefix).all()
-        ]
-    return [S3ObjectMetaInformation.from_native_s3_object(obj) for obj in s3.Bucket(bucket.name).objects.all()]
-
-
-@router.get(
-    "/{bucket_name}/objects/{object_path:path}",
-    response_model=S3ObjectMetaInformation,
-    tags=["Object"],
-    summary="Get the metadata about a specific object",
-)
-async def get_bucket_object(
-    bucket: BucketDB = Depends(get_current_bucket),
-    s3: S3ServiceResource = Depends(get_s3_resource),
-    object_path: str = Path(
-        ...,
-        decsription="Name of the object",
-        examples={
-            "normal": {
-                "summary": "Normal file",
-                "description": "A normal file in a bucket",
-                "value": "test.txt",
-            },
-            "pseudo-folder": {
-                "summary": "Pseudo-folder file",
-                "description": "A file in a pseudo folder",
-                "value": "pseudo/sub/folder/test.txt",
-            },
-        },
-    ),
-    current_user: User = Depends(get_current_user),
-    db: AsyncSession = Depends(get_db),
-) -> S3ObjectMetaInformation:
-    """
-    Get the metadata of a specific object in a bucket.
-    \f
-    Parameters
-    ----------
-    bucket : app.models.bucket.Bucket
-        Bucket with the name provided in the URL path. Dependency Injection.
-    s3 : boto3_type_annotations.s3.ServiceResource
-        S3 Service to perform operations on buckets in Ceph. Dependency Injection.
-    object_path : str
-        Key of a specific object in the bucket. URL Path Parameter.
-    current_user : app.models.user.User
-        Current user. Dependency Injection.
-    db : sqlalchemy.ext.asyncio.AsyncSession.
-        Async database session to perform query on. Dependency Injection.
-
-    Returns
-    -------
-    objs : app.schemas.bucket.S3ObjectMetaInformation
-        Meta information about the specific object in the bucket.
-    """
-    permission = await CRUDBucketPermission.get(db, bucket.name, current_user.uid)
-    try:
-        if permission is None or permission.file_prefix is None or object_path.startswith(permission.file_prefix):
-            obj = s3.ObjectSummary(bucket_name=bucket.name, key=object_path)
-            return S3ObjectMetaInformation.from_native_s3_object(obj)
-        raise HTTPException(status.HTTP_403_FORBIDDEN, detail="No rights for this object.")
-    except ClientError:
-        raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Object not found")
diff --git a/app/api/endpoints/login.py b/app/api/endpoints/login.py
deleted file mode 100644
index 5f55bd550b3a60429ecc4c172dad2db35e0739a2..0000000000000000000000000000000000000000
--- a/app/api/endpoints/login.py
+++ /dev/null
@@ -1,133 +0,0 @@
-from typing import Any
-
-from fastapi import APIRouter, Depends, Request, Response, status
-from fastapi.responses import RedirectResponse
-from rgwadmin import RGWAdmin
-from sqlalchemy.ext.asyncio import AsyncSession
-
-from app.api.dependencies import LoginException, get_db, get_rgw_admin, get_userinfo_from_access_token
-from app.core.config import settings
-from app.core.security import create_access_token, oauth
-from app.crud.crud_user import CRUDUser
-from app.models.user import User
-
-router = APIRouter(prefix="/auth", tags=["Auth"])
-
-
-@router.get("/login", include_in_schema=False)
-async def login(request: Request) -> RedirectResponse:
-    """
-    Redirect route to OIDC provider.
-    \f
-    Parameters
-    ----------
-    request : fastapi.requests.Request
-        Raw request object.
-
-    Returns
-    -------
-    response : fastapi.responses.RedirectResponse
-        Redirect response to right OAuth2 endpoint
-    """
-    # Clear session to prevent an overflow
-    request.session.clear()
-    # construct absolute url for callback
-    base_url = str(request.base_url)[:-1]
-    if settings.SSL_TERMINATION:  # pragma: no cover
-        base_url = "https" + base_url[4:]
-    redirect_uri = base_url + router.prefix + "/callback"
-    return await oauth.lifescience.authorize_redirect(request, redirect_uri)
-
-
-@router.get(
-    "/callback",
-    response_class=RedirectResponse,
-    status_code=status.HTTP_302_FOUND,
-    summary="Life Science Login Callback",
-    responses={
-        status.HTTP_302_FOUND: {
-            "headers": {
-                "Set-Cookie": {
-                    "description": "JWT for accessing the API",
-                    "schema": {
-                        "type": "string",
-                        "example": "bearer=fake-jwt-cookie; Domain=localhost; expired=Wed, 05 Jan 2022 "
-                        "09:00:00 GMT; Path=/; SameSite=strict; Secure",
-                    },
-                }
-            }
-        }
-    },
-)
-async def login_callback(
-    response: RedirectResponse,
-    user_info: dict[str, Any] = Depends(get_userinfo_from_access_token),
-    db: AsyncSession = Depends(get_db),
-    rgw: RGWAdmin = Depends(get_rgw_admin),
-) -> str:
-    """
-    Callback for the Life Science Identity Provider.\n
-    To start the login process visit the route [login route](/api/auth/login/)
-
-    If the user is already known to the system, then a JWT token will be created and sent via the 'set-cookie' header.
-    The key for this Cookie is 'bearer'.\n
-    If the user is new, he will be created and then a JWT token is issued.\n
-    This JWT has to be sent to all authorized endpoints via the HTTPBearer scheme.
-    \f
-    Parameters
-    ----------
-    response : fastapi.responses.RedirectResponse
-        Response which will hold the JWT cookie.
-    user_info : dict[str, Any]
-        Get the userinfo with OAuth2. Dependency Injection.
-    db : sqlalchemy.ext.asyncio.AsyncSession.
-        Async database session to perform query on. Dependency Injection.
-    rgw : rgwadmin.RGWAdmin
-        RGW admin interface to manage Ceph's object store. Dependency Injection.
-
-    Returns
-    -------
-    path : str
-        Redirect path after successful login.
-    """
-    try:
-        lifescience_id = (
-            user_info["voperson_id"] if isinstance(user_info["voperson_id"], str) else user_info["voperson_id"][0]
-        )
-        uid = lifescience_id.split("@")[0]
-        user = await CRUDUser.get(db, uid)
-        if user is None:
-            new_user = User(uid=uid, display_name=user_info["name"])
-            user = await CRUDUser.create(db, new_user)
-            rgw.create_user(uid=user.uid, max_buckets=-1, display_name=user.display_name)
-        token = create_access_token(uid)
-        response.set_cookie(
-            key="bearer",
-            value=token,
-            samesite="strict",
-            max_age=settings.JWT_TOKEN_EXPIRE_MINUTES,
-            secure=True,
-            domain=settings.DOMAIN,
-        )
-    except Exception:  # pragma: no cover
-        raise LoginException(error_source="server")
-    return "/"
-
-
-def login_exception_handler(request: Request, exc: LoginException) -> Response:
-    """
-    Exception handler for all kinds of login errors.
-
-    Parameters
-    ----------
-    request : fastapi.Request
-        Original request where the exception occurred.
-    exc : LoginException
-        The exception that was raised.
-
-    Returns
-    -------
-    redirect : fastapi.Response
-        Redirect to base URL with error as query parameter
-    """
-    return RedirectResponse(f"/?login_error={exc.error_source}", status_code=status.HTTP_302_FOUND)
diff --git a/app/api/endpoints/s3key.py b/app/api/endpoints/s3key.py
new file mode 100644
index 0000000000000000000000000000000000000000..03b9cbff02a6b2255e9453155bb0dd9310da5bad
--- /dev/null
+++ b/app/api/endpoints/s3key.py
@@ -0,0 +1,201 @@
+from typing import Annotated, Any, Awaitable, Callable, List
+
+from fastapi import APIRouter, Depends, HTTPException, Path, status
+from opentelemetry import trace
+from rgwadmin.exceptions import RGWAdminException
+
+from app.api.dependencies import AuthorizationDependency, CurrentUser, PathUser, RGWAdminResource
+from app.ceph.rgw import get_s3_keys
+from app.otlp import start_as_current_span_async
+from app.schemas.user import S3Key
+
+router = APIRouter(prefix="/users/{uid}/keys", tags=["S3Key"])
+s3key_authorization = AuthorizationDependency(resource="s3_key")
+Authorization = Annotated[Callable[[str], Awaitable[Any]], Depends(s3key_authorization)]
+tracer = trace.get_tracer_provider().get_tracer(__name__)
+
+AccessID = Annotated[
+    str,
+    Path(
+        ...,
+        description="ID of the S3 access key",
+        examples=["CRJ6B037V2ZT4U3W17VC"],
+    ),
+]
+
+
+@router.get(
+    "",
+    response_model=List[S3Key],
+    summary="Get the S3 Access keys from a user",
+)
+@start_as_current_span_async("api_list_s3_keys", tracer=tracer)
+async def get_user_keys(
+    rgw: RGWAdminResource,
+    current_user: CurrentUser,
+    authorization: Authorization,
+    user: PathUser,
+) -> List[S3Key]:
+    """
+    Get all the S3 Access keys for a specific user.\n
+    Permission "s3_key:list" required.
+    \f
+    Parameters
+    ----------
+    rgw : rgwadmin.RGWAdmin
+        RGW admin interface to manage Ceph's object store. Dependency Injection.
+    user : clowmdb.models.User
+        User with given uid. Dependency Injection.
+    authorization : Callable[[str], Awaitable[Any]]
+        Async function to ask the auth service for authorization. Dependency Injection.
+    current_user : clowmdb.models.User
+        Current user who will be the owner of the newly created bucket. Dependency Injection.
+
+    Returns
+    -------
+    keys : List(app.schemas.user.S3Key)
+        All S3 keys from the user.
+    """
+    trace.get_current_span().set_attribute("uid", user.uid)
+    if current_user.uid != user.uid:
+        raise HTTPException(status.HTTP_403_FORBIDDEN, detail="Action forbidden.")
+    await authorization("list")
+    return get_s3_keys(rgw, user.uid)
+
+
+@router.post(
+    "",
+    response_model=S3Key,
+    summary="Create a Access key for a user",
+    status_code=status.HTTP_201_CREATED,
+)
+@start_as_current_span_async("api_create_s3_key", tracer=tracer)
+async def create_user_key(
+    rgw: RGWAdminResource,
+    current_user: CurrentUser,
+    authorization: Authorization,
+    user: PathUser,
+) -> S3Key:
+    """
+    Create a S3 Access key for a specific user.\n
+    Permission "s3_key:create" required.
+    \f
+    Parameters
+    ----------
+    rgw : rgwadmin.RGWAdmin
+        RGW admin interface to manage Ceph's object store. Dependency Injection.
+    user : clowmdb.models.User
+        User with given uid. Dependency Injection.
+    authorization : Callable[[str], Awaitable[Any]]
+        Async function to ask the auth service for authorization. Dependency Injection.
+    current_user : clowmdb.models.User
+        Current user who will be the owner of the newly created bucket. Dependency Injection.
+
+    Returns
+    -------
+    key : app.schemas.user.S3Key
+        Newly created S3 key.
+    """
+    trace.get_current_span().set_attribute("uid", user.uid)
+    if current_user.uid != user.uid:
+        raise HTTPException(status.HTTP_403_FORBIDDEN, detail="Action forbidden.")
+    await authorization("create")
+    with tracer.start_as_current_span("rgw_list_keys") as span:
+        span.set_attribute("uid", user.uid)
+        before_keys_set = set(map(lambda key: key.access_key, get_s3_keys(rgw, user.uid)))
+    with tracer.start_as_current_span("rgw_create_key") as span:
+        span.set_attribute("uid", user.uid)
+        # create keys returns all keys for a user including the new one
+        after_keys = rgw.create_key(uid=user.uid, key_type="s3", generate_key=True)
+    new_key_id = list(set(map(lambda key: key["access_key"], after_keys)) - before_keys_set)[0]  # find ID of the key
+    index = [key["access_key"] for key in after_keys].index(new_key_id)  # find new key by ID
+    return S3Key(**after_keys[index])
+
+
+@router.get(
+    "/{access_id}",
+    response_model=S3Key,
+    summary="Get a specific S3 Access key from a user",
+)
+@start_as_current_span_async("api_get_s3_key", tracer=tracer)
+async def get_user_key(
+    rgw: RGWAdminResource,
+    current_user: CurrentUser,
+    authorization: Authorization,
+    access_id: AccessID,
+    user: PathUser,
+) -> S3Key:
+    """
+    Get a specific S3 Access Key for a specific user.\n
+    Permission "s3_key:read" required.
+    \f
+    Parameters
+    ----------
+    access_id : str
+        ID of the requested S3 key. URL Path Parameter.
+    rgw : rgwadmin.RGWAdmin
+        RGW admin interface to manage Ceph's object store. Dependency Injection.
+    user : clowmdb.models.User
+        User with given uid. Dependency Injection.
+    authorization : Callable[[str], Awaitable[Any]]
+        Async function to ask the auth service for authorization. Dependency Injection.
+    current_user : clowmdb.models.User
+        Current user who will be the owner of the newly created bucket. Dependency Injection.
+
+    Returns
+    -------
+    key : app.schemas.user.S3Key
+        Requested S3 key.
+    """
+    trace.get_current_span().set_attribute("uid", user.uid)
+    if current_user.uid != user.uid:
+        raise HTTPException(status.HTTP_403_FORBIDDEN, detail="Action forbidden.")
+    await authorization("read")
+    keys = get_s3_keys(rgw, user.uid)
+    try:
+        index = [key.access_key for key in keys].index(access_id)
+        return keys[index]
+    except ValueError:
+        raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Key not found")
+
+
+@router.delete(
+    "/{access_id}",
+    summary="Delete a specific S3 Access key from a user",
+    status_code=status.HTTP_204_NO_CONTENT,
+)
+@start_as_current_span_async("api_delete_s3_key", tracer=tracer)
+async def delete_user_key(
+    access_id: AccessID,
+    rgw: RGWAdminResource,
+    current_user: CurrentUser,
+    authorization: Authorization,
+    user: PathUser,
+) -> None:
+    """
+    Delete a specific S3 Access key for a specific user.\n
+    Permission "s3_key:delete" required if the current user is the target, otherwise "s3_key:delete_any" required.
+    \f
+    Parameters
+    ----------
+    access_id : str
+        ID of the S3 key to delete. URL Path Parameter.
+    rgw : rgwadmin.RGWAdmin
+        RGW admin interface to manage Ceph's object store. Dependency Injection.
+    user : clowmdb.models.User
+        User with given uid. Dependency Injection.
+    authorization : Callable[[str], Awaitable[Any]]
+        Async function to ask the auth service for authorization. Dependency Injection.
+    current_user : clowmdb.models.User
+        Current user who will be the owner of the newly created bucket. Dependency Injection.
+    """
+    trace.get_current_span().set_attribute("uid", user.uid)
+    await authorization("delete" if current_user.uid == user.uid else "delete_any")
+    if len(get_s3_keys(rgw, user.uid)) <= 1:
+        raise HTTPException(status.HTTP_400_BAD_REQUEST, detail="It's not possible to delete the last key")
+    try:
+        with tracer.start_as_current_span("rgw_delete_key") as span:
+            span.set_attribute("uid", user.uid)
+            rgw.remove_key(access_key=access_id, uid=user.uid)
+    except RGWAdminException:
+        raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Key not found")
diff --git a/app/api/endpoints/users.py b/app/api/endpoints/users.py
deleted file mode 100644
index e5e6cb3a82e332abcb463d1202c0976592cacea5..0000000000000000000000000000000000000000
--- a/app/api/endpoints/users.py
+++ /dev/null
@@ -1,208 +0,0 @@
-from fastapi import APIRouter, Depends, HTTPException, Path, Query, status
-from rgwadmin import RGWAdmin
-from rgwadmin.exceptions import RGWAdminException
-from sqlalchemy.ext.asyncio import AsyncSession
-
-from app.api.dependencies import get_current_user, get_db, get_rgw_admin, get_user_by_path_uid
-from app.crud.crud_user import CRUDUser
-from app.models.user import User as UserDB
-from app.schemas.user import S3Key
-from app.schemas.user import User as UserSchema
-
-router = APIRouter(prefix="/users", tags=["User"])
-
-
-@router.get("/me", response_model=UserSchema, summary="Get the logged in user")
-def get_logged_in_user(
-    current_user: UserDB = Depends(get_current_user),
-) -> UserDB:
-    """
-    Return the user associated with the used JWT.
-    \f
-    Parameters
-    ----------
-    current_user : app.models.user.User
-        User from the database associated to the used JWT. Dependency injection.
-
-    Returns
-    -------
-    current_user : app.models.user.User
-        User associated to used JWT.
-    """
-    return current_user
-
-
-@router.get("/", response_model=list[UserSchema], summary="Search for users by their name")
-async def search_users(
-    name_like: str = Query(..., min_length=3, max_length=30),
-    db: AsyncSession = Depends(get_db),
-) -> list[UserDB]:
-    """
-    Return the users that have a specific substring in their name.
-    \f
-    Parameters
-    ----------
-    name_like : string
-        Substring of a name to search users for. Query Parameter.
-    db : sqlalchemy.ext.asyncio.AsyncSession.
-        Async database session to perform query on. Dependency Injection.
-
-    Returns
-    -------
-    users: list[app.models.user.User]
-        Users who have the substring in their name.
-    """
-    return await CRUDUser.search_for_name(db, name_like)
-
-
-@router.get("/{uid}", response_model=UserSchema, summary="Get a user by its uid")
-def get_user(user: UserDB = Depends(get_user_by_path_uid)) -> UserDB:
-    """
-    Return the user with the specific uid. A user can only view himself.
-    \f
-    Parameters
-    ----------
-    user : app.models.user.User
-        User with given uid. Dependency Injection.
-    Returns
-    -------
-    user : app.models.user.User
-        User with given uid.
-    """
-    return user
-
-
-@router.get(
-    "/{uid}/keys",
-    response_model=list[S3Key],
-    tags=["Key"],
-    summary="Get the S3 Access keys from a user",
-)
-def get_user_keys(rgw: RGWAdmin = Depends(get_rgw_admin), user: UserDB = Depends(get_user_by_path_uid)) -> list[S3Key]:
-    """
-    Get all the S3 Access keys for a specific user.
-    \f
-    Parameters
-    ----------
-    rgw : rgwadmin.RGWAdmin
-        RGW admin interface to manage Ceph's object store. Dependency Injection.
-    user : app.models.user.User
-        User with given uid. Dependency Injection.
-
-    Returns
-    -------
-    keys : list(app.schemas.user.S3Key)
-        All S3 keys from the user.
-    """
-    return [S3Key(**key) for key in rgw.get_user(uid=user.uid, stats=False)["keys"]]
-
-
-@router.post(
-    "/{uid}/keys",
-    response_model=S3Key,
-    tags=["Key"],
-    summary="Create a Access key for a user",
-    status_code=status.HTTP_201_CREATED,
-)
-def create_user_key(rgw: RGWAdmin = Depends(get_rgw_admin), user: UserDB = Depends(get_user_by_path_uid)) -> S3Key:
-    """
-    Create a S3 Access key for a specific user.
-    \f
-    Parameters
-    ----------
-    rgw : rgwadmin.RGWAdmin
-        RGW admin interface to manage Ceph's object store. Dependency Injection.
-    user : app.models.user.User
-        User with given uid. Dependency Injection.
-
-    Returns
-    -------
-    key : app.schemas.user.S3Key
-        Newly created S3 key.
-    """
-    before_keys_set = set(
-        map(
-            lambda key: key["access_key"],
-            rgw.get_user(uid=user.uid, stats=False)["keys"],
-        )
-    )
-    # create keys returns all keys for a user including the new one
-    after_keys = rgw.create_key(uid=user.uid, key_type="s3", generate_key=True)
-    new_key_id = list(set(map(lambda key: key["access_key"], after_keys)) - before_keys_set)[0]  # find ID of the key
-    index = [key["access_key"] for key in after_keys].index(new_key_id)  # find new key by ID
-    return S3Key(**after_keys[index])
-
-
-@router.get(
-    "/{uid}/keys/{access_id}",
-    response_model=S3Key,
-    tags=["Key"],
-    summary="Get a specific S3 Access key from a user",
-)
-def get_user_key(
-    access_id: str = Path(
-        ...,
-        description="ID of the S3 access key",
-        example="CRJ6B037V2ZT4U3W17VC",
-    ),
-    rgw: RGWAdmin = Depends(get_rgw_admin),
-    user: UserDB = Depends(get_user_by_path_uid),
-) -> S3Key:
-    """
-    Get a specific S3 Access Key for a specific user.
-    \f
-    Parameters
-    ----------
-    access_id : str
-        ID of the requested S3 key. URL Path Parameter.
-    rgw : rgwadmin.RGWAdmin
-        RGW admin interface to manage Ceph's object store. Dependency Injection.
-    user : app.models.user.User
-        User with given uid. Dependency Injection.
-
-    Returns
-    -------
-    key : app.schemas.user.S3Key
-        Requested S3 key.
-    """
-    keys = rgw.get_user(uid=user.uid, stats=False)["keys"]
-    try:
-        index = [key["access_key"] for key in keys].index(access_id)
-        return S3Key(**keys[index])
-    except ValueError:
-        raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Key not found")
-
-
-@router.delete(
-    "/{uid}/keys/{access_id}",
-    tags=["Key"],
-    summary="Delete a specific S3 Access key from a user",
-    status_code=status.HTTP_204_NO_CONTENT,
-)
-def delete_user_key(
-    access_id: str = Path(
-        ...,
-        description="ID of the S3 access key",
-        example="CRJ6B037V2ZT4U3W17VC",
-    ),
-    rgw: RGWAdmin = Depends(get_rgw_admin),
-    user: UserDB = Depends(get_user_by_path_uid),
-) -> None:
-    """
-    Delete a specific S3 Access key for a specific user.
-    \f
-    Parameters
-    ----------
-    access_id : str
-        ID of the S3 key to delete. URL Path Parameter.
-    rgw : rgwadmin.RGWAdmin
-        RGW admin interface to manage Ceph's object store. Dependency Injection.
-    user : app.models.user.User
-        User with given uid. Dependency Injection.
-    """
-    if len(rgw.get_user(uid=user.uid, stats=False)["keys"]) <= 1:
-        raise HTTPException(status.HTTP_400_BAD_REQUEST, detail="It's not possible to delete the last key")
-    try:
-        rgw.remove_key(access_key=access_id, uid=user.uid)
-    except RGWAdminException:
-        raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Key not found")
diff --git a/app/api/miscellaneous_endpoints.py b/app/api/miscellaneous_endpoints.py
index 6a0bd8e827fe063e5dd8b4a53ab2e243ebdd1fe9..0f4f786b06506ed0843fceb5f84a7a791441ab2e 100644
--- a/app/api/miscellaneous_endpoints.py
+++ b/app/api/miscellaneous_endpoints.py
@@ -1,6 +1,8 @@
+from typing import Dict
+
 from fastapi import APIRouter, status
 
-miscellaneous_router = APIRouter(include_in_schema=True)
+miscellaneous_router = APIRouter(include_in_schema=False)
 
 
 @miscellaneous_router.get(
@@ -13,14 +15,14 @@ miscellaneous_router = APIRouter(include_in_schema=True)
         },
     },
 )
-def health_check() -> dict[str, str]:
+def health_check() -> Dict[str, str]:
     """
     Check if the service is reachable.
     \f
 
     Returns
     -------
-    response : dict[str, str]
+    response : Dict[str, str]
         status ok
     """
     return {"status": "OK"}
diff --git a/app/ceph/rgw.py b/app/ceph/rgw.py
index a5a3486d0fe24090401177a12c463146533f40bf..417fae6ec423b0621fc87e9f6918496ad63ac785 100644
--- a/app/ceph/rgw.py
+++ b/app/ceph/rgw.py
@@ -1,25 +1,22 @@
-from typing import TYPE_CHECKING
+from typing import List
 
-from boto3 import resource
+from opentelemetry import trace
 from rgwadmin import RGWAdmin
 
 from app.core.config import settings
+from app.schemas.user import S3Key
 
-if TYPE_CHECKING:
-    from boto3.resources.base import ServiceResource
-else:
-    ServiceResource = object
+tracer = trace.get_tracer_provider().get_tracer(__name__)
 
-s3_resource: ServiceResource = resource(
-    service_name="s3",
-    endpoint_url=settings.OBJECT_GATEWAY_URI,
-    aws_access_key_id=settings.CEPH_ACCESS_KEY,
-    aws_secret_access_key=settings.CEPH_SECRET_KEY,
-    verify=False,
-)
 rgw = RGWAdmin(
-    access_key=settings.CEPH_ACCESS_KEY,
-    secret_key=settings.CEPH_SECRET_KEY,
-    secure=False,
-    server=settings.OBJECT_GATEWAY_URI.split("://")[-1],
+    access_key=settings.USER_CEPH_ACCESS_KEY,
+    secret_key=settings.USER_CEPH_SECRET_KEY,
+    secure=str(settings.OBJECT_GATEWAY_URI).startswith("https"),
+    server=str(settings.OBJECT_GATEWAY_URI).split("://")[-1][:-1],
 )
+
+
+def get_s3_keys(rgw: RGWAdmin, uid: str) -> List[S3Key]:
+    with tracer.start_as_current_span("s3_get_user_keys") as span:
+        span.set_attribute("uid", uid)
+        return [S3Key(**key) for key in rgw.get_user(uid=uid, stats=False)["keys"]]
diff --git a/app/ceph/s3.py b/app/ceph/s3.py
new file mode 100644
index 0000000000000000000000000000000000000000..6f0f94ef7535de410c453e8a38ba205bb807bb2e
--- /dev/null
+++ b/app/ceph/s3.py
@@ -0,0 +1,44 @@
+from typing import TYPE_CHECKING, List
+
+from boto3 import resource
+from opentelemetry import trace
+
+from app.core.config import settings
+
+if TYPE_CHECKING:
+    from mypy_boto3_s3.service_resource import BucketPolicy, ObjectSummary, S3ServiceResource
+else:
+    S3ServiceResource = object
+    BucketPolicy = object
+    ObjectSummary = object
+
+
+tracer = trace.get_tracer_provider().get_tracer(__name__)
+
+s3_resource: S3ServiceResource = resource(
+    service_name="s3",
+    endpoint_url=str(settings.OBJECT_GATEWAY_URI)[:-1],
+    aws_access_key_id=settings.BUCKET_CEPH_ACCESS_KEY,
+    aws_secret_access_key=settings.BUCKET_CEPH_SECRET_KEY,
+    verify=str(settings.OBJECT_GATEWAY_URI).startswith("https"),
+)
+
+
+def get_s3_bucket_policy(s3: S3ServiceResource, bucket_name: str) -> BucketPolicy:
+    with tracer.start_as_current_span("s3_get_bucket_policy") as span:
+        span.set_attribute("bucket_name", bucket_name)
+        s3_policy = s3.Bucket(bucket_name).Policy()
+        s3_policy.load()
+        return s3_policy
+
+
+def put_s3_bucket_policy(s3: S3ServiceResource, bucket_name: str, policy: str) -> None:
+    with tracer.start_as_current_span("s3_put_bucket_policy") as span:
+        span.set_attribute("bucket_name", bucket_name)
+        s3.Bucket(bucket_name).Policy().put(Policy=policy)
+
+
+def get_s3_bucket_objects(s3: S3ServiceResource, bucket_name: str) -> List[ObjectSummary]:
+    with tracer.start_as_current_span("s3_get_object_meta_data") as span:
+        span.set_attribute("bucket_name", bucket_name)
+        return list(s3.Bucket(bucket_name).objects.all())
diff --git a/app/check_ceph_connection.py b/app/check_ceph_connection.py
index 5106b509fe4a871f79261f9266973bd615638d4d..4a3716ea39af355a8183d2441fdf8560aa1409ea 100644
--- a/app/check_ceph_connection.py
+++ b/app/check_ceph_connection.py
@@ -8,7 +8,7 @@ from app.core.config import settings
 logging.basicConfig(level=logging.INFO)
 logger = logging.getLogger(__name__)
 
-max_tries = 60 * 3  # 3 minutes
+max_tries = 30  # 2*30 seconds
 wait_seconds = 2
 
 
@@ -20,7 +20,7 @@ wait_seconds = 2
 )
 def init() -> None:
     try:
-        httpx.get(settings.OBJECT_GATEWAY_URI, timeout=5.0)
+        httpx.get(str(settings.OBJECT_GATEWAY_URI), timeout=5.0)
     except Exception as e:
         logger.error(e)
         raise e
diff --git a/app/check_database_connection.py b/app/check_database_connection.py
index 7f04075b5f543b3881e12c5ead6b9fa7d76f3a2e..30102f8561f941951fee1bef510f4ab8b2376b81 100644
--- a/app/check_database_connection.py
+++ b/app/check_database_connection.py
@@ -1,13 +1,17 @@
 import logging
 
-from db.session import SessionLocal
+from clowmdb import latest_revision
+from clowmdb.db.session import get_session
+from sqlalchemy import text
 from tenacity import after_log, before_log, retry, stop_after_attempt, wait_fixed
 
+from app.core.config import settings
+
 logging.basicConfig(level=logging.INFO)
 logger = logging.getLogger(__name__)
 
-max_tries = 60 * 3  # 3 minutes
-wait_seconds = 2
+max_tries = 30  # 3*30 seconds
+wait_seconds = 3
 
 
 @retry(
@@ -18,9 +22,13 @@ wait_seconds = 2
 )
 def init() -> None:
     try:
-        with SessionLocal() as db:
+        with get_session(url=str(settings.SQLALCHEMY_DATABASE_NORMAL_URI)) as db:
             # Try to create session to check if DB is awake
-            db.execute("SELECT 1")
+            db_revision = db.execute(text("SELECT version_num FROM alembic_version LIMIT 1")).scalar_one_or_none()
+            if db_revision != latest_revision:
+                raise ValueError(
+                    f"Database revision doesn't match revision defined by package `clowmdb`. Expected {latest_revision}, found {db_revision}"  # noqa:E501
+                )
     except Exception as e:
         logger.error(e)
         raise e
diff --git a/app/check_oidc_connection.py b/app/check_oidc_connection.py
deleted file mode 100644
index cd76d1a20f17bd565362cfe78b884a7564858a59..0000000000000000000000000000000000000000
--- a/app/check_oidc_connection.py
+++ /dev/null
@@ -1,36 +0,0 @@
-import logging
-
-import httpx
-from tenacity import after_log, before_log, retry, stop_after_attempt, wait_fixed
-
-from app.core.config import settings
-
-logging.basicConfig(level=logging.INFO)
-logger = logging.getLogger(__name__)
-
-max_tries = 60 * 3  # 3 minutes
-wait_seconds = 2
-
-
-@retry(
-    stop=stop_after_attempt(max_tries),
-    wait=wait_fixed(wait_seconds),
-    before=before_log(logger, logging.INFO),
-    after=after_log(logger, logging.WARN),
-)
-def init() -> None:
-    try:
-        httpx.get(settings.OIDC_BASE_URI + settings.OIDC_META_INFO_PATH, timeout=5.0)
-    except Exception as e:
-        logger.error(e)
-        raise e
-
-
-def main() -> None:
-    logger.info("Check OIDC Provider connection")
-    init()
-    logger.info("OIDC Provider connection established")
-
-
-if __name__ == "__main__":
-    main()
diff --git a/app/core/config.py b/app/core/config.py
index b64fb4825ea92eb79cd26b242e27e40a93c6fa91..773fef537f147d448abd13eee4207afec95f4b9f 100644
--- a/app/core/config.py
+++ b/app/core/config.py
@@ -1,37 +1,52 @@
-import secrets
-from typing import Any, Dict, List, Optional, Union
+from pathlib import Path
+from typing import Any, Dict, Optional
 
-from pydantic import AnyHttpUrl, AnyUrl, BaseSettings, Field, validator
+from pydantic import AnyHttpUrl, AnyUrl, Field, computed_field
+from pydantic_settings import BaseSettings, SettingsConfigDict
 
 
 def _assemble_db_uri(values: Dict[str, Any], async_flag: bool = True) -> Any:
     return AnyUrl.build(
         scheme=f"mysql+{'aiomysql' if async_flag else 'pymysql'}",
         password=values.get("DB_PASSWORD"),
-        user=values.get("DB_USER"),
-        port=str(values.get("DB_PORT")),
-        host=values.get("DB_HOST"),
-        path=f"/{values.get('DB_DATABASE') or ''}",
+        username=values.get("DB_USER"),
+        port=values.get("DB_PORT"),
+        host=values.get("DB_HOST"),  # type: ignore[arg-type]
+        path=f"{values.get('DB_DATABASE') or ''}",
     )
 
 
+def _load_public_key(pub_key_val: Optional[str], pub_key_file: Optional[Path]) -> str:
+    pub_key = ""
+    if pub_key_val is not None:
+        pub_key = pub_key_val
+    if pub_key_file is not None:
+        with open(pub_key_file) as f:
+            pub_key = f.read()
+    if len(pub_key) == 0:
+        raise ValueError("PUBLIC_KEY_VALUE or PUBLIC_KEY_FILE must be set")
+    return pub_key
+
+
 class Settings(BaseSettings):
-    DOMAIN: str = Field("localhost", description="Domain of the service.")
-    SSL_TERMINATION: bool = Field(False, description="Flag if the service runs behind a SSL termination proxy")
+    CLOWM_URL: AnyHttpUrl = Field(
+        AnyHttpUrl("http://localhost:8080"),
+        description="Base HTTP URL where the CloWM service is reachable.",
+        examples=["http://localhost:8080"],
+    )
     API_PREFIX: str = Field("/api", description="Path Prefix for all API endpoints.")
-    SECRET_KEY: str = Field(secrets.token_urlsafe(32), description="Secret key to sign the JWTs.")
-    # 60 minutes * 24 hours * 8 days = 8 days
-    JWT_TOKEN_EXPIRE_MINUTES: int = Field(60 * 24 * 8, description="JWT lifespan in minutes.")
-    # BACKEND_CORS_ORIGINS is a JSON-formatted list of origins
-    BACKEND_CORS_ORIGINS: List[AnyHttpUrl] = Field([], description="List of all valid CORS origins")
-
-    @validator("BACKEND_CORS_ORIGINS", pre=True)
-    def assemble_cors_origins(cls, v: Union[str, List[str]]) -> Union[List[str], str]:
-        if isinstance(v, str) and not v.startswith("["):
-            return [i.strip() for i in v.split(",")]
-        elif isinstance(v, (list, str)):
-            return v
-        raise ValueError(v)
+
+    public_key_value: Optional[str] = Field(
+        None, description="Public RSA Key in PEM format to sign the JWTs.", validation_alias="PUBLIC_KEY_VALUE"
+    )
+    public_key_file: Optional[Path] = Field(
+        None, description="Path to Public RSA Key in PEM format to sign the JWTs.", validation_alias="PUBLIC_KEY_FILE"
+    )
+
+    @computed_field  # type: ignore[misc]
+    @property
+    def PUBLIC_KEY(self) -> str:
+        return _load_public_key(self.public_key_value, self.public_key_file)
 
     DB_HOST: str = Field(..., description="Host of the database.")
     DB_USER: str = Field(..., description="Username in the database.")
@@ -39,36 +54,58 @@ class Settings(BaseSettings):
     DB_DATABASE: str = Field(..., description="Name of the database.")
     DB_PORT: int = Field(3306, description="Port of the database.")
     SQLALCHEMY_VERBOSE_LOGGER: bool = Field(False, description="Flag whether to print the SQL Queries in the logs.")
-    SQLALCHEMY_DATABASE_ASYNC_URI: AnyUrl | None = None
-
-    @validator("SQLALCHEMY_DATABASE_ASYNC_URI", pre=True)
-    def assemble_async_db_connection(cls, v: Optional[str], values: Dict[str, Any]) -> Any:
-        if isinstance(v, str):
-            return v
-        return _assemble_db_uri(values, async_flag=True)
 
-    SQLALCHEMY_DATABASE_NORMAL_URI: AnyUrl | None = None
+    @computed_field  # type: ignore[misc]
+    @property
+    def SQLALCHEMY_DATABASE_ASYNC_URI(self) -> AnyUrl:
+        return _assemble_db_uri(
+            {
+                "DB_HOST": self.DB_HOST,
+                "DB_USER": self.DB_USER,
+                "DB_PASSWORD": self.DB_PASSWORD,
+                "DB_DATABASE": self.DB_DATABASE,
+                "DB_PORT": self.DB_PORT,
+            },
+            async_flag=True,
+        )
 
-    @validator("SQLALCHEMY_DATABASE_NORMAL_URI", pre=True)
-    def assemble_db_connection(cls, v: Optional[str], values: Dict[str, Any]) -> Any:
-        if isinstance(v, str):
-            return v
-        return _assemble_db_uri(values, async_flag=False)
+    @computed_field  # type: ignore[misc]
+    @property
+    def SQLALCHEMY_DATABASE_NORMAL_URI(self) -> AnyUrl:
+        return _assemble_db_uri(
+            {
+                "DB_HOST": self.DB_HOST,
+                "DB_USER": self.DB_USER,
+                "DB_PASSWORD": self.DB_PASSWORD,
+                "DB_DATABASE": self.DB_DATABASE,
+                "DB_PORT": self.DB_PORT,
+            },
+            async_flag=False,
+        )
 
     OBJECT_GATEWAY_URI: AnyHttpUrl = Field(..., description="URI of the Ceph Object Gateway.")
-    CEPH_ACCESS_KEY: str = Field(..., description="Access key for the Ceph Object Gateway with admin privileges.")
-    CEPH_SECRET_KEY: str = Field(..., description="Secret key for the Ceph Object Gateway with admin privileges.")
-    CEPH_USERNAME: str = Field(..., description="ID of the Proxy user in Ceph.")
-
-    OIDC_CLIENT_SECRET: str = Field(..., description="OIDC Client secret")
-    OIDC_CLIENT_ID: str = Field(..., description="OIDC Client ID")
-    OIDC_BASE_URI: AnyHttpUrl = Field(..., description="OIDC Base URI")
-    OIDC_META_INFO_PATH: str = Field("/.well-known/openid-configuration", description="Path to the OIDC meta data file")
-
-    class Config:
-        case_sensitive = True
-        env_file = ".env"
-        secrets_dir = "/run/secrets"
+    USER_CEPH_ACCESS_KEY: str = Field(
+        ..., description="Access key for the Ceph Object Gateway with 'user:read,user:write privileges'."
+    )
+    USER_CEPH_SECRET_KEY: str = Field(
+        ..., description="Secret key for the Ceph Object Gateway with 'user:read,user:write privileges'."
+    )
+    BUCKET_CEPH_ACCESS_KEY: str = Field(
+        ..., description="Access key for the Ceph Object Gateway with unlimited buckets."
+    )
+    BUCKET_CEPH_SECRET_KEY: str = Field(
+        ..., description="Secret key for the Ceph Object Gateway with unlimited buckets."
+    )
+    BUCKET_CEPH_USERNAME: str = Field(
+        ..., description="ID of the user in ceph who owns all the buckets. Owner of 'BUCKET_CEPH_ACCESS_KEY'"
+    )
+    OPA_URI: AnyHttpUrl = Field(..., description="URI of the OPA Service")
+    OPA_POLICY_PATH: str = Field("/clowm/authz/allow", description="Path to the OPA Policy for Authorization")
+    OTLP_GRPC_ENDPOINT: Optional[str] = Field(
+        None, description="OTLP compatible endpoint to send traces via gRPC, e.g. Jaeger"
+    )
+
+    model_config = SettingsConfigDict(case_sensitive=True, env_file=".env", secrets_dir="/run/secrets", extra="ignore")
 
 
 settings = Settings()
diff --git a/app/core/security.py b/app/core/security.py
index 2499c6b01056d4e8f4d85a0a632e68165db40d4e..40e1febb83fc4308892c2ec6a038e04c10858640 100644
--- a/app/core/security.py
+++ b/app/core/security.py
@@ -1,38 +1,22 @@
-from datetime import datetime, timedelta
-from typing import Any, Union
+from typing import Dict
 
-from authlib.integrations.starlette_client import OAuth
 from authlib.jose import JsonWebToken
+from fastapi import HTTPException, status
+from httpx import AsyncClient
+from opentelemetry import trace
 
 from app.core.config import settings
+from app.otlp import start_as_current_span_async
+from app.schemas.security import AuthzRequest, AuthzResponse
 
-ISSUER = "proxy_api"
-ALGORITHM = "HS256"
+ISSUER = "clowm"
+ALGORITHM = "RS256"
 jwt = JsonWebToken([ALGORITHM])
 
-
-def create_access_token(subject: Union[str, Any]) -> str:
-    """
-    Create a JWT access token.
-
-    Parameters
-    ----------
-    subject : Union[str, Any]
-        Ths subject in the JWT.
-
-    Returns
-    -------
-    token : str
-        The generated JWT.
-
-    """
-    expire = datetime.utcnow() + timedelta(minutes=settings.JWT_TOKEN_EXPIRE_MINUTES)
-    to_encode = {"exp": expire, "sub": str(subject), "iss": ISSUER}
-    encoded_jwt = jwt.encode(header={"alg": ALGORITHM}, payload=to_encode, key=settings.SECRET_KEY)
-    return encoded_jwt.decode("utf-8")
+tracer = trace.get_tracer_provider().get_tracer(__name__)
 
 
-def decode_token(token: str) -> dict[str, str]:
+def decode_token(token: str) -> Dict[str, str]:  # pragma: no cover
     """
     Decode and verify a JWT token.
 
@@ -43,12 +27,12 @@ def decode_token(token: str) -> dict[str, str]:
 
     Returns
     -------
-    decoded_token : dict[str, str]
+    decoded_token : Dict[str, str]
         Payload of the decoded token.
     """
     claims = jwt.decode(
         s=token,
-        key=settings.SECRET_KEY,
+        key=settings.PUBLIC_KEY,
         claims_options={
             "iss": {"essential": True},
             "sub": {"essential": True},
@@ -59,11 +43,34 @@ def decode_token(token: str) -> dict[str, str]:
     return claims
 
 
-oauth = OAuth()
-oauth.register(
-    name="lifescience",
-    client_id=settings.OIDC_CLIENT_ID,
-    client_secret=settings.OIDC_CLIENT_SECRET,
-    server_metadata_url=settings.OIDC_BASE_URI + settings.OIDC_META_INFO_PATH,
-    client_kwargs={"scope": "openid profile aarc", "code_challenge_method": "S256"},
-)
+@start_as_current_span_async("authorization", tracer=tracer)
+async def request_authorization(request_params: AuthzRequest, client: AsyncClient) -> AuthzResponse:
+    """
+    Send a request to OPA for a policy decision. Raise an HTTPException with status 403 if authorization is denied.
+
+    Parameters
+    ----------
+    request_params : app.schemas.security.AuthRequest
+        Input parameters for the authorization request.
+    client : httpx.AsyncClient
+        An async http client with an open connection. This function doesn't close the connection afterwards.
+
+    Returns
+    -------
+    response : app.schemas.security.AuthzResponse
+        Response by the Auth service about the authorization request
+    """
+    current_span = trace.get_current_span()
+    current_span.set_attributes({"resource": request_params.resource, "operation": request_params.operation})
+    response = await client.post(
+        f"{settings.OPA_URI}v1/data{settings.OPA_POLICY_PATH}", json={"input": request_params.model_dump()}
+    )
+
+    parsed_response = AuthzResponse(**response.json())
+    current_span.set_attribute("decision_id", str(parsed_response.decision_id))
+    if not parsed_response.result:  # pragma: no cover
+        raise HTTPException(
+            status_code=status.HTTP_403_FORBIDDEN,
+            detail=f"Action forbidden. Decision ID {parsed_response.decision_id}",
+        )
+    return parsed_response
diff --git a/app/crud/__init__.py b/app/crud/__init__.py
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..e9fad32b2657d1e39304b366d8bdca5601c7bc70 100644
--- a/app/crud/__init__.py
+++ b/app/crud/__init__.py
@@ -0,0 +1,2 @@
+class DuplicateError(Exception):
+    pass
diff --git a/app/crud/crud_bucket.py b/app/crud/crud_bucket.py
index 1f4cb29f53d9f106b19d5c5edfde621e6422b25d..875f304ca832d4fa4f463d7292e7b03f49b9b0ce 100644
--- a/app/crud/crud_bucket.py
+++ b/app/crud/crud_bucket.py
@@ -1,17 +1,37 @@
-from sqlalchemy import func, or_
+from enum import Enum, unique
+from typing import Optional, Sequence
+
+from clowmdb.models import Bucket
+from clowmdb.models import BucketPermission as BucketPermissionDB
+from opentelemetry import trace
+from sqlalchemy import delete, func, or_, select
 from sqlalchemy.ext.asyncio import AsyncSession
-from sqlalchemy.future import select
-from sqlalchemy.orm import joinedload
 
-from app.models.bucket import Bucket
-from app.models.bucket_permission import BucketPermission as BucketPermissionDB
-from app.models.bucket_permission import PermissionEnum
+from app.crud import DuplicateError
+from app.otlp import start_as_current_span_async
 from app.schemas.bucket import BucketIn as BucketInSchema
 
+tracer = trace.get_tracer_provider().get_tracer(__name__)
+
 
 class CRUDBucket:
+    @unique
+    class BucketType(str, Enum):
+        """
+        Enumeration for the type of buckets to fetch from the DB
+
+        OWN: Only fetch buckets that the user owns
+        PERMISSION: Only fetch foreign buckets that the user has access to
+        ALL: Fetch all buckets that the user has access to
+        """
+
+        OWN: str = "OWN"
+        ALL: str = "ALL"
+        PERMISSION: str = "PERMISSION"
+
     @staticmethod
-    async def get(db: AsyncSession, bucket_name: str) -> Bucket | None:
+    @start_as_current_span_async("db_get_bucket", tracer=tracer)
+    async def get(db: AsyncSession, bucket_name: str) -> Optional[Bucket]:
         """
         Get a bucket by its name.
 
@@ -28,13 +48,22 @@ class CRUDBucket:
             Returns the bucket if it exists, None otherwise.
         """
         stmt = select(Bucket).where(Bucket.name == bucket_name)
-        row = await db.execute(stmt)
-        return row.scalar()
+        trace.get_current_span().set_attributes({"bucket_name": bucket_name, "sql_query": str(stmt)})
+        return await db.scalar(stmt)
+
+    @staticmethod
+    @start_as_current_span_async("db_list_all_buckets", tracer=tracer)
+    async def get_all(db: AsyncSession) -> Sequence[Bucket]:
+        stmt = select(Bucket)
+        trace.get_current_span().set_attribute("sql_query", str(stmt))
+        return (await db.scalars(stmt)).all()
 
     @staticmethod
-    async def get_for_user(db: AsyncSession, uid: str) -> list[Bucket]:
+    @start_as_current_span_async("db_list_buckets_for_user", tracer=tracer)
+    async def get_for_user(db: AsyncSession, uid: str, bucket_type: BucketType = BucketType.ALL) -> Sequence[Bucket]:
         """
-        Get all buckets where the given user has READ permissions for.
+        Get all buckets for a user. Depending on the `bucket_type`, the user is either owner of the bucket or has
+        permission for the bucket
 
         Parameters
         ----------
@@ -42,79 +71,89 @@ class CRUDBucket:
             Async database session to perform query on.
         uid : str
             UID of a user.
+        bucket_type : BucketType, default BucketType.ALL
+
 
         Returns
         -------
-        buckets : list[app.models.bucket.Bucket]
+        buckets : List[clowmdb.models.Bucket]
             A list of all buckets where the given user has READ permissions for.
 
         Notes
         -----
-        Creates this SQL Query
-                SELECT bucket.name, bucket.description, bucket.public, bucket.owner_id,
-                       user_1.uid, user_1.display_name
-                FROM bucket LEFT OUTER JOIN user AS user_1 ON user_1.uid = bucket.owner_id
-                WHERE bucket.owner_id = %s OR (EXISTS
-                    (SELECT 1 FROM bucketpermission
-                    WHERE bucket.name = bucketpermission.bucket_name AND bucketpermission.user_id = %s
-                        AND(bucketpermission.permissions = %s OR bucketpermission.permissions = %s)
-                        AND(datediff(now(), bucketpermission.`from`) <= %s OR bucketpermission.`from` IS NULL)
-                        AND(datediff(now(), bucketpermission.`to`) >= %s OR bucketpermission.`to` IS NULL)))
+        SQL Query own buckets:
+        ```
+        SELECT bucket.name, bucket.description, bucket.public, bucket.owner_id
+        FROM bucket
+        WHERE bucket.owner_id = %s
+        ```
+
+        SQL Query all buckets that the user has access to:
+        ```
+        SELECT bucket.name, bucket.description, bucket.public, bucket.owner_id
+        FROM bucket
+        WHERE bucket.owner_id = %s OR (EXISTS
+            (SELECT 1 FROM bucketpermission
+            WHERE bucket.name = bucketpermission.bucket_name AND bucketpermission.user_id = %s
+                AND(UNIX_TIMESTAMP() >= bucketpermission.`from` 0 OR bucketpermission.`from` IS NULL)
+                AND(UNIX_TIMESTAMP() <= bucketpermission.`to` >= 0 OR bucketpermission.`to` IS NULL)))
+        ```
+
+        SQL Query only foreign buckets where user has permission to
+        ```
+        SELECT bucket.name, bucket.description, bucket.public, bucket.owner_id
+        FROM bucket
+        WHERE (EXISTS
+            (SELECT 1 FROM bucketpermission
+            WHERE bucket.name = bucketpermission.bucket_name AND bucketpermission.user_id = %s
+                AND(UNIX_TIMESTAMP() >=  bucketpermission.`from` <= 0 OR bucketpermission.`from` IS NULL)
+                AND(UNIX_TIMESTAMP() <=  bucketpermission.`to` >= 0 OR bucketpermission.`to` IS NULL)))
+        ```
         """
-        stmt = (
-            select(Bucket)
-            .options(joinedload(Bucket.owner))
-            .where(
+        stmt = select(Bucket)
+        if bucket_type == CRUDBucket.BucketType.ALL:
+            stmt = stmt.where(
                 or_(
                     Bucket.owner_id == uid,
                     Bucket.permissions.any(BucketPermissionDB.user_id == uid)
                     .where(
                         or_(
-                            BucketPermissionDB.permissions == PermissionEnum.READ,
-                            BucketPermissionDB.permissions == PermissionEnum.READWRITE,
-                        )
-                    )
-                    .where(
-                        or_(
-                            func.datediff(func.now(), BucketPermissionDB.from_) >= 0,
+                            func.UNIX_TIMESTAMP() >= BucketPermissionDB.from_,
                             BucketPermissionDB.from_ == None,  # noqa:E711
                         )
                     )
                     .where(
                         or_(
-                            func.datediff(func.now(), BucketPermissionDB.to) <= 0,
+                            func.UNIX_TIMESTAMP() <= BucketPermissionDB.to,
                             BucketPermissionDB.to == None,  # noqa:E711
                         )
                     ),
                 )
             )
-        )
-
-        buckets = (await db.execute(stmt)).scalars().all()
-        return buckets
-
-    @staticmethod
-    async def get_own_buckets(db: AsyncSession, uid: str) -> list[Bucket]:
-        """
-        Get all the buckets where the user is the owner of it.
-
-        Parameters
-        ----------
-        db : sqlalchemy.ext.asyncio.AsyncSession
-            Async database session to perform query on.
-        uid : str
-            UID of the user to get the buckets for.
-
-        Returns
-        -------
-        buckets : list[app.models.bucket.Bucket]
-            All the buckets for the given UID.
-        """
-        stmt = select(Bucket).where(Bucket.owner_id == uid)
-        return (await db.execute(stmt)).scalars().all()
+        elif bucket_type == CRUDBucket.BucketType.OWN:
+            stmt = stmt.where(Bucket.owner_id == uid)
+        else:
+            stmt = stmt.where(
+                Bucket.permissions.any(BucketPermissionDB.user_id == uid)
+                .where(
+                    or_(
+                        func.UNIX_TIMESTAMP() >= BucketPermissionDB.from_,
+                        BucketPermissionDB.from_ == None,  # noqa:E711
+                    )
+                )
+                .where(
+                    or_(
+                        func.UNIX_TIMESTAMP() <= BucketPermissionDB.to,
+                        BucketPermissionDB.to == None,  # noqa:E711
+                    )
+                ),
+            )
+        trace.get_current_span().set_attributes({"sql_query": str(stmt), "uid": uid, "bucket_type": bucket_type.name})
+        return (await db.scalars(stmt)).all()
 
     @staticmethod
-    async def create(db: AsyncSession, bucket_in: BucketInSchema, uid: str) -> Bucket | None:
+    @start_as_current_span_async("db_create_bucket", tracer=tracer)
+    async def create(db: AsyncSession, bucket_in: BucketInSchema, uid: str) -> Bucket:
         """
         Create a bucket for a given user.
 
@@ -129,19 +168,22 @@ class CRUDBucket:
 
         Returns
         -------
-        bucket : app.models.bucket.Bucket | None
-            Returns the created bucket. If None then there was a problem, e.g. the name of the bucket is already taken.
+        bucket : clowmdb.models.Bucket
+            Returns the created bucket.
         """
-        bucket = Bucket(**bucket_in.dict(), owner_id=uid)
-        if await CRUDBucket.get(db, bucket.name) is None:
-            db.add(bucket)
-            await db.commit()
-            await db.refresh(bucket)
-            return bucket
-        return None
+        bucket = Bucket(**bucket_in.model_dump(), owner_id=uid)
+        current_span = trace.get_current_span()
+        current_span.set_attribute("bucket_name", bucket.name)
+        if await CRUDBucket.get(db, bucket.name) is not None:
+            raise DuplicateError(f"Bucket {bucket.name} exists already")
+        db.add(bucket)
+        await db.commit()
+        await db.refresh(bucket)
+        return bucket
 
     @staticmethod
-    async def delete(db: AsyncSession, bucket: Bucket) -> None:
+    @start_as_current_span_async("db_delete_bucket", tracer=tracer)
+    async def delete(db: AsyncSession, bucket_name: str) -> None:
         """
         Delete a specific bucket.
 
@@ -149,8 +191,10 @@ class CRUDBucket:
         ----------
         db : sqlalchemy.ext.asyncio.AsyncSession
             Async database session to perform query on.
-        bucket : app.models.bucket.Bucket
-            The bucket to delete.
+        bucket_name : string
+            The name of the bucket to delete.
         """
-        await db.delete(bucket)
+        stmt = delete(Bucket).where(Bucket.name == bucket_name)
+        trace.get_current_span().set_attributes({"sql_query": str(stmt), "bucket_name": bucket_name})
+        await db.execute(stmt)
         await db.commit()
diff --git a/app/crud/crud_bucket_permission.py b/app/crud/crud_bucket_permission.py
index b983c64179cea9fbfcae79c8050a6f8427e957aa..6b32bf6895e9dedffbffc24370aeba9243509a9d 100644
--- a/app/crud/crud_bucket_permission.py
+++ b/app/crud/crud_bucket_permission.py
@@ -1,26 +1,52 @@
-from sqlalchemy import and_
+from enum import Enum, unique
+from typing import List, Optional, Sequence
+
+from clowmdb.models import BucketPermission as BucketPermissionDB
+from opentelemetry import trace
+from sqlalchemy import and_, delete, func, or_, select, update
 from sqlalchemy.ext.asyncio import AsyncSession
-from sqlalchemy.future import select
 from sqlalchemy.orm import joinedload
+from sqlalchemy.sql import Select as SQLSelect
 
+from app.crud import DuplicateError
 from app.crud.crud_bucket import CRUDBucket
 from app.crud.crud_user import CRUDUser
-from app.models.bucket_permission import BucketPermission as BucketPermissionDB
+from app.otlp import start_as_current_span_async
 from app.schemas.bucket_permission import BucketPermissionIn as BucketPermissionSchema
 from app.schemas.bucket_permission import BucketPermissionParameters as BucketPermissionParametersSchema
 
+tracer = trace.get_tracer_provider().get_tracer(__name__)
+
 
 class CRUDBucketPermission:
+    @unique
+    class PermissionStatus(str, Enum):
+        """
+        Status of a bucket permission. Can be either `ACTIVE` or `INACTIVE`. A permission can only get `INACTIVE` if the
+        permission itself has a time limit and the current time is not in the timespan.
+        """
+
+        ACTIVE: str = "ACTIVE"
+        INACTIVE: str = "INACTIVE"
+
     @staticmethod
-    async def get(db: AsyncSession, bucket_name: str, user_id: str) -> BucketPermissionDB | None:
+    @start_as_current_span_async("db_get_bucket_permission", tracer=tracer)
+    async def get(db: AsyncSession, bucket_name: str, uid: str) -> Optional[BucketPermissionDB]:
         stmt = select(BucketPermissionDB).where(
-            and_(BucketPermissionDB.user_id == user_id, BucketPermissionDB.bucket_name == bucket_name)
+            BucketPermissionDB.user_id == uid, BucketPermissionDB.bucket_name == bucket_name
         )
-        row = await db.execute(stmt)
-        return row.scalar()
+        trace.get_current_span().set_attributes({"sql_query": str(stmt), "bucket_name": bucket_name, "uid": uid})
+        return await db.scalar(stmt)
 
     @staticmethod
-    async def get_permissions_for_bucket(db: AsyncSession, bucket_name: str) -> list[BucketPermissionDB]:
+    @start_as_current_span_async("db_list_bucket_permissions", tracer=tracer)
+    async def list(
+        db: AsyncSession,
+        bucket_name: Optional[str] = None,
+        uid: Optional[str] = None,
+        permission_types: Optional[List[BucketPermissionDB.Permission]] = None,
+        permission_status: Optional[PermissionStatus] = None,
+    ) -> Sequence[BucketPermissionDB]:
         """
         Get the permissions for the given bucket.
 
@@ -28,47 +54,45 @@ class CRUDBucketPermission:
         ----------
         db : sqlalchemy.ext.asyncio.AsyncSession.
             Async database session to perform query on.
-        bucket_name : str
+        bucket_name : str | None
             Name of the bucket which to query.
+        uid : str | None
+            UID of the user which to query.
+        permission_types : List[clowmdb.models.BucketPermission.Permission] | None, default None
+            Type of Bucket Permissions to fetch.
+        permission_status : app.crud.crud_bucket_permission.CRUDBucketPermission.PermissionStatus | None, default None
+            Status of Bucket Permissions to fetch.
 
         Returns
         -------
-        buckets : list[BucketPermission]
+        buckets : List[BucketPermission]
             Returns the permissions for the given bucket.
         """
-        stmt = (
-            select(BucketPermissionDB)
-            .options(joinedload(BucketPermissionDB.grantee))
-            .where(BucketPermissionDB.bucket_name == bucket_name)
-        )
-        row = await db.execute(stmt)
-        return row.scalars().all()
-
-    @staticmethod
-    async def get_permissions_for_user(db: AsyncSession, user_id: str) -> list[BucketPermissionDB]:
-        """
-        Get the permissions for the given user.
-
-        Parameters
-        ----------
-        db : sqlalchemy.ext.asyncio.AsyncSession.
-            Async database session to perform query on.
-        user_id : str
-            UID of the user which to query.
+        current_span = trace.get_current_span()
+        stmt = select(BucketPermissionDB)
 
-        Returns
-        -------
-        buckets : list[BucketPermission]
-            Returns the permissions for the given user.
-        """
-        stmt = select(BucketPermissionDB).where(BucketPermissionDB.user_id == user_id)
-        row = await db.execute(stmt)
-        return row.scalars().all()
+        if bucket_name is not None:
+            current_span.set_attribute("bucket_name", bucket_name)
+            stmt = stmt.options(joinedload(BucketPermissionDB.grantee)).where(
+                BucketPermissionDB.bucket_name == bucket_name
+            )
+        if uid is not None:
+            current_span.set_attribute("uid", uid)
+            stmt = stmt.where(BucketPermissionDB.user_id == uid)
+        if permission_types is not None and len(permission_types) > 0:
+            current_span.set_attribute("permission_types", [ptype.name for ptype in permission_types])
+            stmt = stmt.where(or_(*[BucketPermissionDB.permissions == p_type for p_type in permission_types]))
+        if permission_status is not None:
+            current_span.set_attribute("permission_status", permission_status.name)
+            stmt = CRUDBucketPermission._filter_permission_status(stmt, permission_status)
+        current_span.set_attribute("sql_query", str(stmt))
+        return (await db.scalars(stmt)).all()
 
     @staticmethod
-    async def check_permission(db: AsyncSession, bucket_name: str, uid: str, only_own: bool = False) -> bool:
+    @start_as_current_span_async("db_check_bucket_permission", tracer=tracer)
+    async def check_permission(db: AsyncSession, bucket_name: str, uid: str) -> bool:
         """
-        Check if the provided user has READ permission to the provided bucket.
+        Check if the provided user has any permission to the provided bucket.
 
         Parameters
         ----------
@@ -78,18 +102,18 @@ class CRUDBucketPermission:
             Name of the bucket for which to perform the check.
         uid : str
             UID of the user for which to perform the check.
-        only_own : bool, default False
-            Flag if the check is only for the users own buckets or include foreign buckets with permission.
 
         Returns
         -------
         permission_check : bool
-            Return True if the user has READ permission on the bucket, False otherwise.
+            Return True if the user has any permission on the bucket, False otherwise.
         """
-        buckets = await (CRUDBucket.get_own_buckets(db, uid) if only_own else CRUDBucket.get_for_user(db, uid))
+        trace.get_current_span().set_attributes({"uid": uid, "bucket_name": bucket_name})
+        buckets = await CRUDBucket.get_for_user(db, uid, bucket_type=CRUDBucket.BucketType.ALL)
         return bucket_name in map(lambda x: x.name, buckets)
 
     @staticmethod
+    @start_as_current_span_async("db_create_bucket_permission", tracer=tracer)
     async def create(db: AsyncSession, permission: BucketPermissionSchema) -> BucketPermissionDB:
         """
         Create a permission in the database and raise Exceptions if there are problems.
@@ -102,9 +126,10 @@ class CRUDBucketPermission:
             The permission to create.
         Returns
         -------
-        permission : app.models.bucket_permission.BucketPermission
+        permission : clowmdb.models.BucketPermission
             Newly created permission model from the db.
         """
+        trace.get_current_span().set_attributes({"bucket_name": permission.bucket_name, "uid": permission.uid})
         # Check if user exists
         user = await CRUDUser.get(db, uid=permission.uid)
         if user is None:
@@ -116,10 +141,7 @@ class CRUDBucketPermission:
         if bucket is None or bucket.owner_id == user.uid:
             raise ValueError(f"User {permission.uid} is the owner of the bucket {permission.bucket_name}")
         # Check if combination of user and bucket already exists
-        duplicate_check_stmt = select(BucketPermissionDB).where(
-            and_(BucketPermissionDB.user_id == user.uid, BucketPermissionDB.bucket_name == permission.bucket_name)
-        )
-        previous_permission = (await db.execute(duplicate_check_stmt)).scalar()
+        previous_permission = await CRUDBucketPermission.get(db, bucket_name=permission.bucket_name, uid=user.uid)
         if previous_permission is not None:
             raise DuplicateError(
                 f"bucket permission for combination {permission.bucket_name} {permission.uid} already exists."
@@ -139,7 +161,8 @@ class CRUDBucketPermission:
         return permission_db
 
     @staticmethod
-    async def delete(db: AsyncSession, permission: BucketPermissionDB) -> None:
+    @start_as_current_span_async("db_delete_bucket_permission", tracer=tracer)
+    async def delete(db: AsyncSession, bucket_name: str, uid: str) -> None:
         """
         Delete a permission in the database.
 
@@ -147,17 +170,20 @@ class CRUDBucketPermission:
         ----------
         db : sqlalchemy.ext.asyncio.AsyncSession
             Async database session to perform query on.
-        permission : app.schemas.bucket_permission.BucketPermissionOut
-            The permission to create.
-        Returns
-        -------
-        permission : app.models.bucket_permission.BucketPermission
-            Newly created permission model from the db.
+        bucket_name : str
+            Name of the bucket.
+        uid : str
+            UID of the user.
         """
-        await db.delete(permission)
+        stmt = delete(BucketPermissionDB).where(
+            BucketPermissionDB.user_id == uid, BucketPermissionDB.bucket_name == bucket_name
+        )
+        trace.get_current_span().set_attributes({"bucket_name": bucket_name, "uid": uid, "sql_query": str(stmt)})
+        await db.execute(stmt)
         await db.commit()
 
     @staticmethod
+    @start_as_current_span_async("db_update_bucket_permission", tracer=tracer)
     async def update_permission(
         db: AsyncSession, permission: BucketPermissionDB, new_params: BucketPermissionParametersSchema
     ) -> BucketPermissionDB:
@@ -175,14 +201,69 @@ class CRUDBucketPermission:
 
         Returns
         -------
-        permission : app.models.bucket_permission.BucketPermission
+        permission : clowmdb.models.BucketPermission
             Updated permission model from the db.
         """
-        permission.update_parameters(new_params)
+        stmt = (
+            update(BucketPermissionDB)
+            .where(
+                BucketPermissionDB.bucket_name == permission.bucket_name,
+                BucketPermissionDB.user_id == permission.user_id,
+            )
+            .values(
+                from_=new_params.from_timestamp,
+                to=new_params.to_timestamp,
+                file_prefix=new_params.file_prefix,
+                permissions=new_params.permission,
+            )
+        )
+        trace.get_current_span().set_attributes(
+            {"sql_query": str(stmt), "bucket_name": permission.bucket_name, "uid": permission.user_id}
+        )
+        await db.execute(stmt)
         await db.commit()
         await db.refresh(permission)
         return permission
 
+    @staticmethod
+    def _filter_permission_status(stmt: SQLSelect, permission_status: PermissionStatus) -> SQLSelect:
+        """
+        Add a where clauses to the SQL Statement where the status of permission is filtered based on the current time.
+
+        Parameters
+        ----------
+        stmt : sqlalchemy.sql.Select
+            Declarative Select statement from SQLAlchemy
+        permission_status : PermissionStatus
+            Status of Bucket Permissions to filter for.
 
-class DuplicateError(Exception):
-    pass
+        Returns
+        -------
+        stmt : sqlalchemy.sql.Select
+            Declarative Select statement with added where clause.
+        """
+        if permission_status == CRUDBucketPermission.PermissionStatus.ACTIVE:
+            return stmt.where(
+                or_(
+                    func.UNIX_TIMESTAMP() >= BucketPermissionDB.from_,
+                    BucketPermissionDB.from_ == None,  # noqa:E711
+                )
+            ).where(
+                or_(
+                    func.UNIX_TIMESTAMP() <= BucketPermissionDB.to,
+                    BucketPermissionDB.to == None,  # noqa:E711
+                )
+            )
+        else:
+            return stmt.where(
+                or_(
+                    and_(
+                        func.UNIX_TIMESTAMP() <= BucketPermissionDB.from_,
+                        BucketPermissionDB.from_ != None,  # noqa:E711
+                    ),
+                    and_(
+                        func.UNIX_TIMESTAMP() >= BucketPermissionDB.to,
+                        BucketPermissionDB.to != None,  # noqa:E711
+                    ),
+                )
+            )
diff --git a/app/crud/crud_user.py b/app/crud/crud_user.py
index 01244e9c7d546b7b50e5f391b3482a95b268e30b..34151eb1b24567eb0eaf1243c0117c2430e07fba 100644
--- a/app/crud/crud_user.py
+++ b/app/crud/crud_user.py
@@ -1,34 +1,16 @@
+from typing import Optional
+
+from clowmdb.models import User
+from opentelemetry import trace
+from sqlalchemy import select
 from sqlalchemy.ext.asyncio import AsyncSession
-from sqlalchemy.future import select
 
-from app.models.user import User
+tracer = trace.get_tracer_provider().get_tracer(__name__)
 
 
 class CRUDUser:
     @staticmethod
-    async def create(db: AsyncSession, user: User) -> User:
-        """
-        Create a new user in the database.
-
-        Parameters
-        ----------
-        db : sqlalchemy.ext.asyncio.AsyncSession.
-            Async database session to perform query on.
-        user : app.models.user.User
-            The user to create.
-
-        Returns
-        -------
-        user : app.models.user.User
-            The newly created user.
-        """
-        db.add(user)
-        await db.commit()
-        await db.refresh(user)
-        return user
-
-    @staticmethod
-    async def get(db: AsyncSession, uid: str) -> User | None:
+    async def get(db: AsyncSession, uid: str) -> Optional[User]:
         """
         Get a user by its UID.
 
@@ -41,28 +23,10 @@ class CRUDUser:
 
         Returns
         -------
-        user : app.models.user.User | None
+        user : clowmdb.models.User | None
             The user for the given UID if he exists, None otherwise
         """
-        stmt = select(User).where(User.uid == uid)
-        return (await db.execute(stmt)).scalar()
-
-    @staticmethod
-    async def search_for_name(db: AsyncSession, name_substring: str) -> list[User]:
-        """
-        Search for users that contain a specific substring in their name.
-
-        Parameters
-        ----------
-        db : sqlalchemy.ext.asyncio.AsyncSession.
-            Async database session to perform query on.
-        name_substring : str
-            Substring to search for in the name of a user.
-
-        Returns
-        -------
-        users : list[app.models.user.User]
-            List of users which have the given substring in their name.
-        """
-        stmt = select(User).where(User.display_name.contains(name_substring))
-        return (await db.execute(stmt)).scalars().all()
+        with tracer.start_as_current_span("db_get_user") as span:
+            stmt = select(User).where(User.uid == uid)
+            span.set_attribute("sql_query", str(stmt))
+            return await db.scalar(stmt)
diff --git a/app/db/__init__.py b/app/db/__init__.py
deleted file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000
diff --git a/app/db/base.py b/app/db/base.py
deleted file mode 100644
index 92a999350a7d18f09ee8872382f5d40913156ba4..0000000000000000000000000000000000000000
--- a/app/db/base.py
+++ /dev/null
@@ -1,6 +0,0 @@
-# Import all the models, so that Base has them before being
-# imported by Alembic
-from app.db.base_class import Base  # noqa
-from app.models.bucket import Bucket  # noqa
-from app.models.bucket_permission import BucketPermission  # noqa
-from app.models.user import User  # noqa
diff --git a/app/db/base_class.py b/app/db/base_class.py
deleted file mode 100644
index 59be70308cbefd11f1c259799bffc030cac717f0..0000000000000000000000000000000000000000
--- a/app/db/base_class.py
+++ /dev/null
@@ -1,3 +0,0 @@
-from sqlalchemy.orm import declarative_base
-
-Base = declarative_base()
diff --git a/app/db/session.py b/app/db/session.py
deleted file mode 100644
index d5f7127d05fbd43f126095132241b997e4f9abbd..0000000000000000000000000000000000000000
--- a/app/db/session.py
+++ /dev/null
@@ -1,11 +0,0 @@
-from sqlalchemy import create_engine
-from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
-from sqlalchemy.orm import sessionmaker
-
-from app.core.config import settings
-
-engine = create_engine(str(settings.SQLALCHEMY_DATABASE_NORMAL_URI), pool_pre_ping=True)
-SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
-
-engineAsync = create_async_engine(settings.SQLALCHEMY_DATABASE_ASYNC_URI, echo=settings.SQLALCHEMY_VERBOSE_LOGGER)
-SessionAsync = sessionmaker(engineAsync, expire_on_commit=False, class_=AsyncSession, future=True)
diff --git a/app/main.py b/app/main.py
index 0f344427e4120555fbc33257cc71bf8cb1aa84b9..fff58814b75024bda371b0a6ef0bdb8989f72152 100644
--- a/app/main.py
+++ b/app/main.py
@@ -1,19 +1,29 @@
-import uvicorn
-from fastapi import FastAPI
-from fastapi.middleware.cors import CORSMiddleware
+from contextlib import asynccontextmanager
+from hashlib import md5
+from typing import AsyncGenerator
+
+from fastapi import FastAPI, Request, Response, status
+from fastapi.exception_handlers import http_exception_handler, request_validation_exception_handler
+from fastapi.exceptions import RequestValidationError, StarletteHTTPException
 from fastapi.middleware.gzip import GZipMiddleware
-from fastapi.responses import RedirectResponse
+from fastapi.openapi.docs import get_swagger_ui_html
+from fastapi.responses import HTMLResponse, JSONResponse
 from fastapi.routing import APIRoute
-from starlette.middleware.sessions import SessionMiddleware
+from httpx import AsyncClient
+from opentelemetry import trace
+from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
+from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
+from opentelemetry.sdk.resources import SERVICE_NAME, Resource
+from opentelemetry.sdk.trace import TracerProvider
+from opentelemetry.sdk.trace.export import BatchSpanProcessor
+from opentelemetry.trace import Status, StatusCode
 
 from app.api.api import api_router
-from app.api.endpoints.login import LoginException, login_exception_handler
 from app.api.miscellaneous_endpoints import miscellaneous_router
 from app.core.config import settings
 
 description = """
-This is the backend for a new UI which can leverage the additional powerful functionality provided by Ceph in a
-user-friendly manner.
+This is the s3 proxy service from the CloWM Service.
 """
 
 
@@ -21,9 +31,17 @@ def custom_generate_unique_id(route: APIRoute) -> str:
     return f"{route.tags[-1]}-{route.name}"
 
 
+@asynccontextmanager
+async def lifespan(fastapi_app: FastAPI) -> AsyncGenerator[None, None]:  # pragma: no cover
+    # Create a http client once instead for every request and attach it to the app
+    async with AsyncClient() as client:
+        fastapi_app.requests_client = client  # type: ignore[attr-defined]
+        yield
+
+
 app = FastAPI(
-    title="S3-Proxy",
-    version="1.1.1",
+    title="CloWM S3-Proxy Service",
+    version="2.0.0",
     description=description,
     contact={
         "name": "Daniel Goebel",
@@ -33,15 +51,37 @@ app = FastAPI(
     generate_unique_id_function=custom_generate_unique_id,
     # license_info={"name": "MIT", "url": "https://mit-license.org/"},
     root_path=settings.API_PREFIX,
+    openapi_url=None,  # create it manually to enable caching on client side
+    lifespan=lifespan,
 )
+if settings.API_PREFIX:  # pragma: no cover
+    app.servers.insert(0, {"url": settings.API_PREFIX})
+
+if settings.OTLP_GRPC_ENDPOINT is not None and len(settings.OTLP_GRPC_ENDPOINT) > 0:  # pragma: no cover
+    resource = Resource(attributes={SERVICE_NAME: "clowm-s3proxy-service"})
+    provider = TracerProvider(resource=resource)
+    provider.add_span_processor(
+        BatchSpanProcessor(OTLPSpanExporter(endpoint=settings.OTLP_GRPC_ENDPOINT, insecure=True))
+    )
+    trace.set_tracer_provider(provider)
+
+    @app.exception_handler(StarletteHTTPException)
+    async def trace_http_exception_handler(request: Request, exc: StarletteHTTPException) -> Response:
+        current_span = trace.get_current_span()
+        current_span.set_status(Status(StatusCode.ERROR))
+        current_span.record_exception(exc)
+        return await http_exception_handler(request, exc)
+
+    @app.exception_handler(RequestValidationError)
+    async def trace_validation_exception_handler(request: Request, exc: RequestValidationError) -> JSONResponse:
+        current_span = trace.get_current_span()
+        current_span.set_status(Status(StatusCode.ERROR))
+        current_span.record_exception(exc)
+        return await request_validation_exception_handler(request, exc)
 
-# CORS Settings for the API
-app.add_middleware(
-    CORSMiddleware,
-    allow_origins=settings.BACKEND_CORS_ORIGINS,
-    allow_credentials=False,
-    allow_methods=["*"],
-    allow_headers=["*"],
+
+FastAPIInstrumentor.instrument_app(
+    app, excluded_urls="health,docs,openapi.json", tracer_provider=trace.get_tracer_provider()
 )
 
 # Enable gzip compression for large responses
@@ -50,15 +90,30 @@ app.add_middleware(GZipMiddleware, minimum_size=500)
 # Include all routes
 app.include_router(api_router)
 app.include_router(miscellaneous_router)
-app.add_exception_handler(LoginException, login_exception_handler)
 
-app.add_middleware(SessionMiddleware, secret_key=settings.SECRET_KEY)
+
+# manually add Swagger UI route
+async def swagger_ui_html(req: Request) -> HTMLResponse:
+    return get_swagger_ui_html(
+        openapi_url=app.root_path + "/openapi.json",
+        title=app.title + " - Swagger UI",
+        swagger_favicon_url="/favicon.ico",
+    )
+
+
+app.add_route("/docs", swagger_ui_html, include_in_schema=False)
+
+# Hash openapi.json content and ensure the serialization is the same as the one in the route
+openapi_hash = md5(JSONResponse(app.openapi()).body).hexdigest()
 
 
-@app.get("/", response_class=RedirectResponse, tags=["Miscellaneous"], include_in_schema=False)
-def redirect_docs() -> str:
-    return settings.API_PREFIX + "/docs"
+# Create Custom route for OpenAPI schema
+async def openapi(req: Request) -> Response:
+    # If schema on clients side is still valid, return empty Body with 304 response code (client will use cached body)
+    if req.headers.get("If-None-Match") == openapi_hash:
+        return Response(status_code=status.HTTP_304_NOT_MODIFIED)
+    # Return openapi.json with ETag header
+    return JSONResponse(app.openapi(), headers={"ETag": openapi_hash})
 
 
-if __name__ == "__main__":
-    uvicorn.run(app, host="0.0.0.0", port=8000)
+app.add_route("/openapi.json", openapi, include_in_schema=False)
diff --git a/app/models/__init__.py b/app/models/__init__.py
deleted file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000
diff --git a/app/models/bucket.py b/app/models/bucket.py
deleted file mode 100644
index 6c2f0f5f7b0bb0cd2a0a80afd4dc80ba82b53d16..0000000000000000000000000000000000000000
--- a/app/models/bucket.py
+++ /dev/null
@@ -1,38 +0,0 @@
-from typing import TYPE_CHECKING, Any
-
-from sqlalchemy import Boolean, Column, ForeignKey, String
-from sqlalchemy.dialects.mysql import TEXT
-from sqlalchemy.orm import relationship
-
-from app.db.base_class import Base
-
-if TYPE_CHECKING:
-    from .bucket_permission import BucketPermission
-    from .user import User
-
-
-class Bucket(Base):
-    """
-    Database model for a bucket.
-    """
-
-    __tablename__: str = "bucket"
-    name: str = Column(String(63), primary_key=True, index=True, unique=True)
-    description: str = Column(TEXT, nullable=False)
-    public: bool = Column(Boolean(), default=False, server_default="0", nullable=False)
-    owner_id: str = Column(ForeignKey("user.uid"), nullable=True)
-    owner: "User" = relationship("User", back_populates="buckets")
-    permissions: list["BucketPermission"] = relationship(
-        "BucketPermission",
-        back_populates="bucket",
-        cascade="all, delete",
-        passive_deletes=True,
-    )
-
-    __mapper_args__ = {"eager_defaults": True}
-
-    def __eq__(self, other: Any) -> bool:
-        return self.name == other.name if isinstance(other, Bucket) else False
-
-    def __repr__(self) -> str:
-        return f"'Bucket(name={self.name}', owner='{self.owner_id}')"
diff --git a/app/models/bucket_permission.py b/app/models/bucket_permission.py
deleted file mode 100644
index 7b26d80502d8ed1330de9f0ed292f3fd934d9801..0000000000000000000000000000000000000000
--- a/app/models/bucket_permission.py
+++ /dev/null
@@ -1,62 +0,0 @@
-from datetime import datetime
-from enum import Enum, unique
-from typing import TYPE_CHECKING
-
-from sqlalchemy import Column, ForeignKey, String
-from sqlalchemy.dialects.mysql import ENUM, TIMESTAMP
-from sqlalchemy.orm import relationship
-
-from app.db.base_class import Base
-
-if TYPE_CHECKING:
-    from app.schemas.bucket_permission import BucketPermissionParameters
-
-    from .bucket import Bucket
-    from .user import User
-else:
-    BucketPermissionParameters = object
-
-
-@unique
-class PermissionEnum(str, Enum):
-    """
-    Enumeration for the possible permission on a bucket.
-    """
-
-    READ: str = "READ"
-    WRITE: str = "WRITE"
-    READWRITE: str = "READWRITE"
-
-
-class BucketPermission(Base):
-    """
-    Database model for the permission for a user on a bucket.
-    Will be deleted if either the user or the bucket is deleted.
-    """
-
-    __tablename__: str = "bucketpermission"
-    user_id: str = Column(ForeignKey("user.uid", ondelete="CASCADE"), primary_key=True)
-    bucket_name: str = Column(ForeignKey("bucket.name", ondelete="CASCADE"), primary_key=True)
-    from_: datetime | None = Column("from", TIMESTAMP, nullable=True)
-    to: datetime | None = Column(TIMESTAMP, nullable=True)
-    file_prefix: str | None = Column(String(512), nullable=True)
-    permissions: str | PermissionEnum = Column(ENUM(PermissionEnum), default=PermissionEnum.READ, nullable=False)
-    grantee: "User" = relationship("User", back_populates="permissions")
-    bucket: "Bucket" = relationship("Bucket", back_populates="permissions")
-
-    def update_parameters(self, params: BucketPermissionParameters) -> None:  # pragma: no cover
-        """
-        Update the object with the new parameters.
-
-        Parameters
-        ----------
-        params : app.schemas.bucket_permission.BucketPermissionParameters
-            The parameters which should be updated.
-        """
-        self.from_ = params.from_timestamp
-        self.to = params.to_timestamp
-        self.file_prefix = params.file_prefix
-        self.permissions = params.permission
-
-    def __repr__(self) -> str:
-        return f"BucketPermission(uid={self.user_id} bucket_name={self.bucket_name})"
diff --git a/app/models/user.py b/app/models/user.py
deleted file mode 100644
index cebd54af3bdbcbbcac54ae6a631a085c1004ccb6..0000000000000000000000000000000000000000
--- a/app/models/user.py
+++ /dev/null
@@ -1,28 +0,0 @@
-from typing import TYPE_CHECKING, Any
-
-from sqlalchemy import Column, String
-from sqlalchemy.orm import relationship
-
-from app.db.base_class import Base
-
-if TYPE_CHECKING:
-    from .bucket import Bucket
-    from .bucket_permission import BucketPermission
-
-
-class User(Base):
-    """
-    Database model for a user.
-    """
-
-    __tablename__: str = "user"
-    uid: str = Column(String(64), primary_key=True, index=True, unique=True)
-    display_name: str = Column(String(256), nullable=False)
-    buckets: list["Bucket"] = relationship("Bucket", back_populates="owner")
-    permissions: list["BucketPermission"] = relationship("BucketPermission", back_populates="grantee")
-
-    def __eq__(self, other: Any) -> bool:
-        return self.uid == other.uid if isinstance(other, User) else False
-
-    def __repr__(self) -> str:
-        return f"'User(uid={self.uid}', display_name='{self.display_name}')"
diff --git a/app/otlp/__init__.py b/app/otlp/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..40720d76a97378786a6719507c09fdefe318e538
--- /dev/null
+++ b/app/otlp/__init__.py
@@ -0,0 +1,25 @@
+from collections.abc import AsyncGenerator
+from contextlib import asynccontextmanager
+from typing import Any
+
+from opentelemetry.trace import Tracer
+
+
+@asynccontextmanager
+async def start_as_current_span_async(
+    *args: Any,
+    tracer: Tracer,
+    **kwargs: Any,
+) -> AsyncGenerator[None, None]:
+    """Start a new span and set it as the current span.
+
+    Args:
+        *args: Arguments to pass to the tracer.start_as_current_span method
+        tracer: Tracer to use to start the span
+        **kwargs: Keyword arguments to pass to the tracer.start_as_current_span method
+
+    Yields:
+        None
+    """
+    with tracer.start_as_current_span(*args, **kwargs):
+        yield
diff --git a/app/schemas/bucket.py b/app/schemas/bucket.py
index 01fd83fd646b335eec70e8a6f3471eb3f5346258..4e1790d0563eb1b23d692d9bad94183802836328 100644
--- a/app/schemas/bucket.py
+++ b/app/schemas/bucket.py
@@ -1,12 +1,10 @@
-from datetime import datetime
-from typing import TYPE_CHECKING
+import re
+from typing import Optional
 
-from pydantic import BaseModel, Field
+from clowmdb.models import Bucket
+from pydantic import BaseModel, ConfigDict, Field, field_validator
 
-if TYPE_CHECKING:
-    from mypy_boto3_s3.service_resource import ObjectSummary
-else:
-    ObjectSummary = object
+ip_like_regex = re.compile(r"^(\d+\.){3}\d+$")
 
 
 class _BaseBucket(BaseModel):
@@ -16,23 +14,28 @@ class _BaseBucket(BaseModel):
 
     name: str = Field(
         ...,
-        example="test-bucket",
+        examples=["test-bucket"],
         description="Name of the bucket",
         min_length=3,
         max_length=63,
-        regex=r"(?!(^((2(5[0-5]|[0-4]\d)|[01]?\d{1,2})\.){3}(2(5[0-5]|[0-4]\d)|[01]?\d{1,2})$))^[a-z\d][a-z\d.-]{1,61}[a-z\d]$",  # noqa:E501
+        pattern=r"^([a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)*[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$",
+        # https://docs.ceph.com/en/latest/radosgw/s3/bucketops/#constraints
     )
     description: str = Field(
         ...,
-        example="""\
-This is a very long sample description of a bucket and its purpose which has to be more \
-than 126 characters long and is mandatory.
-                             """.strip(),
+        examples=["This is a sample description of a bucket"],
         description="Description of the bucket",
-        min_length=126,
+        min_length=32,
         max_length=2**16,
     )
 
+    @field_validator("name")
+    @classmethod
+    def name_is_not_an_ip_address(cls, name: str) -> str:
+        if ip_like_regex.search(name):
+            raise ValueError("no IP address as bucket name")
+        return name
+
 
 class BucketIn(_BaseBucket):
     """
@@ -45,63 +48,17 @@ class BucketOut(_BaseBucket):
     Schema for answering a request with a bucket.
     """
 
-    created_at: datetime = Field(
-        ...,
-        example=datetime(2022, 1, 1, 0, 0),
-        description="Time when the bucket was created",
-    )
-    owner: str = Field(..., description="UID of the owner", example="28c5353b8bb34984a8bd4169ba94c606")
-    num_objects: int = Field(..., description="Number of Objects in this bucket", example=6)
-    size: int = Field(..., description="Total size of objects in this bucket in bytes", example=3256216)
-
-    class Config:
-        orm_mode = True
-
-
-class S3ObjectMetaInformation(BaseModel):
-    """
-    Schema for the meta-information about a S3 object.
-    """
-
-    key: str = Field(
-        ...,
-        description="Key of the Object in the S3 store",
-        example="test.txt",
-        max_length=512,
-    )
-    bucket: str = Field(
+    created_at: int = Field(
         ...,
-        description="Name of the Bucket in which the object is",
-        example="test-bucket",
-        max_length=256,
+        examples=[1640991600],  # 01.01.2022 00:00
+        description="Time when the bucket was created as UNIX timestamp",
     )
-    content_type: str = Field(..., description="MIME type of the object", example="text/plain")
-    size: int = Field(..., description="Size of the object in Bytes", example=123456)
-    last_modified: datetime = Field(
+    owner: str = Field(..., description="UID of the owner", examples=["28c5353b8bb34984a8bd4169ba94c606"])
+    num_objects: int = Field(..., description="Number of Objects in this bucket", examples=[6])
+    size: int = Field(..., description="Total size of objects in this bucket in bytes", examples=[3256216])
+    owner_constraint: Optional[Bucket.Constraint] = Field(None, description="Constraint for the owner of the bucket")
+    description: str = Field(
         ...,
-        description="Last time the object was modified",
-        example=datetime(2022, 1, 1, 0, 0),
+        description="Description of the bucket",
     )
-
-    @staticmethod
-    def from_native_s3_object(obj: ObjectSummary) -> "S3ObjectMetaInformation":  # pragma: no cover
-        """
-        Create an S3ObjectMetaInformation object from a boto3 S3ObjectSummary object.
-
-        Parameters
-        ----------
-        obj : mypy_boto3_s3.service_resource.ObjectSummary
-            boto3 S3ObjectSummary.
-
-        Returns
-        -------
-        obj : app.schemas.bucket.S3ObjectMetaInformation
-            Converted S3objectMetaInformation.
-        """
-        return S3ObjectMetaInformation(
-            key=obj.key,
-            bucket=obj.bucket_name,
-            size=obj.size,
-            last_modified=obj.last_modified,
-            content_type=obj.Object().content_type,
-        )
+    model_config = ConfigDict(from_attributes=True)
diff --git a/app/schemas/bucket_permission.py b/app/schemas/bucket_permission.py
index 1af02fae5d2daf4a56961250ef2ae6b7f6db5523..e4fa93f6a2c275822da96d22b6e80ef7590cb36b 100644
--- a/app/schemas/bucket_permission.py
+++ b/app/schemas/bucket_permission.py
@@ -1,31 +1,35 @@
 import hashlib
 from datetime import datetime
-from typing import Any
+from typing import Any, Dict, List, Optional, Union
 
+from clowmdb.models import BucketPermission as BucketPermissionDB
 from pydantic import BaseModel, Field
 
-from app.models.bucket_permission import BucketPermission as BucketPermissionDB
-from app.models.bucket_permission import PermissionEnum
-
 
 class BucketPermissionParameters(BaseModel):
     """
     Schema for the parameters of a bucket permission.
     """
 
-    from_timestamp: datetime | None = Field(
-        None, description="Start date of permission", example=datetime(2022, 1, 1, 0, 0)
+    from_timestamp: Optional[int] = Field(
+        None,
+        description="Start date of permission as UNIX timestamp",
+        examples=[1640991600],  # 01.01.2022 00:00
+    )
+    to_timestamp: Optional[int] = Field(
+        None,
+        description="End date of permission as UNIX timestamp",
+        examples=[1640991600],  # 01.01.2022 00:00
     )
-    to_timestamp: datetime | None = Field(
-        None, description="End date of permission", example=datetime(2023, 1, 1, 0, 0)
+    file_prefix: Optional[str] = Field(None, description="Prefix of subfolder", examples=["pseudo/sub/folder/"])
+    permission: Union[BucketPermissionDB.Permission, str] = Field(
+        BucketPermissionDB.Permission.READ, description="Permission", examples=[BucketPermissionDB.Permission.READ]
     )
-    file_prefix: str | None = Field(None, description="Prefix of subfolder", example="pseudo/sub/folder/")
-    permission: PermissionEnum | str = Field(PermissionEnum.READ, description="Permission", example=PermissionEnum.READ)
 
 
 class BucketPermissionIn(BucketPermissionParameters):
-    uid: str = Field(..., description="UID of the grantee", example="28c5353b8bb34984a8bd4169ba94c606")
-    bucket_name: str = Field(..., description="Name of Bucket", example="test-bucket")
+    uid: str = Field(..., description="UID of the grantee", examples=["28c5353b8bb34984a8bd4169ba94c606"])
+    bucket_name: str = Field(..., description="Name of Bucket", examples=["test-bucket"])
 
     def to_hash(self, user_id: str) -> str:
         """
@@ -44,7 +48,7 @@ class BucketPermissionIn(BucketPermissionParameters):
         str_for_id_hash = self.bucket_name + user_id
         return hashlib.md5(str_for_id_hash.encode("utf-8")).hexdigest()
 
-    def map_to_bucket_policy_statement(self, user_id: str) -> list[dict[str, Any]]:
+    def map_to_bucket_policy_statement(self, user_id: str) -> List[Dict[str, Any]]:
         """
         Create a bucket policy statement from the schema and the user_id.\n
         The Sid is unique for every bucket and user combination.
@@ -56,10 +60,10 @@ class BucketPermissionIn(BucketPermissionParameters):
 
         Returns
         -------
-        statements : list[dict[str, Any]]
+        statements : List[Dict[str, Any]]
             Bucket and object permission statements.
         """
-        obj_policy: dict[str, Any] = {
+        obj_policy: Dict[str, Any] = {
             "Sid": self.to_hash(user_id),
             "Effect": "Allow",
             "Principal": {"AWS": f"arn:aws:iam:::user/{self.uid}"},
@@ -67,7 +71,7 @@ class BucketPermissionIn(BucketPermissionParameters):
             "Action": [],
             "Condition": {},
         }
-        bucket_policy: dict[str, Any] = {
+        bucket_policy: Dict[str, Any] = {
             "Sid": self.to_hash(user_id),
             "Effect": "Allow",
             "Principal": {"AWS": f"arn:aws:iam:::user/{self.uid}"},
@@ -75,19 +79,28 @@ class BucketPermissionIn(BucketPermissionParameters):
             "Action": [],
             "Condition": {},
         }
-        if self.permission == PermissionEnum.READ or self.permission == PermissionEnum.READWRITE:
-            bucket_policy["Action"] += ["s3:ListBucket"]
+        bucket_policy["Action"] += ["s3:ListBucket"]
+        if (
+            self.permission == BucketPermissionDB.Permission.READ
+            or self.permission == BucketPermissionDB.Permission.READWRITE
+        ):
             obj_policy["Action"] += ["s3:GetObject"]
-        if self.permission == PermissionEnum.WRITE or self.permission == PermissionEnum.READWRITE:
+        if (
+            self.permission == BucketPermissionDB.Permission.WRITE
+            or self.permission == BucketPermissionDB.Permission.READWRITE
+        ):
             obj_policy["Action"] += ["s3:DeleteObject", "s3:PutObject"]
+            bucket_policy["Action"] += ["s3:DeleteObject"]
         if self.to_timestamp is not None:
+            print(self.to_timestamp)
             obj_policy["Condition"]["DateLessThan"] = {
-                "aws:CurrentTime": self.to_timestamp.strftime("%Y-%m-%dT%H:%M:%SZ")
+                "aws:CurrentTime": datetime.fromtimestamp(self.to_timestamp).strftime("%Y-%m-%dT%H:%M:%SZ")
             }
             bucket_policy["Condition"]["DateLessThan"] = obj_policy["Condition"]["DateLessThan"]
         if self.from_timestamp is not None:
+            print(self.from_timestamp)
             obj_policy["Condition"]["DateGreaterThan"] = {
-                "aws:CurrentTime": self.from_timestamp.strftime("%Y-%m-%dT%H:%M:%SZ")
+                "aws:CurrentTime": datetime.fromtimestamp(self.from_timestamp).strftime("%Y-%m-%dT%H:%M:%SZ")
             }
             bucket_policy["Condition"]["DateGreaterThan"] = obj_policy["Condition"]["DateGreaterThan"]
         if self.file_prefix is not None:
@@ -96,7 +109,7 @@ class BucketPermissionIn(BucketPermissionParameters):
             del bucket_policy["Condition"]
         if len(obj_policy["Condition"]) == 0:
             del obj_policy["Condition"]
-        return [obj_policy] if self.permission == PermissionEnum.WRITE else [obj_policy, bucket_policy]
+        return [obj_policy, bucket_policy]
 
 
 class BucketPermissionOut(BucketPermissionIn):
@@ -104,18 +117,18 @@ class BucketPermissionOut(BucketPermissionIn):
     Schema for the bucket permissions.
     """
 
-    grantee_display_name: str = Field(..., description="Display Name of the grantee", example="Bilbo Baggins")
+    grantee_display_name: str = Field(..., description="Display Name of the grantee", examples=["Bilbo Baggins"])
 
     @staticmethod
     def from_db_model(
-        permission: BucketPermissionDB, uid: str | None = None, grantee_display_name: str | None = None
+        permission: BucketPermissionDB, uid: Optional[str] = None, grantee_display_name: Optional[str] = None
     ) -> "BucketPermissionOut":
         """
         Create a bucket permission schema from the database model.
 
         Parameters
         ----------
-        permission : app.models.bucket_permission.BucketPermission
+        permission : clowmdb.models.BucketPermission
             DB model for the permission.
         uid : str | None, default None
             Sets the uid in the schema. If None it will be taken from the database model.
diff --git a/app/schemas/security.py b/app/schemas/security.py
index e2328cb056603a3f1c7f955f1b5dfea8bbd62da5..3c0f596a45e25b0eaa0ea6b86f961733c0d7e3c7 100644
--- a/app/schemas/security.py
+++ b/app/schemas/security.py
@@ -3,13 +3,33 @@ from datetime import datetime
 from pydantic import BaseModel, Field
 
 
-class JWTToken(BaseModel):
+class AuthzResponse(BaseModel):
+    """Schema for a response from OPA"""
+
+    decision_id: str = Field(
+        ...,
+        description="Decision ID for for the specific decision",
+        examples=["8851dce0-7546-4e81-a89d-111cbec376c1"],
+    )
+    result: bool = Field(..., description="Result of the Authz request")
+
+
+class AuthzRequest(BaseModel):
+    """Schema for a Request to OPA"""
+
+    uid: str = Field(..., description="UID of user", examples=["28c5353b8bb34984a8bd4169ba94c606"])
+    operation: str = Field(..., description="Operation the user wants to perform", examples=["read"])
+    resource: str = Field(..., description="Resource the operation should be performed on", examples=["bucket"])
+
+
+class JWT(BaseModel):
     """
     Schema for a JWT. Only for convenience
     """
 
     exp: datetime
     sub: str
+    raw_token: str
 
 
 class ErrorDetail(BaseModel):
diff --git a/app/schemas/user.py b/app/schemas/user.py
index 950f3d73d42429db4c7bf8d30385787ed79a02e8..c9ca4704b89afe36a3ac3ece46224fc908927d78 100644
--- a/app/schemas/user.py
+++ b/app/schemas/user.py
@@ -1,37 +1,17 @@
 from pydantic import BaseModel, Field
 
 
-class User(BaseModel):
-    """
-    Schema for a user.
-    """
-
-    uid: str = Field(
-        ...,
-        description="ID of the user",
-        example="28c5353b8bb34984a8bd4169ba94c606",
-        max_length=64,
-    )
-    display_name: str = Field(
-        ...,
-        description="Full Name of the user",
-        example="Bilbo Baggins",
-        max_length=256,
-    )
-
-    class Config:
-        orm_mode = True
-
-
 class S3Key(BaseModel):
     """
     Schema for a S3 key associated with a user.
     """
 
-    user: str = Field(..., description="UID of the user of that access key", example="28c5353b8bb34984a8bd4169ba94c606")
-    access_key: str = Field(..., description="ID of the S3 access key", example="CRJ6B037V2ZT4U3W17VC")
+    user: str = Field(
+        ..., description="UID of the user of that access key", examples=["28c5353b8bb34984a8bd4169ba94c606"]
+    )
+    access_key: str = Field(..., description="ID of the S3 access key", examples=["CRJ6B037V2ZT4U3W17VC"])
     secret_key: str = Field(
         ...,
         description="Secret of the S3 access key",
-        example="2F5uNTI1qvt4oAroXV0wWct8rWclL2QvFXKqSqjS",
+        examples=["2F5uNTI1qvt4oAroXV0wWct8rWclL2QvFXKqSqjS"],
     )
diff --git a/app/tests/api/test_bucket_permissions.py b/app/tests/api/test_bucket_permissions.py
index f13b4af39279055e2ea0ce1cdbd5690c6a31210f..e209798f43a5e3629207f47014e90b9078c1384a 100644
--- a/app/tests/api/test_bucket_permissions.py
+++ b/app/tests/api/test_bucket_permissions.py
@@ -1,23 +1,22 @@
 import json
-from datetime import datetime, timedelta
+from datetime import datetime
 
 import pytest
+from clowmdb.models import Bucket, BucketPermission
 from fastapi import status
 from httpx import AsyncClient
+from sqlalchemy import update
 from sqlalchemy.ext.asyncio import AsyncSession
 
-from app.models.bucket import Bucket
-from app.models.bucket_permission import PermissionEnum
-from app.models.user import User
 from app.schemas.bucket_permission import BucketPermissionIn as BucketPermissionSchema
 from app.schemas.bucket_permission import BucketPermissionParameters as BucketPermissionParametersSchema
 from app.tests.utils.bucket import add_permission_for_bucket
-from app.tests.utils.user import get_authorization_headers
+from app.tests.utils.user import UserWithAuthHeader
 from app.tests.utils.utils import json_datetime_converter
 
 
 class _TestBucketPermissionRoutes:
-    base_path = "/permissions/"
+    base_path = "/permissions"
 
 
 class TestBucketPermissionRoutesGet(_TestBucketPermissionRoutes):
@@ -25,7 +24,7 @@ class TestBucketPermissionRoutesGet(_TestBucketPermissionRoutes):
     async def test_get_valid_bucket_permission(
         self,
         client: AsyncClient,
-        user_token_headers: dict[str, str],
+        random_user: UserWithAuthHeader,
         random_bucket_permission_schema: BucketPermissionSchema,
     ) -> None:
         """
@@ -35,14 +34,14 @@ class TestBucketPermissionRoutesGet(_TestBucketPermissionRoutes):
         ----------
         client : httpx.AsyncClient
             HTTP Client to perform the request on. pytest fixture.
-        user_token_headers : dict[str,str]
-            HTTP Headers to authorize the request. pytest fixture.
+        random_user : app.tests.utils.user.UserWithAuthHeader
+            Random user for testing. pytest fixture.
         random_bucket_permission_schema : app.schemas.bucket_permission.BucketPermissionOut
             Bucket permission for a random bucket for testing. pytest fixture.
         """
         response = await client.get(
-            f"{self.base_path}bucket/{random_bucket_permission_schema.bucket_name}/user/{random_bucket_permission_schema.uid}",  # noqa:E501
-            headers=user_token_headers,
+            f"{self.base_path}/bucket/{random_bucket_permission_schema.bucket_name}/user/{random_bucket_permission_schema.uid}",  # noqa:E501
+            headers=random_user.auth_headers,
         )
         assert response.status_code == status.HTTP_200_OK
 
@@ -56,7 +55,7 @@ class TestBucketPermissionRoutesGet(_TestBucketPermissionRoutes):
     async def test_get_bucket_permission_for_unknown_user(
         self,
         client: AsyncClient,
-        user_token_headers: dict[str, str],
+        random_user: UserWithAuthHeader,
         random_bucket_permission_schema: BucketPermissionSchema,
     ) -> None:
         """
@@ -66,20 +65,24 @@ class TestBucketPermissionRoutesGet(_TestBucketPermissionRoutes):
         ----------
         client : httpx.AsyncClient
             HTTP Client to perform the request on. pytest fixture.
-        user_token_headers : dict[str,str]
-            HTTP Headers to authorize the request. pytest fixture.
+        random_user : app.tests.utils.user.UserWithAuthHeader
+            Random user for testing. pytest fixture.
         random_bucket_permission_schema : app.schemas.bucket_permission.BucketPermissionOut
             Bucket permission for a random bucket for testing. pytest fixture.
         """
         response = await client.get(
-            f"{self.base_path}bucket/{random_bucket_permission_schema.bucket_name}/user/ImpossibleUser",
-            headers=user_token_headers,
+            f"{self.base_path}/bucket/{random_bucket_permission_schema.bucket_name}/user/ImpossibleUser",
+            headers=random_user.auth_headers,
         )
         assert response.status_code == status.HTTP_404_NOT_FOUND
 
     @pytest.mark.asyncio
     async def test_get_unknown_bucket_permission(
-        self, client: AsyncClient, user_token_headers: dict[str, str], random_bucket: Bucket, random_second_user: User
+        self,
+        client: AsyncClient,
+        random_user: UserWithAuthHeader,
+        random_bucket: Bucket,
+        random_second_user: UserWithAuthHeader,
     ) -> None:
         """
         Test for getting a bucket permission for an unknown user/bucket combination.
@@ -88,22 +91,25 @@ class TestBucketPermissionRoutesGet(_TestBucketPermissionRoutes):
         ----------
         client : httpx.AsyncClient
             HTTP Client to perform the request on. pytest fixture.
-        user_token_headers : dict[str,str]
-            HTTP Headers to authorize the request. pytest fixture.
-        random_bucket : app.models.bucket.Bucket
+        random_user : app.tests.utils.user.UserWithAuthHeader
+            Random user for testing. pytest fixture.
+        random_bucket : clowmdb.models.Bucket
             Random bucket for testing. pytest fixture.
-        random_second_user : app.models.user.User
+        random_second_user : app.tests.utils.user.UserWithAuthHeader
             Random second user for testing. pytest fixture.
         """
         response = await client.get(
-            f"{self.base_path}bucket/{random_bucket.name}/user/{random_second_user.uid}",
-            headers=user_token_headers,
+            f"{self.base_path}/bucket/{random_bucket.name}/user/{random_second_user.user.uid}",
+            headers=random_user.auth_headers,
         )
         assert response.status_code == status.HTTP_404_NOT_FOUND
 
     @pytest.mark.asyncio
     async def test_get_foreign_bucket_permission_with_permission(
-        self, client: AsyncClient, random_bucket_permission_schema: BucketPermissionSchema, random_second_user: User
+        self,
+        client: AsyncClient,
+        random_bucket_permission_schema: BucketPermissionSchema,
+        random_second_user: UserWithAuthHeader,
     ) -> None:
         """
         Test for getting a bucket permission for a foreign bucket with READ permission for that bucket.
@@ -114,13 +120,12 @@ class TestBucketPermissionRoutesGet(_TestBucketPermissionRoutes):
             HTTP Client to perform the request on. pytest fixture.
         random_bucket_permission_schema : app.schemas.bucket_permission.BucketPermissionOut
             Bucket permission for a random bucket for testing. pytest fixture.
-        random_second_user : app.models.user.User
+        random_second_user : app.tests.utils.user.UserWithAuthHeader
             Random second user for testing. pytest fixture.
         """
-        user_token_headers = get_authorization_headers(random_second_user.uid)
         response = await client.get(
-            f"{self.base_path}bucket/{random_bucket_permission_schema.bucket_name}/user/{random_second_user.uid}",
-            headers=user_token_headers,
+            f"{self.base_path}/bucket/{random_bucket_permission_schema.bucket_name}/user/{random_second_user.user.uid}",
+            headers=random_second_user.auth_headers,
         )
         assert response.status_code == status.HTTP_200_OK
         permission = response.json()
@@ -135,7 +140,7 @@ class TestBucketPermissionRoutesGet(_TestBucketPermissionRoutes):
         db: AsyncSession,
         client: AsyncClient,
         random_bucket_permission_schema: BucketPermissionSchema,
-        random_third_user: User,
+        random_third_user: UserWithAuthHeader,
     ) -> None:
         """
         Test for getting a bucket permission as a grantee for another grantee for the bucket.
@@ -148,20 +153,22 @@ class TestBucketPermissionRoutesGet(_TestBucketPermissionRoutes):
             HTTP Client to perform the request on. pytest fixture.
         random_bucket_permission_schema : app.schemas.bucket_permission.BucketPermissionOut
             Bucket permission for a random bucket for testing. pytest fixture.
-        random_third_user : app.models.user.User
+        random_third_user : app.tests.utils.user.UserWithAuthHeader
             Random third user who has no permissions for the bucket. pytest fixture.
         """
-        await add_permission_for_bucket(db, random_bucket_permission_schema.bucket_name, random_third_user.uid)
-        user_token_headers = get_authorization_headers(random_third_user.uid)
+        await add_permission_for_bucket(db, random_bucket_permission_schema.bucket_name, random_third_user.user.uid)
         response = await client.get(
-            f"{self.base_path}bucket/{random_bucket_permission_schema.bucket_name}/user/{random_bucket_permission_schema.uid}",  # noqa:E501
-            headers=user_token_headers,
+            f"{self.base_path}/bucket/{random_bucket_permission_schema.bucket_name}/user/{random_bucket_permission_schema.uid}",  # noqa:E501
+            headers=random_third_user.auth_headers,
         )
         assert response.status_code == status.HTTP_403_FORBIDDEN
 
     @pytest.mark.asyncio
     async def test_get_bucket_permissions_for_user(
-        self, client: AsyncClient, random_second_user: User, random_bucket_permission_schema: BucketPermissionSchema
+        self,
+        client: AsyncClient,
+        random_second_user: UserWithAuthHeader,
+        random_bucket_permission_schema: BucketPermissionSchema,
     ) -> None:
         """
         Test for getting all bucket permission for a user.
@@ -170,15 +177,14 @@ class TestBucketPermissionRoutesGet(_TestBucketPermissionRoutes):
         ----------
         client : httpx.AsyncClient
             HTTP Client to perform the request on. pytest fixture.
-        random_second_user : app.models.user.User
+        random_second_user : app.tests.utils.user.UserWithAuthHeader
             Random second user for testing. pytest fixture.
         random_bucket_permission_schema : app.schemas.bucket_permission.BucketPermissionOut
             Bucket permission for a random bucket for testing. pytest fixture.
         """
-        user_token_headers = get_authorization_headers(random_second_user.uid)
         response = await client.get(
-            f"{self.base_path}user/{random_bucket_permission_schema.uid}",
-            headers=user_token_headers,
+            f"{self.base_path}/user/{random_bucket_permission_schema.uid}",
+            headers=random_second_user.auth_headers,
         )
         assert response.status_code == status.HTTP_200_OK
         permission_list = response.json()
@@ -192,7 +198,7 @@ class TestBucketPermissionRoutesGet(_TestBucketPermissionRoutes):
     async def test_get_bucket_permissions_for_bucket(
         self,
         client: AsyncClient,
-        user_token_headers: dict[str, str],
+        random_user: UserWithAuthHeader,
         random_bucket_permission_schema: BucketPermissionSchema,
     ) -> None:
         """
@@ -202,14 +208,14 @@ class TestBucketPermissionRoutesGet(_TestBucketPermissionRoutes):
         ----------
         client : httpx.AsyncClient
             HTTP Client to perform the request on. pytest fixture.
-        user_token_headers : dict[str,str]
-            HTTP Headers to authorize the request. pytest fixture.
+        random_user : app.tests.utils.user.UserWithAuthHeader
+            Random user for testing. pytest fixture.
         random_bucket_permission_schema : app.schemas.bucket_permission.BucketPermissionOut
             Bucket permission for a random bucket for testing. pytest fixture.
         """
         response = await client.get(
-            f"{self.base_path}bucket/{random_bucket_permission_schema.bucket_name}",
-            headers=user_token_headers,
+            f"{self.base_path}/bucket/{random_bucket_permission_schema.bucket_name}",
+            headers=random_user.auth_headers,
         )
         assert response.status_code == status.HTTP_200_OK
         permission_list = response.json()
@@ -221,7 +227,10 @@ class TestBucketPermissionRoutesGet(_TestBucketPermissionRoutes):
 
     @pytest.mark.asyncio
     async def test_get_bucket_permissions_for_foreign_bucket(
-        self, client: AsyncClient, random_second_user: User, random_bucket_permission_schema: BucketPermissionSchema
+        self,
+        client: AsyncClient,
+        random_second_user: UserWithAuthHeader,
+        random_bucket_permission_schema: BucketPermissionSchema,
     ) -> None:
         """
         Test for getting all bucket permissions for a foreign bucket with READ permission for that bucket.
@@ -230,15 +239,14 @@ class TestBucketPermissionRoutesGet(_TestBucketPermissionRoutes):
         ----------
         client : httpx.AsyncClient
             HTTP Client to perform the request on. pytest fixture.
-        random_second_user : app.models.user.User
+        random_second_user : app.tests.utils.user.UserWithAuthHeader
             Random second user for testing. pytest fixture.
         random_bucket_permission_schema : app.schemas.bucket_permission.BucketPermissionOut
             Bucket permission for a random bucket for testing. pytest fixture.
         """
-        user_token_headers = get_authorization_headers(random_second_user.uid)
         response = await client.get(
-            f"{self.base_path}bucket/{random_bucket_permission_schema.bucket_name}",
-            headers=user_token_headers,
+            f"{self.base_path}/bucket/{random_bucket_permission_schema.bucket_name}",
+            headers=random_second_user.auth_headers,
         )
         assert response.status_code == status.HTTP_403_FORBIDDEN
 
@@ -246,7 +254,7 @@ class TestBucketPermissionRoutesGet(_TestBucketPermissionRoutes):
 class TestBucketPermissionRoutesCreate(_TestBucketPermissionRoutes):
     @pytest.mark.asyncio
     async def test_create_bucket_permissions_for_unknown_user(
-        self, client: AsyncClient, user_token_headers: dict[str, str], random_bucket: Bucket
+        self, client: AsyncClient, random_user: UserWithAuthHeader, random_bucket: Bucket
     ) -> None:
         """
         Test for creating a bucket permission for an unknown user.
@@ -255,18 +263,18 @@ class TestBucketPermissionRoutesCreate(_TestBucketPermissionRoutes):
         ----------
         client : httpx.AsyncClient
             HTTP Client to perform the request on. pytest fixture.
-        user_token_headers : dict[str,str]
-            HTTP Headers to authorize the request. pytest fixture.
-        random_bucket : app.models.bucket.Bucket
+        random_user : app.tests.utils.user.UserWithAuthHeader
+            Random user for testing. pytest fixture.
+        random_bucket : clowmdb.models.Bucket
             Random bucket for testing. pytest fixture.
         """
         permission = BucketPermissionSchema(bucket_name=random_bucket.name, uid="ImpossibleUser")
-        response = await client.post(self.base_path, headers=user_token_headers, json=permission.dict())
+        response = await client.post(self.base_path, headers=random_user.auth_headers, json=permission.model_dump())
         assert response.status_code == status.HTTP_404_NOT_FOUND
 
     @pytest.mark.asyncio
     async def test_create_bucket_permissions_for_owner(
-        self, client: AsyncClient, user_token_headers: dict[str, str], random_user: User, random_bucket: Bucket
+        self, client: AsyncClient, random_user: UserWithAuthHeader, random_bucket: Bucket
     ) -> None:
         """
         Test for creating a bucket permission for the owner of the bucket.
@@ -275,22 +283,22 @@ class TestBucketPermissionRoutesCreate(_TestBucketPermissionRoutes):
         ----------
         client : httpx.AsyncClient
             HTTP Client to perform the request on. pytest fixture.
-        user_token_headers : dict[str,str]
-            HTTP Headers to authorize the request. pytest fixture.
-        random_user : app.models.user.User
+        random_user : app.tests.utils.user.UserWithAuthHeader
+            Random user for testing. pytest fixture.
+        random_user : app.tests.utils.user.UserWithAuthHeader
             Random user for testing who is owner of the bucket. pytest fixture.
-        random_bucket : app.models.bucket.Bucket
+        random_bucket : clowmdb.models.Bucket
             Random bucket for testing. pytest fixture.
         """
-        permission = BucketPermissionSchema(bucket_name=random_bucket.name, uid=random_user.uid)
-        response = await client.post(self.base_path, headers=user_token_headers, json=permission.dict())
+        permission = BucketPermissionSchema(bucket_name=random_bucket.name, uid=random_user.user.uid)
+        response = await client.post(self.base_path, headers=random_user.auth_headers, json=permission.model_dump())
         assert response.status_code == status.HTTP_400_BAD_REQUEST
 
     @pytest.mark.asyncio
     async def test_create_duplicate_bucket_permissions(
         self,
         client: AsyncClient,
-        user_token_headers: dict[str, str],
+        random_user: UserWithAuthHeader,
         random_bucket_permission_schema: BucketPermissionSchema,
     ) -> None:
         """
@@ -300,20 +308,50 @@ class TestBucketPermissionRoutesCreate(_TestBucketPermissionRoutes):
         ----------
         client : httpx.AsyncClient
             HTTP Client to perform the request on. pytest fixture.
-        user_token_headers : dict[str,str]
-            HTTP Headers to authorize the request. pytest fixture.
+        random_user : app.tests.utils.user.UserWithAuthHeader
+            Random user for testing. pytest fixture.
         random_bucket_permission_schema : app.schemas.bucket_permission.BucketPermissionOut
             Bucket permission for a random bucket for testing. pytest fixture.
         """
         permission = BucketPermissionSchema(
             bucket_name=random_bucket_permission_schema.bucket_name, uid=random_bucket_permission_schema.uid
         )
-        response = await client.post(self.base_path, headers=user_token_headers, json=permission.dict())
+        response = await client.post(self.base_path, headers=random_user.auth_headers, json=permission.model_dump())
         assert response.status_code == status.HTTP_400_BAD_REQUEST
 
+    @pytest.mark.asyncio
+    async def test_create_bucket_permissions_on_foreign_bucket(
+        self,
+        client: AsyncClient,
+        random_second_user: UserWithAuthHeader,
+        random_bucket: Bucket,
+    ) -> None:
+        """
+        Test for creating a valid bucket permission.
+
+        Parameters
+        ----------
+        client : httpx.AsyncClient
+            HTTP Client to perform the request on. pytest fixture.
+        random_second_user : app.tests.utils.user.UserWithAuthHeader
+            Random second user for testing. pytest fixture.
+        random_bucket : clowmdb.models.Bucket
+            Random bucket for testing. pytest fixture.
+        """
+        permission = BucketPermissionSchema(bucket_name=random_bucket.name, uid=random_second_user.user.uid)
+
+        response = await client.post(
+            self.base_path, headers=random_second_user.auth_headers, json=permission.model_dump()
+        )
+        assert response.status_code == status.HTTP_403_FORBIDDEN
+
     @pytest.mark.asyncio
     async def test_create_valid_bucket_permissions(
-        self, client: AsyncClient, user_token_headers: dict[str, str], random_second_user: User, random_bucket: Bucket
+        self,
+        client: AsyncClient,
+        random_user: UserWithAuthHeader,
+        random_second_user: UserWithAuthHeader,
+        random_bucket: Bucket,
     ) -> None:
         """
         Test for creating a valid bucket permission.
@@ -322,21 +360,56 @@ class TestBucketPermissionRoutesCreate(_TestBucketPermissionRoutes):
         ----------
         client : httpx.AsyncClient
             HTTP Client to perform the request on. pytest fixture.
-        user_token_headers : dict[str,str]
-            HTTP Headers to authorize the request. pytest fixture.
-        random_second_user : app.models.user.User
+        random_user : app.tests.utils.user.UserWithAuthHeader
+            Random user for testing. pytest fixture.
+        random_second_user : app.tests.utils.user.UserWithAuthHeader
             Random second user for testing. pytest fixture.
-        random_bucket : app.models.bucket.Bucket
+        random_bucket : clowmdb.models.Bucket
             Random bucket for testing. pytest fixture.
         """
-        permission = BucketPermissionSchema(bucket_name=random_bucket.name, uid=random_second_user.uid)
+        permission = BucketPermissionSchema(bucket_name=random_bucket.name, uid=random_second_user.user.uid)
 
-        response = await client.post(self.base_path, headers=user_token_headers, json=permission.dict())
+        response = await client.post(self.base_path, headers=random_user.auth_headers, json=permission.model_dump())
         assert response.status_code == status.HTTP_201_CREATED
         created_permission = response.json()
-        assert created_permission["uid"] == random_second_user.uid
+        assert created_permission["uid"] == random_second_user.user.uid
         assert created_permission["bucket_name"] == random_bucket.name
-        assert created_permission["grantee_display_name"] == random_second_user.display_name
+        assert created_permission["grantee_display_name"] == random_second_user.user.display_name
+
+    @pytest.mark.asyncio
+    async def test_create_bucket_permissions_on_initial_bucket(
+        self,
+        client: AsyncClient,
+        db: AsyncSession,
+        random_user: UserWithAuthHeader,
+        random_second_user: UserWithAuthHeader,
+        random_bucket: Bucket,
+    ) -> None:
+        """
+        Test for creating a bucket permission on an initial READ Bucket.
+
+        Parameters
+        ----------
+        client : httpx.AsyncClient
+            HTTP Client to perform the request on. pytest fixture.
+        db : sqlalchemy.ext.asyncio.AsyncSession.
+            Async database session to perform query on. pytest fixture.
+        random_user : app.tests.utils.user.UserWithAuthHeader
+            Random user for testing. pytest fixture.
+        random_second_user : app.tests.utils.user.UserWithAuthHeader
+            Random second user for testing. pytest fixture.
+        random_bucket : clowmdb.models.Bucket
+            Random bucket for testing. pytest fixture.
+        """
+        update_stmt = (
+            update(Bucket).where(Bucket.name == random_bucket.name).values(owner_constraint=Bucket.Constraint.READ)
+        )
+        await db.execute(update_stmt)
+        await db.commit()
+        permission = BucketPermissionSchema(bucket_name=random_bucket.name, uid=random_second_user.user.uid)
+
+        response = await client.post(self.base_path, headers=random_user.auth_headers, json=permission.model_dump())
+        assert response.status_code == status.HTTP_403_FORBIDDEN
 
 
 class TestBucketPermissionRoutesDelete(_TestBucketPermissionRoutes):
@@ -344,7 +417,7 @@ class TestBucketPermissionRoutesDelete(_TestBucketPermissionRoutes):
     async def test_delete_bucket_permission_from_owner(
         self,
         client: AsyncClient,
-        user_token_headers: dict[str, str],
+        random_user: UserWithAuthHeader,
         random_bucket_permission_schema: BucketPermissionSchema,
     ) -> None:
         """
@@ -354,20 +427,23 @@ class TestBucketPermissionRoutesDelete(_TestBucketPermissionRoutes):
         ----------
         client : httpx.AsyncClient
             HTTP Client to perform the request on. pytest fixture.
-        user_token_headers : dict[str,str]
-            HTTP Headers to authorize the request. pytest fixture.
+        random_user : app.tests.utils.user.UserWithAuthHeader
+            Random user for testing. pytest fixture.
         random_bucket_permission_schema : app.schemas.bucket_permission.BucketPermissionOut
             Bucket permission for a random bucket for testing. pytest fixture.
         """
         response = await client.delete(
-            f"{self.base_path}bucket/{random_bucket_permission_schema.bucket_name}/user/{random_bucket_permission_schema.uid}",  # noqa:E501
-            headers=user_token_headers,
+            f"{self.base_path}/bucket/{random_bucket_permission_schema.bucket_name}/user/{random_bucket_permission_schema.uid}",  # noqa:E501
+            headers=random_user.auth_headers,
         )
         assert response.status_code == status.HTTP_204_NO_CONTENT
 
     @pytest.mark.asyncio
     async def test_delete_foreign_bucket_permission_with_permission(
-        self, client: AsyncClient, random_second_user: User, random_bucket_permission_schema: BucketPermissionSchema
+        self,
+        client: AsyncClient,
+        random_second_user: UserWithAuthHeader,
+        random_bucket_permission_schema: BucketPermissionSchema,
     ) -> None:
         """
         Test for deleting a bucket permission as a grantee.
@@ -376,15 +452,14 @@ class TestBucketPermissionRoutesDelete(_TestBucketPermissionRoutes):
         ----------
         client : httpx.AsyncClient
             HTTP Client to perform the request on. pytest fixture.
-        random_second_user : app.models.user.User
+        random_second_user : app.tests.utils.user.UserWithAuthHeader
             Random second user for testing. pytest fixture.
         random_bucket_permission_schema : app.schemas.bucket_permission.BucketPermissionOut
             Bucket permission for a random bucket for testing. pytest fixture.
         """
-        user_token_headers = get_authorization_headers(random_second_user.uid)
         response = await client.get(
-            f"{self.base_path}bucket/{random_bucket_permission_schema.bucket_name}/user/{random_bucket_permission_schema.uid}",  # noqa:E501
-            headers=user_token_headers,
+            f"{self.base_path}/bucket/{random_bucket_permission_schema.bucket_name}/user/{random_bucket_permission_schema.uid}",  # noqa:E501
+            headers=random_second_user.auth_headers,
         )
         assert response.status_code == status.HTTP_200_OK
 
@@ -392,7 +467,7 @@ class TestBucketPermissionRoutesDelete(_TestBucketPermissionRoutes):
     async def test_delete_bucket_permission_with_unknown_user(
         self,
         client: AsyncClient,
-        user_token_headers: dict[str, str],
+        random_user: UserWithAuthHeader,
         random_bucket_permission_schema: BucketPermissionSchema,
     ) -> None:
         """
@@ -406,14 +481,18 @@ class TestBucketPermissionRoutesDelete(_TestBucketPermissionRoutes):
             Bucket permission for a random bucket for testing. pytest fixture.
         """
         response = await client.delete(
-            f"{self.base_path}bucket/{random_bucket_permission_schema.bucket_name}/user/ImpossibleUser",
-            headers=user_token_headers,
+            f"{self.base_path}/bucket/{random_bucket_permission_schema.bucket_name}/user/ImpossibleUser",
+            headers=random_user.auth_headers,
         )
         assert response.status_code == status.HTTP_404_NOT_FOUND
 
     @pytest.mark.asyncio
     async def test_delete_bucket_permission_without_permission(
-        self, client: AsyncClient, user_token_headers: dict[str, str], random_bucket: Bucket, random_second_user: User
+        self,
+        client: AsyncClient,
+        random_user: UserWithAuthHeader,
+        random_bucket: Bucket,
+        random_second_user: UserWithAuthHeader,
     ) -> None:
         """
         Test for deleting a bucket permission with an unknown bucket/user combination.
@@ -422,14 +501,14 @@ class TestBucketPermissionRoutesDelete(_TestBucketPermissionRoutes):
         ----------
         client : httpx.AsyncClient
             HTTP Client to perform the request on. pytest fixture.
-        random_bucket : app.models.bucket.Bucket
+        random_bucket : clowmdb.models.Bucket
             Random bucket for testing. pytest fixture.
-        random_second_user : app.models.user.User
+        random_second_user : app.tests.utils.user.UserWithAuthHeader
             Random second user for testing. pytest fixture.
         """
         response = await client.delete(
-            f"{self.base_path}bucket/{random_bucket.name}/user/{random_second_user.uid}",
-            headers=user_token_headers,
+            f"{self.base_path}/bucket/{random_bucket.name}/user/{random_second_user.user.uid}",
+            headers=random_user.auth_headers,
         )
         assert response.status_code == status.HTTP_404_NOT_FOUND
 
@@ -439,7 +518,7 @@ class TestBucketPermissionRoutesDelete(_TestBucketPermissionRoutes):
         db: AsyncSession,
         client: AsyncClient,
         random_bucket_permission_schema: BucketPermissionSchema,
-        random_third_user: User,
+        random_third_user: UserWithAuthHeader,
     ) -> None:
         """
         Test for deleting a bucket permission as a grantee for another grantee for the bucket.
@@ -452,14 +531,13 @@ class TestBucketPermissionRoutesDelete(_TestBucketPermissionRoutes):
             HTTP Client to perform the request on. pytest fixture.
         random_bucket_permission_schema : app.schemas.bucket_permission.BucketPermissionOut
             Bucket permission for a random bucket for testing. pytest fixture.
-        random_third_user : app.models.user.User
+        random_third_user : app.tests.utils.user.UserWithAuthHeader
             Random third user who has no permissions for the bucket. pytest fixture.
         """
-        await add_permission_for_bucket(db, random_bucket_permission_schema.bucket_name, random_third_user.uid)
-        user_token_headers = get_authorization_headers(random_third_user.uid)
+        await add_permission_for_bucket(db, random_bucket_permission_schema.bucket_name, random_third_user.user.uid)
         response = await client.delete(
-            f"{self.base_path}bucket/{random_bucket_permission_schema.bucket_name}/user/{random_bucket_permission_schema.uid}",  # noqa:E501
-            headers=user_token_headers,
+            f"{self.base_path}/bucket/{random_bucket_permission_schema.bucket_name}/user/{random_bucket_permission_schema.uid}",  # noqa:E501
+            headers=random_third_user.auth_headers,
         )
         assert response.status_code == status.HTTP_403_FORBIDDEN
 
@@ -469,7 +547,7 @@ class TestBucketPermissionRoutesUpdate(_TestBucketPermissionRoutes):
     async def test_update_valid_bucket_permission(
         self,
         client: AsyncClient,
-        user_token_headers: dict[str, str],
+        random_user: UserWithAuthHeader,
         random_bucket_permission_schema: BucketPermissionSchema,
     ) -> None:
         """
@@ -482,25 +560,25 @@ class TestBucketPermissionRoutesUpdate(_TestBucketPermissionRoutes):
         random_bucket_permission_schema : app.schemas.bucket_permission.BucketPermissionOut
             Bucket permission for a random bucket for testing. pytest fixture.
         """
-        new_from_time = datetime(2022, 1, 1, 0, 0)
+        new_from_time = round(datetime(2022, 1, 1, 0, 0).timestamp())
         new_params = BucketPermissionParametersSchema(
             from_timestamp=new_from_time,
-            to_timestamp=new_from_time + timedelta(days=1),
-            permission=PermissionEnum.READWRITE,
+            to_timestamp=new_from_time + 86400,  # plus one day
+            permission=BucketPermission.Permission.READWRITE,
             file_prefix="pseudo/folder/",
         )
         response = await client.put(
-            f"{self.base_path}bucket/{random_bucket_permission_schema.bucket_name}/user/{random_bucket_permission_schema.uid}",  # noqa:E501
-            headers=user_token_headers,
-            content=json.dumps(new_params.dict(), default=json_datetime_converter),
+            f"{self.base_path}/bucket/{random_bucket_permission_schema.bucket_name}/user/{random_bucket_permission_schema.uid}",  # noqa:E501
+            headers=random_user.auth_headers,
+            content=json.dumps(new_params.model_dump(), default=json_datetime_converter),
         )
         assert response.status_code == status.HTTP_200_OK
         updated_permission = response.json()
         assert updated_permission["uid"] == random_bucket_permission_schema.uid
         assert updated_permission["bucket_name"] == random_bucket_permission_schema.bucket_name
         if new_params.from_timestamp is not None and new_params.to_timestamp is not None:
-            assert updated_permission["from_timestamp"] == new_params.from_timestamp.strftime("%Y-%m-%dT%H:%M:%S")
-            assert updated_permission["to_timestamp"] == new_params.to_timestamp.strftime("%Y-%m-%dT%H:%M:%S")
+            assert updated_permission["from_timestamp"] == new_params.from_timestamp
+            assert updated_permission["to_timestamp"] == new_params.to_timestamp
         assert updated_permission["permission"] == new_params.permission
         assert updated_permission["file_prefix"] == new_params.file_prefix
 
@@ -508,7 +586,7 @@ class TestBucketPermissionRoutesUpdate(_TestBucketPermissionRoutes):
     async def test_update_unknown_bucket_permission(
         self,
         client: AsyncClient,
-        user_token_headers: dict[str, str],
+        random_user: UserWithAuthHeader,
         random_bucket_permission_schema: BucketPermissionSchema,
     ) -> None:
         """
@@ -522,19 +600,23 @@ class TestBucketPermissionRoutesUpdate(_TestBucketPermissionRoutes):
             Bucket permission for a random bucket for testing. pytest fixture.
         """
         new_params = BucketPermissionParametersSchema(
-            permission=PermissionEnum.READWRITE,
+            permission=BucketPermission.Permission.READWRITE,
             file_prefix="pseudo/folder/",
         )
         response = await client.put(
-            f"{self.base_path}bucket/{random_bucket_permission_schema.bucket_name}/user/impossibleUser",
-            headers=user_token_headers,
-            json=new_params.dict(),
+            f"{self.base_path}/bucket/{random_bucket_permission_schema.bucket_name}/user/impossibleUser",
+            headers=random_user.auth_headers,
+            json=new_params.model_dump(),
         )
         assert response.status_code == status.HTTP_404_NOT_FOUND
 
     @pytest.mark.asyncio
     async def test_update_bucket_permission_without_permission(
-        self, client: AsyncClient, user_token_headers: dict[str, str], random_bucket: Bucket, random_second_user: User
+        self,
+        client: AsyncClient,
+        random_user: UserWithAuthHeader,
+        random_bucket: Bucket,
+        random_second_user: UserWithAuthHeader,
     ) -> None:
         """
         Test for updating a non-existing bucket permission with a valid user.
@@ -543,25 +625,28 @@ class TestBucketPermissionRoutesUpdate(_TestBucketPermissionRoutes):
         ----------
         client : httpx.AsyncClient
             HTTP Client to perform the request on. pytest fixture.
-        random_bucket : app.models.bucket.Bucket
+        random_bucket : clowmdb.models.Bucket
             Random bucket for testing. pytest fixture.
-        random_second_user : app.models.user.User
+        random_second_user : app.tests.utils.user.UserWithAuthHeader
             Random second user for testing. pytest fixture.
         """
         new_params = BucketPermissionParametersSchema(
-            permission=PermissionEnum.READWRITE,
+            permission=BucketPermission.Permission.READWRITE,
             file_prefix="pseudo/folder/",
         )
         response = await client.put(
-            f"{self.base_path}bucket/{random_bucket.name}/user/{random_second_user.uid}",
-            headers=user_token_headers,
-            json=new_params.dict(),
+            f"{self.base_path}/bucket/{random_bucket.name}/user/{random_second_user.user.uid}",
+            headers=random_user.auth_headers,
+            json=new_params.model_dump(),
         )
         assert response.status_code == status.HTTP_404_NOT_FOUND
 
     @pytest.mark.asyncio
     async def test_update_foreign_bucket_permission_with_permission(
-        self, client: AsyncClient, random_bucket_permission_schema: BucketPermissionSchema, random_second_user: User
+        self,
+        client: AsyncClient,
+        random_bucket_permission_schema: BucketPermissionSchema,
+        random_second_user: UserWithAuthHeader,
     ) -> None:
         """
         Test for updating a bucket permission as the grantee of the permission.
@@ -572,24 +657,26 @@ class TestBucketPermissionRoutesUpdate(_TestBucketPermissionRoutes):
             HTTP Client to perform the request on. pytest fixture.
         random_bucket_permission_schema : app.schemas.bucket_permission.BucketPermissionOut
             Bucket permission for a random bucket for testing. pytest fixture.
-        random_second_user : app.models.user.User
+        random_second_user : app.tests.utils.user.UserWithAuthHeader
             Random second user for testing. pytest fixture.
         """
-        user_token_headers = get_authorization_headers(random_second_user.uid)
         new_params = BucketPermissionParametersSchema(
-            permission=PermissionEnum.READWRITE,
+            permission=BucketPermission.Permission.READWRITE,
             file_prefix="pseudo/folder/",
         )
         response = await client.put(
-            f"{self.base_path}bucket/{random_bucket_permission_schema.bucket_name}/user/{random_second_user.uid}",
-            headers=user_token_headers,
-            json=new_params.dict(),
+            f"{self.base_path}/bucket/{random_bucket_permission_schema.bucket_name}/user/{random_second_user.user.uid}",
+            headers=random_second_user.auth_headers,
+            json=new_params.model_dump(),
         )
         assert response.status_code == status.HTTP_403_FORBIDDEN
 
     @pytest.mark.asyncio
     async def test_update_foreign_bucket_permission_without_permission(
-        self, client: AsyncClient, random_bucket_permission_schema: BucketPermissionSchema, random_third_user: User
+        self,
+        client: AsyncClient,
+        random_bucket_permission_schema: BucketPermissionSchema,
+        random_third_user: UserWithAuthHeader,
     ) -> None:
         """
         Test for updating a bucket permission as an unrelated third user.
@@ -600,17 +687,16 @@ class TestBucketPermissionRoutesUpdate(_TestBucketPermissionRoutes):
             HTTP Client to perform the request on. pytest fixture.
         random_bucket_permission_schema : app.schemas.bucket_permission.BucketPermissionOut
             Bucket permission for a random bucket for testing. pytest fixture.
-        random_third_user : app.models.user.User
+        random_third_user : app.tests.utils.user.UserWithAuthHeader
             Random second user for testing. pytest fixture.
         """
-        user_token_headers = get_authorization_headers(random_third_user.uid)
         new_params = BucketPermissionParametersSchema(
-            permission=PermissionEnum.READWRITE,
+            permission=BucketPermission.Permission.READWRITE,
             file_prefix="pseudo/folder/",
         )
         response = await client.put(
-            f"{self.base_path}bucket/{random_bucket_permission_schema.bucket_name}/user/{random_bucket_permission_schema.uid}",  # noqa:E501
-            headers=user_token_headers,
-            json=new_params.dict(),
+            f"{self.base_path}/bucket/{random_bucket_permission_schema.bucket_name}/user/{random_bucket_permission_schema.uid}",  # noqa:E501
+            headers=random_third_user.auth_headers,
+            json=new_params.model_dump(),
         )
         assert response.status_code == status.HTTP_403_FORBIDDEN
diff --git a/app/tests/api/test_buckets.py b/app/tests/api/test_buckets.py
index 887aec5bef667e14bd6c6ed2915f2896035bd091..0e9ab652b008f73b33109ac76d2bf0145be08f2d 100644
--- a/app/tests/api/test_buckets.py
+++ b/app/tests/api/test_buckets.py
@@ -1,30 +1,55 @@
 import pytest
+from clowmdb.models import Bucket, BucketPermission
 from fastapi import status
 from httpx import AsyncClient
 from sqlalchemy.ext.asyncio import AsyncSession
 
 from app.crud.crud_bucket import CRUDBucket
-from app.models.bucket import Bucket
-from app.models.bucket_permission import PermissionEnum
-from app.models.user import User
 from app.schemas.bucket import BucketIn
 from app.tests.mocks.mock_s3_resource import MockS3ServiceResource
 from app.tests.utils.bucket import add_permission_for_bucket
-from app.tests.utils.user import get_authorization_headers
+from app.tests.utils.user import UserWithAuthHeader
 from app.tests.utils.utils import random_lower_string
 
 
 class _TestBucketRoutes:
-    base_path = "/buckets/"
+    base_path = "/buckets"
 
 
 class TestBucketRoutesGet(_TestBucketRoutes):
+    @pytest.mark.asyncio
+    async def test_get_all_buckets(
+        self, client: AsyncClient, random_bucket: Bucket, random_second_user: UserWithAuthHeader
+    ) -> None:
+        """
+        Test for getting all buckets with "list_all" operation.
+
+        Parameters
+        ----------
+        client : httpx.AsyncClient
+            HTTP Client to perform the request on. pytest fixture.
+        random_bucket : clowmdb.models.Bucket
+            Random bucket for testing. pytest fixture.
+        random_second_user : app.tests.utils.user.UserWithAuthHeader
+            Random user for testing. pytest fixture.
+        """
+        response = await client.get(f"{self.base_path}", headers=random_second_user.auth_headers)
+        assert response.status_code == status.HTTP_200_OK
+
+        buckets = response.json()
+
+        assert len(buckets) == 1
+        bucket = buckets[0]
+
+        assert bucket["name"].split(":")[-1] == random_bucket.name
+        assert bucket["owner"] == random_bucket.owner_id
+
     @pytest.mark.asyncio
     async def test_get_own_buckets(
         self,
         client: AsyncClient,
         random_bucket: Bucket,
-        user_token_headers: dict[str, str],
+        random_user: UserWithAuthHeader,
     ) -> None:
         """
         Test for getting the buckets where the user is the owner.
@@ -33,29 +58,30 @@ class TestBucketRoutesGet(_TestBucketRoutes):
         ----------
         client : httpx.AsyncClient
             HTTP Client to perform the request on. pytest fixture.
-        random_bucket : app.models.bucket.Bucket
+        random_bucket : clowmdb.models.Bucket
             Random bucket for testing. pytest fixture.
-        user_token_headers : dict[str,str]
-            HTTP Headers to authorize the request. pytest fixture.
+        random_user : app.tests.utils.user.UserWithAuthHeader
+            Random user for testing. pytest fixture.
         """
-        response = await client.get(f"{self.base_path}", headers=user_token_headers)
+        response = await client.get(
+            f"{self.base_path}", params={"user": random_bucket.owner_id}, headers=random_user.auth_headers
+        )
         assert response.status_code == status.HTTP_200_OK
 
         buckets = response.json()
 
-        assert buckets
         assert len(buckets) == 1
         bucket = buckets[0]
 
-        assert bucket["name"] == random_bucket.name
-        assert bucket["owner"] == random_bucket.owner.uid
+        assert bucket["name"].split(":")[-1] == random_bucket.name
+        assert bucket["owner"] == random_bucket.owner_id
 
     @pytest.mark.asyncio
     async def test_get_bucket_by_name(
         self,
         client: AsyncClient,
         random_bucket: Bucket,
-        user_token_headers: dict[str, str],
+        random_user: UserWithAuthHeader,
     ) -> None:
         """
         Test for getting a bucket by its name where the user is the owner of the bucket.
@@ -64,21 +90,21 @@ class TestBucketRoutesGet(_TestBucketRoutes):
         ----------
         client : httpx.AsyncClient
             HTTP Client to perform the request on. pytest fixture.
-        random_bucket : app.models.bucket.Bucket
+        random_bucket : clowmdb.models.Bucket
             Random bucket for testing. pytest fixture.
-        user_token_headers : dict[str,str]
-            HTTP Headers to authorize the request. pytest fixture.
+        random_user : app.tests.utils.user.UserWithAuthHeader
+            Random user for testing. pytest fixture.
         """
-        response = await client.get(f"{self.base_path}{random_bucket.name}", headers=user_token_headers)
+        response = await client.get(f"{self.base_path}/{random_bucket.name}", headers=random_user.auth_headers)
         assert response.status_code == status.HTTP_200_OK
 
         bucket = response.json()
 
-        assert bucket["name"] == random_bucket.name
+        assert bucket["name"].split(":")[-1] == random_bucket.name
         assert bucket["owner"] == random_bucket.owner.uid
 
     @pytest.mark.asyncio
-    async def test_get_unknown_bucket(self, client: AsyncClient, user_token_headers: dict[str, str]) -> None:
+    async def test_get_unknown_bucket(self, client: AsyncClient, random_user: UserWithAuthHeader) -> None:
         """
         Test for getting an unknown bucket by its name.
 
@@ -86,15 +112,15 @@ class TestBucketRoutesGet(_TestBucketRoutes):
         ----------
         client : httpx.AsyncClient
             HTTP Client to perform the request on. pytest fixture.
-        user_token_headers : dict[str,str]
-            HTTP Headers to authorize the request. pytest fixture.
+        random_user : app.tests.utils.user.UserWithAuthHeader
+            Random user for testing. pytest fixture.
         """
-        response = await client.get(f"{self.base_path}impossible_bucket_name", headers=user_token_headers)
+        response = await client.get(f"{self.base_path}/impossible_bucket_name", headers=random_user.auth_headers)
         assert response.status_code == status.HTTP_404_NOT_FOUND
 
     @pytest.mark.asyncio
     async def test_get_foreign_bucket(
-        self, client: AsyncClient, random_bucket: Bucket, random_second_user: User
+        self, client: AsyncClient, random_bucket: Bucket, random_second_user: UserWithAuthHeader
     ) -> None:
         """
         Test for getting a foreign bucket with permission by its name.
@@ -103,13 +129,12 @@ class TestBucketRoutesGet(_TestBucketRoutes):
         ----------
         client : httpx.AsyncClient
             HTTP Client to perform the request on. pytest fixture.
-        random_bucket : app.models.bucket.Bucket
+        random_bucket : clowmdb.models.Bucket
             Random bucket for testing. pytest fixture.
-        random_second_user : app.models.user.User
+        random_second_user : app.tests.utils.user.UserWithAuthHeader
             Random user which is not the owner of the bucket. pytest fixture.
         """
-        user_token_headers = get_authorization_headers(random_second_user.uid)
-        response = await client.get(f"{self.base_path}{random_bucket.name}", headers=user_token_headers)
+        response = await client.get(f"{self.base_path}/{random_bucket.name}", headers=random_second_user.auth_headers)
         assert response.status_code == status.HTTP_403_FORBIDDEN
 
 
@@ -119,8 +144,7 @@ class TestBucketRoutesCreate(_TestBucketRoutes):
         self,
         db: AsyncSession,
         client: AsyncClient,
-        random_user: User,
-        user_token_headers: dict[str, str],
+        random_user: UserWithAuthHeader,
     ) -> None:
         """
         Test for creating a bucket.
@@ -131,33 +155,31 @@ class TestBucketRoutesCreate(_TestBucketRoutes):
             Async database session to perform query on. pytest fixture.
         client : httpx.AsyncClient
             HTTP Client to perform the request on. pytest fixture.
-        random_user : app.models.user.User
+        random_user : app.tests.utils.user.UserWithAuthHeader
             Random user for testing. pytest fixture.
-        user_token_headers : dict[str,str]
-            HTTP Headers to authorize the request. pytest fixture.
         """
         bucket_info = BucketIn(name=random_lower_string(), description=random_lower_string(127))
-        response = await client.post(f"{self.base_path}", headers=user_token_headers, json=bucket_info.dict())
+        response = await client.post(self.base_path, headers=random_user.auth_headers, json=bucket_info.model_dump())
 
         assert response.status_code == status.HTTP_201_CREATED
         bucket = response.json()
         assert bucket
-        assert bucket["name"] == bucket_info.name
-        assert bucket["owner"] == random_user.uid
+        assert bucket["name"].split(":")[-1] == bucket_info.name
+        assert bucket["owner"] == random_user.user.uid
 
         dbBucket = await CRUDBucket.get(db, bucket_info.name)
         assert dbBucket
         assert dbBucket.name == bucket_info.name
-        assert dbBucket.owner_id == random_user.uid
+        assert dbBucket.owner_id == random_user.user.uid
 
-        await CRUDBucket.delete(db, dbBucket)
+        await CRUDBucket.delete(db, dbBucket.name)
 
     @pytest.mark.asyncio
     async def test_create_duplicated_bucket(
         self,
         client: AsyncClient,
         random_bucket: Bucket,
-        user_token_headers: dict[str, str],
+        random_user: UserWithAuthHeader,
     ) -> None:
         """
         Test for creating a bucket where the name is already taken.
@@ -166,13 +188,13 @@ class TestBucketRoutesCreate(_TestBucketRoutes):
         ----------
         client : httpx.AsyncClient
             HTTP Client to perform the request on. pytest fixture.
-        random_bucket : app.models.bucket.Bucket
+        random_bucket : clowmdb.models.Bucket
             Random bucket for testing. pytest fixture.
-        user_token_headers : dict[str,str]
-            HTTP Headers to authorize the request. pytest fixture.
+        random_user : app.tests.utils.user.UserWithAuthHeader
+            Random user for testing. pytest fixture.
         """
         bucket_info = BucketIn(name=random_bucket.name, description=random_lower_string(127))
-        response = await client.post(f"{self.base_path}", headers=user_token_headers, json=bucket_info.dict())
+        response = await client.post(self.base_path, headers=random_user.auth_headers, json=bucket_info.model_dump())
 
         assert response.status_code == status.HTTP_400_BAD_REQUEST
 
@@ -183,7 +205,7 @@ class TestBucketRoutesDelete(_TestBucketRoutes):
         self,
         client: AsyncClient,
         random_bucket: Bucket,
-        user_token_headers: dict[str, str],
+        random_user: UserWithAuthHeader,
     ) -> None:
         """
         Test for deleting an empty bucket.
@@ -192,20 +214,24 @@ class TestBucketRoutesDelete(_TestBucketRoutes):
         ----------
         client : httpx.AsyncClient
             HTTP Client to perform the request on. pytest fixture.
-        random_bucket : app.models.bucket.Bucket
+        random_bucket : clowmdb.models.Bucket
             Random bucket for testing. pytest fixture.
-        user_token_headers : dict[str,str]
-            HTTP Headers to authorize the request. pytest fixture.
+        random_user : app.tests.utils.user.UserWithAuthHeader
+            Random user for testing. pytest fixture.
         """
         response = await client.delete(
-            f"{self.base_path}{random_bucket.name}", headers=user_token_headers, params={"force_delete": False}
+            f"{self.base_path}/{random_bucket.name}", headers=random_user.auth_headers, params={"force_delete": False}
         )
 
         assert response.status_code == status.HTTP_204_NO_CONTENT
 
     @pytest.mark.asyncio
     async def test_delete_foreign_bucket_with_permission(
-        self, client: AsyncClient, db: AsyncSession, random_user: User, random_second_user: User
+        self,
+        client: AsyncClient,
+        db: AsyncSession,
+        random_user: UserWithAuthHeader,
+        random_second_user: UserWithAuthHeader,
     ) -> None:
         """
         Test for deleting a foreign bucket.
@@ -214,23 +240,24 @@ class TestBucketRoutesDelete(_TestBucketRoutes):
         ----------
         client : httpx.AsyncClient
             HTTP Client to perform the request on. pytest fixture.
-        random_user : app.models.user.User
+        random_user : app.tests.utils.user.UserWithAuthHeader
             Random user for testing. pytest fixture.
-        random_second_user : app.models.user.User
+        random_second_user : app.tests.utils.user.UserWithAuthHeader
             Random user which is not the owner of the bucket. pytest fixture.
         """
         bucket = Bucket(
             name=random_lower_string(),
             description=random_lower_string(127),
-            owner_id=random_second_user.uid,
+            owner_id=random_second_user.user.uid,
         )
         db.add(bucket)
         await db.commit()
-        await add_permission_for_bucket(db, bucket.name, random_user.uid, permission=PermissionEnum.READWRITE)
+        await add_permission_for_bucket(
+            db, bucket.name, random_user.user.uid, permission=BucketPermission.Permission.READWRITE
+        )
 
-        user_token_headers = get_authorization_headers(random_user.uid)
         response = await client.delete(
-            f"{self.base_path}{bucket.name}", headers=user_token_headers, params={"force_delete": False}
+            f"{self.base_path}/{bucket.name}", headers=random_user.auth_headers, params={"force_delete": False}
         )
 
         assert response.status_code == status.HTTP_403_FORBIDDEN
@@ -242,7 +269,7 @@ class TestBucketRoutesDelete(_TestBucketRoutes):
         self,
         client: AsyncClient,
         random_bucket: Bucket,
-        user_token_headers: dict[str, str],
+        random_user: UserWithAuthHeader,
         mock_s3_service: MockS3ServiceResource,
     ) -> None:
         """
@@ -252,16 +279,16 @@ class TestBucketRoutesDelete(_TestBucketRoutes):
         ----------
         client : httpx.AsyncClient
             HTTP Client to perform the request on. pytest fixture.
-        random_bucket : app.models.bucket.Bucket
+        random_bucket : clowmdb.models.Bucket
             Random bucket for testing. pytest fixture.
-        user_token_headers : dict[str,str]
-            HTTP Headers to authorize the request. pytest fixture.
+        random_user : app.tests.utils.user.UserWithAuthHeader
+            Random user for testing. pytest fixture.
         mock_s3_service : app.tests.mocks.mock_s3_resource.MockS3ServiceResource
             Mock S3 Service to manipulate objects. pytest fixture.
         """
         mock_s3_service.create_object_in_bucket(bucket_name=random_bucket.name, key=random_lower_string())
         response = await client.delete(
-            f"{self.base_path}{random_bucket.name}", headers=user_token_headers, params={"force_delete": False}
+            f"{self.base_path}/{random_bucket.name}", headers=random_user.auth_headers, params={"force_delete": False}
         )
 
         assert response.status_code == status.HTTP_400_BAD_REQUEST
@@ -271,7 +298,7 @@ class TestBucketRoutesDelete(_TestBucketRoutes):
         self,
         client: AsyncClient,
         random_bucket: Bucket,
-        user_token_headers: dict[str, str],
+        random_user: UserWithAuthHeader,
         mock_s3_service: MockS3ServiceResource,
     ) -> None:
         """
@@ -281,15 +308,15 @@ class TestBucketRoutesDelete(_TestBucketRoutes):
         ----------
         client : httpx.AsyncClient
             HTTP Client to perform the request on. pytest fixture.
-        random_bucket : app.models.bucket.Bucket
+        random_bucket : clowmdb.models.Bucket
             Random bucket for testing. pytest fixture.
-        user_token_headers : dict[str,str]
-            HTTP Headers to authorize the request. pytest fixture.
+        random_user : app.tests.utils.user.UserWithAuthHeader
+            Random user for testing. pytest fixture.
         mock_s3_service : app.tests.mocks.mock_s3_resource.MockS3ServiceResource
             Mock S3 Service to manipulate objects. pytest fixture.
         """
         mock_s3_service.create_object_in_bucket(bucket_name=random_bucket.name, key=random_lower_string())
         response = await client.delete(
-            f"{self.base_path}{random_bucket.name}", headers=user_token_headers, params={"force_delete": True}
+            f"{self.base_path}/{random_bucket.name}", headers=random_user.auth_headers, params={"force_delete": True}
         )
         assert response.status_code == status.HTTP_204_NO_CONTENT
diff --git a/app/tests/api/test_login.py b/app/tests/api/test_login.py
deleted file mode 100644
index 1ac4e480838875ab10a902c36137a1f9742c9526..0000000000000000000000000000000000000000
--- a/app/tests/api/test_login.py
+++ /dev/null
@@ -1,138 +0,0 @@
-from urllib.parse import parse_qs, urlparse
-
-import pytest
-from fastapi import status
-from httpx import AsyncClient
-from sqlalchemy.ext.asyncio import AsyncSession
-from sqlalchemy.future import select
-
-from app.core.config import settings
-from app.core.security import decode_token
-from app.models.user import User
-from app.tests.mocks.mock_rgw_admin import MockRGWAdmin
-from app.tests.utils.utils import random_lower_string
-
-
-class TestLoginRoute:
-    login_path: str = "/auth/"
-
-    @pytest.mark.asyncio
-    async def test_login_redirect(self, client: AsyncClient) -> None:
-        """
-        Test for the query parameter on the login redirect route.
-
-        Parameters
-        ----------
-        client : httpx.AsyncClient
-            HTTP Client to perform the request on. pytest fixture.
-        """
-        r = await client.get(self.login_path + "login", follow_redirects=False)
-        query = parse_qs(urlparse(r.headers["location"], scheme="http").query)
-        assert r.status_code == status.HTTP_302_FOUND
-        assert "code" in query["response_type"]
-        assert settings.OIDC_CLIENT_ID in query["client_id"]
-        assert "openid" in query["scope"][0].split(" ")
-        assert query["state"]
-
-    @pytest.mark.asyncio
-    async def test_successful_login_with_existing_user(self, client: AsyncClient, random_user: User) -> None:
-        """
-        Test for login callback route with an existing user.
-
-        Parameters
-        ----------
-        client : httpx.AsyncClient
-            HTTP Client to perform the request on. pytest fixture.
-        random_user : app.models.user.User
-            Random user for testing. pytest fixture.
-        """
-        r = await client.get(
-            self.login_path + "callback",
-            params={
-                "voperson_id": random_user.uid,
-                "name": random_user.display_name,
-            },
-            follow_redirects=False,
-        )
-        assert r.status_code == status.HTTP_302_FOUND
-        assert "set-cookie" in r.headers.keys()
-        cookie_header = r.headers["set-cookie"]
-        right_header = None
-        for t in cookie_header.split(";"):
-            if t.startswith("bearer"):
-                right_header = t
-                break
-        assert right_header
-        claim = decode_token(right_header.split("=")[1])
-        assert claim["sub"] == random_user.uid
-
-    @pytest.mark.asyncio
-    async def test_login_with_error(self, client: AsyncClient) -> None:
-        """
-        Test for login callback route with an existing user.
-
-        Parameters
-        ----------
-        client : httpx.AsyncClient
-            HTTP Client to perform the request on. pytest fixture.
-        """
-        r = await client.get(
-            self.login_path + "callback",
-            params={"voperson_id": "", "name": "", "error": True},
-            follow_redirects=False,
-        )
-        assert r.status_code == status.HTTP_302_FOUND
-        assert "set-cookie" in r.headers.keys()
-        assert find_cookie(searched_cookie_name="bearer", cookie_header=r.headers["set-cookie"]) is None
-        assert r.headers["location"].startswith("/?login_error")
-
-    @pytest.mark.asyncio
-    async def test_successful_login_with_non_existing_user(
-        self, client: AsyncClient, mock_rgw_admin: MockRGWAdmin, db: AsyncSession
-    ) -> None:
-        """
-        Test for login callback route with a non-existing user.
-
-        Parameters
-        ----------
-        client : httpx.AsyncClient
-            HTTP Client to perform the request on. pytest fixture.
-        mock_rgw_admin : app.tests.mocks.mock_rgw_admin.MockRGWAdmin
-            Mock RGW admin for Ceph. pytest fixture.
-        db : sqlalchemy.ext.asyncio.AsyncSession.
-            Async database session to perform query on. pytest fixture.
-        """
-        uid = random_lower_string()
-        display_name = f"{random_lower_string(8)} {random_lower_string(8)}"
-        r = await client.get(
-            self.login_path + "callback",
-            params={"voperson_id": uid, "name": display_name},
-            follow_redirects=False,
-        )
-        # Check response and valid/right jwt token
-        assert r.status_code == status.HTTP_302_FOUND
-        assert "set-cookie" in r.headers.keys()
-        cookie = find_cookie(searched_cookie_name="bearer", cookie_header=r.headers["set-cookie"])
-        assert cookie is not None
-        claim = decode_token(cookie.split("=")[1])
-        assert claim["sub"] == uid
-
-        # Check that user is created in RGW
-        assert mock_rgw_admin.get_user(uid)["keys"][0]["user"] == uid
-
-        # Check that user is created in DB
-        db_user = (await db.execute(select(User).where(User.uid == uid))).scalar()
-        assert db_user
-        assert db_user.uid == uid
-
-        # Cleanup
-        await db.delete(db_user)
-        await db.commit()
-        mock_rgw_admin.delete_user(uid)
-
-
-def find_cookie(searched_cookie_name: str, cookie_header: str) -> str | None:
-    for cookie in cookie_header.split(";"):
-        if cookie.startswith(searched_cookie_name):
-            return cookie
-    return None
diff --git a/app/tests/api/test_miscellaneous_enpoints.py b/app/tests/api/test_miscellaneous_enpoints.py
new file mode 100644
index 0000000000000000000000000000000000000000..f86330a37058bd8e07e4b57b208ea53a19eb849d
--- /dev/null
+++ b/app/tests/api/test_miscellaneous_enpoints.py
@@ -0,0 +1,52 @@
+import pytest
+from fastapi import status
+from httpx import AsyncClient
+
+
+class TestHealthRoute:
+    @pytest.mark.asyncio
+    async def test_health_route(self, client: AsyncClient) -> None:
+        """
+        Test service health route
+
+        Parameters
+        ----------
+        client : httpx.AsyncClient
+            HTTP Client to perform the request on.
+        """
+        response = await client.get("/health")
+        assert response.status_code == status.HTTP_200_OK
+        body = response.json()
+        assert body["status"] == "OK"
+
+
+class TestOpenAPIRoute:
+    @pytest.mark.asyncio
+    async def test_openapi_route(self, client: AsyncClient) -> None:
+        """
+        Test for getting the OpenAPI specification and the caching mechanism.
+
+        Parameters
+        ----------
+        client : httpx.AsyncClient
+            HTTP Client to perform the request on.
+        """
+        response1 = await client.get("/openapi.json")
+        assert response1.status_code == status.HTTP_200_OK
+        assert response1.headers.get("ETag") is not None
+
+        response2 = await client.get("/openapi.json", headers={"If-None-Match": response1.headers.get("ETag")})
+        assert response2.status_code == status.HTTP_304_NOT_MODIFIED
+
+    @pytest.mark.asyncio
+    async def test_swagger_ui_route(self, client: AsyncClient) -> None:
+        """
+        Test for getting the Swagger UI.
+
+        Parameters
+        ----------
+        client : httpx.AsyncClient
+            HTTP Client to perform the request on.
+        """
+        response1 = await client.get("/docs")
+        assert response1.status_code == status.HTTP_200_OK
diff --git a/app/tests/api/test_s3_keys.py b/app/tests/api/test_s3_keys.py
index e2148dbe158554bc7de2b6b371be8a672f52f4df..2e060573b551ed8aa6d2a6bd00a327dba7ec77a1 100644
--- a/app/tests/api/test_s3_keys.py
+++ b/app/tests/api/test_s3_keys.py
@@ -2,13 +2,12 @@ import pytest
 from fastapi import status
 from httpx import AsyncClient
 
-from app.models.user import User
 from app.tests.mocks.mock_rgw_admin import MockRGWAdmin
-from app.tests.utils.user import get_authorization_headers
+from app.tests.utils.user import UserWithAuthHeader
 
 
 class _TestS3KeyRoutes:
-    base_path = "/users/"
+    base_path = "/users"
 
 
 class TestS3KeyRoutesGet(_TestS3KeyRoutes):
@@ -16,8 +15,8 @@ class TestS3KeyRoutesGet(_TestS3KeyRoutes):
     async def test_get_s3_keys_for_foreign_user(
         self,
         client: AsyncClient,
-        user_token_headers: dict[str, str],
-        random_second_user: User,
+        random_user: UserWithAuthHeader,
+        random_second_user: UserWithAuthHeader,
     ) -> None:
         """
         Test for getting the S3 keys from a foreign user.
@@ -26,17 +25,23 @@ class TestS3KeyRoutesGet(_TestS3KeyRoutes):
         ----------
         client : httpx.AsyncClient
             HTTP Client to perform the request on. pytest fixture.
-        user_token_headers : dict[str,str]
-            HTTP Headers to authorize the request. pytest fixture.
-        random_second_user : app.models.user.User
+        random_user : app.tests.utils.user.UserWithAuthHeader
+            Random user for testing. pytest fixture.
+        random_second_user : app.tests.utils.user.UserWithAuthHeader
             Random foreign user for testing. pytest fixture.
         """
-        response = await client.get(f"{self.base_path}{random_second_user.uid}/keys", headers=user_token_headers)
+        response = await client.get(
+            f"{self.base_path}/{random_second_user.user.uid}/keys", headers=random_user.auth_headers
+        )
 
         assert response.status_code == status.HTTP_403_FORBIDDEN
 
     @pytest.mark.asyncio
-    async def test_get_s3_keys_for_user(self, client: AsyncClient, random_user: User) -> None:
+    async def test_get_s3_keys_for_user(
+        self,
+        client: AsyncClient,
+        random_user: UserWithAuthHeader,
+    ) -> None:
         """
         Test for getting the S3 keys from a user.
 
@@ -44,20 +49,22 @@ class TestS3KeyRoutesGet(_TestS3KeyRoutes):
         ----------
         client : httpx.AsyncClient
             HTTP Client to perform the request on. pytest fixture.
-        random_user : app.models.user.User
+        random_user : app.tests.utils.user.UserWithAuthHeader
             Random user for testing. pytest fixture.
         """
-        headers = get_authorization_headers(random_user.uid)
-        response = await client.get(f"{self.base_path}{random_user.uid}/keys", headers=headers)
+        response = await client.get(f"{self.base_path}/{random_user.user.uid}/keys", headers=random_user.auth_headers)
         keys = response.json()
         assert response.status_code == status.HTTP_200_OK
         assert isinstance(keys, list)
         assert len(keys) == 1
-        assert keys[0]["user"] == random_user.uid
+        assert keys[0]["user"] == random_user.user.uid
 
     @pytest.mark.asyncio
     async def test_get_specific_s3_key_for_user(
-        self, client: AsyncClient, random_user: User, mock_rgw_admin: MockRGWAdmin
+        self,
+        client: AsyncClient,
+        random_user: UserWithAuthHeader,
+        mock_rgw_admin: MockRGWAdmin,
     ) -> None:
         """
         Test for getting a specific S3 key from a user.
@@ -66,14 +73,15 @@ class TestS3KeyRoutesGet(_TestS3KeyRoutes):
         ----------
         client : httpx.AsyncClient
             HTTP Client to perform the request on. pytest fixture.
-        random_user : app.models.user.User
+        random_user : app.tests.utils.user.UserWithAuthHeader
             Random user for testing. pytest fixture.
         mock_rgw_admin : app.tests.mocks.mock_rgw_admin.MockRGWAdmin
             Mock class for rgwadmin package. pytest fixture.
         """
-        headers = get_authorization_headers(random_user.uid)
-        s3_key = mock_rgw_admin.get_user(uid=random_user.uid)["keys"][0]
-        response = await client.get(f"{self.base_path}{random_user.uid}/keys/{s3_key['access_key']}", headers=headers)
+        s3_key = mock_rgw_admin.get_user(uid=random_user.user.uid)["keys"][0]
+        response = await client.get(
+            f"{self.base_path}/{random_user.user.uid}/keys/{s3_key['access_key']}", headers=random_user.auth_headers
+        )
         response_key = response.json()
         assert response.status_code == status.HTTP_200_OK
         assert response_key["access_key"] == s3_key["access_key"]
@@ -81,7 +89,36 @@ class TestS3KeyRoutesGet(_TestS3KeyRoutes):
         assert response_key["user"] == s3_key["user"]
 
     @pytest.mark.asyncio
-    async def test_get_unknown_s3_key_for_user(self, client: AsyncClient, random_user: User) -> None:
+    async def test_get_specific_s3_key_from_foreign_user(
+        self,
+        client: AsyncClient,
+        random_user: UserWithAuthHeader,
+        random_second_user: UserWithAuthHeader,
+        mock_rgw_admin: MockRGWAdmin,
+    ) -> None:
+        """
+        Test for getting a specific S3 key from a user.
+
+        Parameters
+        ----------
+        client : httpx.AsyncClient
+            HTTP Client to perform the request on. pytest fixture.
+        random_user : app.tests.utils.user.UserWithAuthHeader
+            Random user for testing. pytest fixture.
+        random_second_user : app.tests.utils.user.UserWithAuthHeader
+            Random second user for testing. pytest fixture.
+        mock_rgw_admin : app.tests.mocks.mock_rgw_admin.MockRGWAdmin
+            Mock class for rgwadmin package. pytest fixture.
+        """
+        s3_key = mock_rgw_admin.get_user(uid=random_user.user.uid)["keys"][0]
+        response = await client.get(
+            f"{self.base_path}/{random_user.user.uid}/keys/{s3_key['access_key']}",
+            headers=random_second_user.auth_headers,
+        )
+        assert response.status_code == status.HTTP_403_FORBIDDEN
+
+    @pytest.mark.asyncio
+    async def test_get_unknown_s3_key_for_user(self, client: AsyncClient, random_user: UserWithAuthHeader) -> None:
         """
         Test for getting an unknown S3 keys from a user.
 
@@ -89,18 +126,19 @@ class TestS3KeyRoutesGet(_TestS3KeyRoutes):
         ----------
         client : httpx.AsyncClient
             HTTP Client to perform the request on. pytest fixture.
-        random_user : app.models.user.User
+        random_user : app.tests.utils.user.UserWithAuthHeader
             Random user for testing. pytest fixture.
         """
-        headers = get_authorization_headers(random_user.uid)
-        response = await client.get(f"{self.base_path}{random_user.uid}/keys/impossible_key", headers=headers)
+        response = await client.get(
+            f"{self.base_path}/{random_user.user.uid}/keys/impossible_key", headers=random_user.auth_headers
+        )
         assert response.status_code == status.HTTP_404_NOT_FOUND
 
 
 class TestS3KeyRoutesCreate(_TestS3KeyRoutes):
     @pytest.mark.asyncio
     async def test_create_s3_key_for_user(
-        self, client: AsyncClient, random_user: User, mock_rgw_admin: MockRGWAdmin
+        self, client: AsyncClient, random_user: UserWithAuthHeader, mock_rgw_admin: MockRGWAdmin
     ) -> None:
         """
         Test for getting a specific S3 key from a user.
@@ -109,27 +147,48 @@ class TestS3KeyRoutesCreate(_TestS3KeyRoutes):
         ----------
         client : httpx.AsyncClient
             HTTP Client to perform the request on. pytest fixture.
-        random_user : app.models.user.User
+        random_user : app.tests.utils.user.UserWithAuthHeader
             Random user for testing. pytest fixture.
         mock_rgw_admin : app.tests.mocks.mock_rgw_admin.MockRGWAdmin
             Mock class for rgwadmin package. pytest fixture.
         """
-        headers = get_authorization_headers(random_user.uid)
-        old_s3_key = mock_rgw_admin.get_user(uid=random_user.uid)["keys"][0]
-        response = await client.post(f"{self.base_path}{random_user.uid}/keys", headers=headers)
+        old_s3_key = mock_rgw_admin.get_user(uid=random_user.user.uid)["keys"][0]
+        response = await client.post(f"{self.base_path}/{random_user.user.uid}/keys", headers=random_user.auth_headers)
         new_key = response.json()
 
         assert response.status_code == status.HTTP_201_CREATED
         assert new_key["access_key"] != old_s3_key["access_key"]
-        assert new_key["user"] == random_user.uid
+        assert new_key["user"] == random_user.user.uid
 
-        mock_rgw_admin.remove_key(uid=random_user.uid, access_key=new_key["access_key"])
+        mock_rgw_admin.remove_key(uid=random_user.user.uid, access_key=new_key["access_key"])
+
+    @pytest.mark.asyncio
+    async def test_create_s3_key_for_foreign_user(
+        self, client: AsyncClient, random_user: UserWithAuthHeader, random_second_user: UserWithAuthHeader
+    ) -> None:
+        """
+        Test for getting a specific S3 key from a user.
+
+        Parameters
+        ----------
+        client : httpx.AsyncClient
+            HTTP Client to perform the request on. pytest fixture.
+        random_user : app.tests.utils.user.UserWithAuthHeader
+            Random user for testing. pytest fixture.
+        random_second_user : app.tests.utils.user.UserWithAuthHeader
+            Random second user for testing. pytest fixture.
+        """
+        response = await client.post(
+            f"{self.base_path}/{random_second_user.user.uid}/keys", headers=random_user.auth_headers
+        )
+
+        assert response.status_code == status.HTTP_403_FORBIDDEN
 
 
 class TestS3KeyRoutesDelete(_TestS3KeyRoutes):
     @pytest.mark.asyncio
     async def test_delete_s3_key_for_user(
-        self, client: AsyncClient, random_user: User, mock_rgw_admin: MockRGWAdmin
+        self, client: AsyncClient, random_user: UserWithAuthHeader, mock_rgw_admin: MockRGWAdmin
     ) -> None:
         """
         Test for deleting a specific S3 key from a user.
@@ -138,24 +197,23 @@ class TestS3KeyRoutesDelete(_TestS3KeyRoutes):
         ----------
         client : httpx.AsyncClient
             HTTP Client to perform the request on. pytest fixture.
-        random_user : app.models.user.User
+        random_user : app.tests.utils.user.UserWithAuthHeader
             Random user for testing. pytest fixture.
         mock_rgw_admin : app.tests.mocks.mock_rgw_admin.MockRGWAdmin
             Mock class for rgwadmin package. pytest fixture.
         """
-        headers = get_authorization_headers(random_user.uid)
-        new_s3_key = mock_rgw_admin.create_key(uid=random_user.uid)[-1]
-        assert len(mock_rgw_admin.get_user(uid=random_user.uid)["keys"]) == 2
+        new_s3_key = mock_rgw_admin.create_key(uid=random_user.user.uid)[-1]
+        assert len(mock_rgw_admin.get_user(uid=random_user.user.uid)["keys"]) == 2
         response = await client.delete(
-            f"{self.base_path}{random_user.uid}/keys/{new_s3_key['access_key']}", headers=headers
+            f"{self.base_path}/{random_user.user.uid}/keys/{new_s3_key['access_key']}", headers=random_user.auth_headers
         )
 
         assert response.status_code == status.HTTP_204_NO_CONTENT
-        assert len(mock_rgw_admin.get_user(uid=random_user.uid)["keys"]) == 1
+        assert len(mock_rgw_admin.get_user(uid=random_user.user.uid)["keys"]) == 1
 
     @pytest.mark.asyncio
     async def test_delete_last_s3_key_for_user(
-        self, client: AsyncClient, random_user: User, mock_rgw_admin: MockRGWAdmin
+        self, client: AsyncClient, random_user: UserWithAuthHeader, mock_rgw_admin: MockRGWAdmin
     ) -> None:
         """
         Test for deleting the last S3 key from a user.
@@ -164,22 +222,23 @@ class TestS3KeyRoutesDelete(_TestS3KeyRoutes):
         ----------
         client : httpx.AsyncClient
             HTTP Client to perform the request on. pytest fixture.
-        random_user : app.models.user.User
+        random_user : app.tests.utils.user.UserWithAuthHeader
             Random user for testing. pytest fixture.
         mock_rgw_admin : app.tests.mocks.mock_rgw_admin.MockRGWAdmin
             Mock class for rgwadmin package. pytest fixture.
         """
-        headers = get_authorization_headers(random_user.uid)
-        assert len(mock_rgw_admin.get_user(uid=random_user.uid)["keys"]) == 1
-        key_id = mock_rgw_admin.get_user(uid=random_user.uid)["keys"][0]
-        response = await client.delete(f"{self.base_path}{random_user.uid}/keys/{key_id}", headers=headers)
+        assert len(mock_rgw_admin.get_user(uid=random_user.user.uid)["keys"]) == 1
+        key_id = mock_rgw_admin.get_user(uid=random_user.user.uid)["keys"][0]
+        response = await client.delete(
+            f"{self.base_path}/{random_user.user.uid}/keys/{key_id}", headers=random_user.auth_headers
+        )
 
         assert response.status_code == status.HTTP_400_BAD_REQUEST
-        assert len(mock_rgw_admin.get_user(uid=random_user.uid)["keys"]) == 1
+        assert len(mock_rgw_admin.get_user(uid=random_user.user.uid)["keys"]) == 1
 
     @pytest.mark.asyncio
     async def test_delete_unknown_s3_key_for_user(
-        self, client: AsyncClient, random_user: User, mock_rgw_admin: MockRGWAdmin
+        self, client: AsyncClient, random_user: UserWithAuthHeader, mock_rgw_admin: MockRGWAdmin
     ) -> None:
         """
         Test for deleting an unknown S3 key from a user.
@@ -188,12 +247,13 @@ class TestS3KeyRoutesDelete(_TestS3KeyRoutes):
         ----------
         client : httpx.AsyncClient
             HTTP Client to perform the request on. pytest fixture.
-        random_user : app.models.user.User
+        random_user : app.tests.utils.user.UserWithAuthHeader
             Random user for testing. pytest fixture.
         mock_rgw_admin : app.tests.mocks.mock_rgw_admin.MockRGWAdmin
             Mock class for rgwadmin package. pytest fixture.
         """
-        headers = get_authorization_headers(random_user.uid)
-        mock_rgw_admin.create_key(uid=random_user.uid)
-        response = await client.delete(f"{self.base_path}{random_user.uid}/keys/impossible", headers=headers)
+        mock_rgw_admin.create_key(uid=random_user.user.uid)
+        response = await client.delete(
+            f"{self.base_path}/{random_user.user.uid}/keys/impossible", headers=random_user.auth_headers
+        )
         assert response.status_code == status.HTTP_404_NOT_FOUND
diff --git a/app/tests/api/test_s3_objects.py b/app/tests/api/test_s3_objects.py
deleted file mode 100644
index f7b2ee3cfe4bc42bdff47f261fd769776eb19d0a..0000000000000000000000000000000000000000
--- a/app/tests/api/test_s3_objects.py
+++ /dev/null
@@ -1,196 +0,0 @@
-import pytest
-from fastapi import status
-from httpx import AsyncClient
-from sqlalchemy.ext.asyncio import AsyncSession
-
-from app.models.bucket import Bucket
-from app.models.bucket_permission import BucketPermission, PermissionEnum
-from app.models.user import User
-from app.tests.mocks.mock_s3_resource import MockS3ServiceResource
-from app.tests.utils.user import get_authorization_headers
-from app.tests.utils.utils import random_lower_string
-
-
-class _TestS3ObjectsRoutes:
-    base_path = "/buckets/"
-
-
-class TestS3ObjectsRoutesGet(_TestS3ObjectsRoutes):
-    @pytest.mark.asyncio
-    async def test_get_objects_with_right_for_specific_prefix(
-        self,
-        db: AsyncSession,
-        client: AsyncClient,
-        random_bucket: Bucket,
-        random_second_user: User,
-        mock_s3_service: MockS3ServiceResource,
-    ) -> None:
-        """
-        Test for getting the list of S3 objects in a bucket while only having rights for a specific prefix.
-
-        Parameters
-        ----------
-        db : sqlalchemy.ext.asyncio.AsyncSession.
-            Async database session to perform query on. pytest fixture.
-        client : httpx.AsyncClient
-            HTTP Client to perform the request on. pytest fixture.
-        random_bucket : app.models.bucket.Bucket
-            Random bucket for testing. pytest fixture.
-        random_second_user : app.models.user.User
-            Random second user for testing. pytest fixture.
-        mock_s3_service : app.tests.mocks.mock_s3_resource.MockS3ServiceResource
-            Mock S3 Service to manipulate objects. pytest fixture.
-        """
-        user_token_headers = get_authorization_headers(random_second_user.uid)
-        mock_s3_service.create_object_in_bucket(bucket_name=random_bucket.name, key=random_lower_string())
-        obj = mock_s3_service.create_object_in_bucket(
-            bucket_name=random_bucket.name, key="pseudo/folder/" + random_lower_string()
-        )
-        permission = BucketPermission(
-            bucket_name=random_bucket.name,
-            user_id=random_second_user.uid,
-            permissions=PermissionEnum.READ,
-            file_prefix="pseudo/folder/",
-        )
-        db.add(permission)
-        await db.commit()
-        response = await client.get(f"{self.base_path}{random_bucket.name}/objects", headers=user_token_headers)
-        assert response.status_code == status.HTTP_200_OK
-        response_obj_list = response.json()
-        assert len(response_obj_list) == 1
-        assert response_obj_list[0]["key"] == obj.key
-
-    @pytest.mark.asyncio
-    async def test_get_object_without_right_for_specific_prefix(
-        self,
-        db: AsyncSession,
-        client: AsyncClient,
-        random_bucket: Bucket,
-        random_second_user: User,
-        mock_s3_service: MockS3ServiceResource,
-    ) -> None:
-        """
-        Test for getting a specific S3 object in a bucket while not having rights for the prefix.
-
-        Parameters
-        ----------
-        db : sqlalchemy.ext.asyncio.AsyncSession.
-            Async database session to perform query on. pytest fixture.
-        client : httpx.AsyncClient
-            HTTP Client to perform the request on. pytest fixture.
-        random_bucket : app.models.bucket.Bucket
-            Random bucket for testing. pytest fixture.
-        random_second_user : app.models.user.User
-            Random second user for testing. pytest fixture.
-        mock_s3_service : app.tests.mocks.mock_s3_resource.MockS3ServiceResource
-            Mock S3 Service to manipulate objects. pytest fixture.
-        """
-        user_token_headers = get_authorization_headers(random_second_user.uid)
-        obj = mock_s3_service.create_object_in_bucket(
-            bucket_name=random_bucket.name, key="another/folder/" + random_lower_string()
-        )
-        permission = BucketPermission(
-            bucket_name=random_bucket.name,
-            user_id=random_second_user.uid,
-            permissions=PermissionEnum.READ,
-            file_prefix="pseudo/folder/",
-        )
-        db.add(permission)
-        await db.commit()
-        response = await client.get(
-            f"{self.base_path}{random_bucket.name}/objects/{obj.key}", headers=user_token_headers
-        )
-        assert response.status_code == status.HTTP_403_FORBIDDEN
-
-    @pytest.mark.asyncio
-    async def test_get_all_s3_object_from_bucket(
-        self,
-        client: AsyncClient,
-        random_bucket: Bucket,
-        user_token_headers: dict[str, str],
-        mock_s3_service: MockS3ServiceResource,
-    ) -> None:
-        """
-        Test for getting the list of S3 objects in a bucket.
-
-        Parameters
-        ----------
-        client : httpx.AsyncClient
-            HTTP Client to perform the request on. pytest fixture.
-        random_bucket : app.models.bucket.Bucket
-            Random bucket for testing. pytest fixture.
-        user_token_headers : dict[str,str]
-            HTTP Headers to authorize the request. pytest fixture.
-        mock_s3_service : app.tests.mocks.mock_s3_resource.MockS3ServiceResource
-            Mock S3 Service to manipulate objects. pytest fixture.
-        """
-        # Create MockS3ObjectSummary in mock service
-        obj = mock_s3_service.create_object_in_bucket(bucket_name=random_bucket.name, key=random_lower_string())
-
-        response = await client.get(f"{self.base_path}{random_bucket.name}/objects", headers=user_token_headers)
-        response_obj_list = response.json()
-
-        assert response.status_code == status.HTTP_200_OK
-        assert isinstance(response_obj_list, list)
-        assert len(response_obj_list) > 0
-        assert len(response_obj_list) == len(mock_s3_service.Bucket(name=random_bucket.name).get_objects())
-
-        response_obj = response_obj_list[0]
-        assert response_obj
-        assert response_obj["key"] == obj.key
-        assert response_obj["bucket"] == obj.bucket_name
-
-    @pytest.mark.asyncio
-    async def test_get_unknown_s3_object(
-        self, client: AsyncClient, random_bucket: Bucket, user_token_headers: dict[str, str]
-    ) -> None:
-        """
-        Test for getting an unknown object from a bucket.
-
-        Parameters
-        ----------
-        client : httpx.AsyncClient
-            HTTP Client to perform the request on. pytest fixture.
-        random_bucket : app.models.bucket.Bucket
-            Random bucket for testing. pytest fixture.
-        user_token_headers : dict[str,str]
-            HTTP Headers to authorize the request. pytest fixture.
-        """
-        response = await client.get(
-            f"{self.base_path}{random_bucket.name}/objects/impossible.pdf", headers=user_token_headers
-        )
-
-        assert response.status_code == status.HTTP_404_NOT_FOUND
-
-    @pytest.mark.asyncio
-    async def test_get_s3_object(
-        self,
-        client: AsyncClient,
-        random_bucket: Bucket,
-        user_token_headers: dict[str, str],
-        mock_s3_service: MockS3ServiceResource,
-    ) -> None:
-        """
-        Test for getting a specific object from a bucket.
-
-        Parameters
-        ----------
-        client : httpx.AsyncClient
-            HTTP Client to perform the request on. pytest fixture.
-        random_bucket : app.models.bucket.Bucket
-            Random bucket for testing. pytest fixture.
-        user_token_headers : dict[str,str]
-            HTTP Headers to authorize the request. pytest fixture.
-        mock_s3_service : app.tests.mocks.mock_s3_resource.MockS3ServiceResource
-            Mock S3 Service to manipulate objects. pytest fixture.
-        """
-        # Create MockS3ObjectSummary in mock service
-        obj = mock_s3_service.create_object_in_bucket(bucket_name=random_bucket.name, key=random_lower_string())
-        response = await client.get(
-            f"{self.base_path}{random_bucket.name}/objects/{obj.key}", headers=user_token_headers
-        )
-        response_obj = response.json()
-        assert response.status_code == status.HTTP_200_OK
-        assert response_obj
-        assert response_obj["key"] == obj.key
-        assert response_obj["bucket"] == obj.bucket_name
diff --git a/app/tests/api/test_security.py b/app/tests/api/test_security.py
index 72115b29de09253932491eca10b5b80e71fd9d9f..6b7b1fea50703cab3f179fb48e0868b63e3b9413 100644
--- a/app/tests/api/test_security.py
+++ b/app/tests/api/test_security.py
@@ -3,11 +3,11 @@ from fastapi import status
 from httpx import AsyncClient
 from sqlalchemy.ext.asyncio import AsyncSession
 
-from app.models.user import User
+from app.tests.utils.user import UserWithAuthHeader
 
 
 class TestJWTProtectedRoutes:
-    protected_route: str = "/users/me"
+    protected_route: str = "/buckets"
 
     @pytest.mark.asyncio
     async def test_missing_authorization_header(self, client: AsyncClient) -> None:
@@ -42,7 +42,7 @@ class TestJWTProtectedRoutes:
         assert error["detail"] == "Malformed JWT"
 
     @pytest.mark.asyncio
-    async def test_correct_authorization_header(self, client: AsyncClient, user_token_headers: dict[str, str]) -> None:
+    async def test_correct_authorization_header(self, client: AsyncClient, random_user: UserWithAuthHeader) -> None:
         """
         Test with correct authorization header on a protected route.
 
@@ -50,10 +50,12 @@ class TestJWTProtectedRoutes:
         ----------
         client : httpx.AsyncClient
             HTTP Client to perform the request on. pytest fixture.
-        user_token_headers : dict[str,str]
-            HTTP Headers to authorize the request. pytest fixture.
+        random_user : app.tests.utils.user.UserWithAuthHeader
+            Random user for testing. pytest fixture.
         """
-        response = await client.get(self.protected_route, headers=user_token_headers)
+        response = await client.get(
+            self.protected_route, params={"user": random_user.user.uid}, headers=random_user.auth_headers
+        )
         assert response.status_code == status.HTTP_200_OK
 
     @pytest.mark.asyncio
@@ -61,8 +63,7 @@ class TestJWTProtectedRoutes:
         self,
         db: AsyncSession,
         client: AsyncClient,
-        random_user: User,
-        user_token_headers: dict[str, str],
+        random_user: UserWithAuthHeader,
     ) -> None:
         """
         Test with correct authorization header from a deleted user on a protected route.
@@ -73,13 +74,13 @@ class TestJWTProtectedRoutes:
             Async database session to perform query on. pytest fixture.
         client : httpx.AsyncClient
             HTTP Client to perform the request on. pytest fixture.
-        random_user : app.models.user.User
+        random_user : app.tests.utils.user.UserWithAuthHeader
             Random user for testing. pytest fixture.
-        user_token_headers : dict[str,str]
-            HTTP Headers to authorize the request. pytest fixture.
         """
-        await db.delete(random_user)
+        await db.delete(random_user.user)
         await db.commit()
 
-        response = await client.get(self.protected_route, headers=user_token_headers)
+        response = await client.get(
+            self.protected_route, params={"user": random_user.user.uid}, headers=random_user.auth_headers
+        )
         assert response.status_code == status.HTTP_404_NOT_FOUND
diff --git a/app/tests/api/test_users.py b/app/tests/api/test_users.py
deleted file mode 100644
index 4ab0399647374feb6ddd4ef2c39d94d5a4fbf3b4..0000000000000000000000000000000000000000
--- a/app/tests/api/test_users.py
+++ /dev/null
@@ -1,120 +0,0 @@
-import random
-
-import pytest
-from fastapi import status
-from httpx import AsyncClient
-
-from app.models.user import User
-from app.tests.utils.user import get_authorization_headers
-
-
-class _TestUserRoutes:
-    base_path = "/users/"
-
-
-class TestUserRoutesGet(_TestUserRoutes):
-    @pytest.mark.asyncio
-    async def test_get_user_me(self, client: AsyncClient, random_user: User) -> None:
-        """
-        Test for getting the currently logged-in user.
-
-        Parameters
-        ----------
-        client : httpx.AsyncClient
-            HTTP Client to perform the request on. pytest fixture.
-        random_user : app.models.user.User
-            Random user for testing. pytest fixture.
-        """
-        headers = get_authorization_headers(random_user.uid)
-        response = await client.get(f"{self.base_path}me", headers=headers)
-        current_user = response.json()
-        assert response.status_code == status.HTTP_200_OK
-        assert current_user
-        assert current_user["uid"] == random_user.uid
-        assert current_user["display_name"] == random_user.display_name
-
-    @pytest.mark.asyncio
-    async def test_get_unknown_user(self, client: AsyncClient, user_token_headers: dict[str, str]) -> None:
-        """
-        Test for getting an unknown user by its uid.
-
-        Parameters
-        ----------
-        client : httpx.AsyncClient
-            HTTP Client to perform the request on. pytest fixture.
-        user_token_headers : dict[str,str]
-            HTTP Headers to authorize the request. pytest fixture.
-        """
-        response = await client.get(f"{self.base_path}impossible_uid", headers=user_token_headers)
-        assert response.status_code == status.HTTP_404_NOT_FOUND
-
-    @pytest.mark.asyncio
-    async def test_get_user_by_uid(self, client: AsyncClient, random_user: User) -> None:
-        """
-        Test for getting a known user by its uid.
-
-        Parameters
-        ----------
-        client : httpx.AsyncClient
-            HTTP Client to perform the request on. pytest fixture.
-        random_user : app.models.user.User
-            Random user for testing. pytest fixture.
-        """
-        headers = get_authorization_headers(random_user.uid)
-        response = await client.get(f"{self.base_path}{random_user.uid}", headers=headers)
-        current_user = response.json()
-        assert response.status_code == status.HTTP_200_OK
-        assert current_user
-        assert current_user["uid"] == random_user.uid
-        assert current_user["display_name"] == random_user.display_name
-
-    @pytest.mark.asyncio
-    async def test_get_foreign_user_by_uid(
-        self,
-        client: AsyncClient,
-        user_token_headers: dict[str, str],
-        random_second_user: User,
-    ) -> None:
-        """
-        Test for getting a foreign user by its uid.
-
-        Parameters
-        ----------
-        client : httpx.AsyncClient
-            HTTP Client to perform the request on. pytest fixture.
-        user_token_headers : dict[str,str]
-            HTTP Headers to authorize the request. pytest fixture.
-        random_second_user : app.models.user.User
-            Random foreign user for testing. pytest fixture.
-        """
-        response = await client.get(f"{self.base_path}{random_second_user.uid}", headers=user_token_headers)
-
-        assert response.status_code == status.HTTP_403_FORBIDDEN
-
-    @pytest.mark.asyncio
-    async def test_search_user_by_name_substring(
-        self, client: AsyncClient, random_user: User, user_token_headers: dict[str, str]
-    ) -> None:
-        """
-        Test for searching a user by its name
-
-        Parameters
-        ----------
-        client : httpx.AsyncClient
-            HTTP Client to perform the request on. pytest fixture.
-        random_user : app.models.user.User
-            Random user for testing. pytest fixture.
-        """
-        substring_indices = [0, 0]
-        while substring_indices[1] - substring_indices[0] < 3:
-            substring_indices = sorted(random.choices(range(len(random_user.display_name)), k=2))
-
-        random_substring = random_user.display_name[substring_indices[0] : substring_indices[1]]
-
-        response = await client.get(
-            f"{self.base_path}", params={"name_like": random_substring}, headers=user_token_headers
-        )
-        users = response.json()
-        assert response.status_code == status.HTTP_200_OK
-        assert len(users) > 0
-        assert sum(1 for u in users if u["uid"] == random_user.uid) == 1
diff --git a/app/tests/conftest.py b/app/tests/conftest.py
index a038ade1a8d2389dd0bcee9a4912243920773124..c3e84d9d9613a1459a092bba9d24de0d9406e532 100644
--- a/app/tests/conftest.py
+++ b/app/tests/conftest.py
@@ -1,23 +1,30 @@
 import asyncio
 import json
-from typing import AsyncGenerator, Generator
+from functools import partial
+from secrets import token_urlsafe
+from typing import AsyncGenerator, Callable, Dict, Generator
+from uuid import uuid4
 
+import httpx
 import pytest
 import pytest_asyncio
-from httpx import AsyncClient
+from clowmdb.db.session import get_async_session
+from clowmdb.models import Bucket
+from clowmdb.models import BucketPermission as BucketPermissionDB
 from sqlalchemy.ext.asyncio import AsyncSession
 
-from app.api.dependencies import LoginException, get_rgw_admin, get_s3_resource, get_userinfo_from_access_token
-from app.db.session import SessionAsync as Session
+from app.api.dependencies import get_decode_jwt_function, get_httpx_client, get_rgw_admin, get_s3_resource
+from app.core.config import settings
 from app.main import app
-from app.models.bucket import Bucket
-from app.models.bucket_permission import BucketPermission as BucketPermissionDB
-from app.models.user import User
 from app.schemas.bucket_permission import BucketPermissionOut as BucketPermissionSchema
+from app.schemas.security import AuthzResponse
 from app.tests.mocks.mock_rgw_admin import MockRGWAdmin
 from app.tests.mocks.mock_s3_resource import MockS3ServiceResource
 from app.tests.utils.bucket import create_random_bucket
-from app.tests.utils.user import create_random_user, get_authorization_headers
+from app.tests.utils.user import UserWithAuthHeader, create_random_user, decode_mock_token, get_authorization_headers
+from app.tests.utils.utils import request_admin_permission
+
+jwt_secret = token_urlsafe(32)
 
 
 @pytest.fixture(scope="session")
@@ -59,81 +66,91 @@ async def client(mock_rgw_admin: MockRGWAdmin, mock_s3_service: MockS3ServiceRes
     def get_mock_s3() -> MockS3ServiceResource:
         return mock_s3_service
 
-    def get_mock_userinfo(voperson_id: str, name: str, error: bool = False) -> dict[str, str]:
-        if error:
-            raise LoginException(error_source="mock_error")
-        return {"voperson_id": voperson_id + "@lifescience-ri.eu", "name": name}
+    def get_decode_token_function() -> Callable[[str], Dict[str, str]]:
+        # Override the decode_jwt function with mock function for tests and inject random shared secret
+        return partial(decode_mock_token, secret=jwt_secret)
+
+    async def get_mock_httpx_client() -> AsyncGenerator[httpx.AsyncClient, None]:
+        def mock_request_handler(request: httpx.Request) -> httpx.Response:
+            response_body = {}
+            if str(request.url).startswith(str(settings.OPA_URI)):
+                response_body = AuthzResponse(
+                    result=not request_admin_permission(request), decision_id=str(uuid4())
+                ).model_dump()
+            return httpx.Response(200, json=response_body)
 
+        async with httpx.AsyncClient(transport=httpx.MockTransport(mock_request_handler)) as http_client:
+            yield http_client
+
+    app.dependency_overrides[get_httpx_client] = get_mock_httpx_client
     app.dependency_overrides[get_rgw_admin] = get_mock_rgw
     app.dependency_overrides[get_s3_resource] = get_mock_s3
-    app.dependency_overrides[get_userinfo_from_access_token] = get_mock_userinfo
-    async with AsyncClient(app=app, base_url="http://localhost:8000") as ac:
+    app.dependency_overrides[get_decode_jwt_function] = get_decode_token_function
+    async with httpx.AsyncClient(app=app, base_url="http://localhost") as ac:
         yield ac
     app.dependency_overrides = {}
 
 
 @pytest_asyncio.fixture(scope="module")
-async def user_token_headers(random_user: User) -> dict[str, str]:
-    """
-    Create valid authorization header with a successful login.
-    """
-    return get_authorization_headers(uid=random_user.uid)
-
-
-@pytest_asyncio.fixture(scope="module")
-async def db() -> AsyncGenerator:
+async def db() -> AsyncGenerator[AsyncSession, None]:
     """
     Fixture for creating a database session to connect to.
     """
-    async with Session() as dbSession:
+    async with get_async_session(
+        url=str(settings.SQLALCHEMY_DATABASE_ASYNC_URI), verbose=settings.SQLALCHEMY_VERBOSE_LOGGER
+    ) as dbSession:
         yield dbSession
 
 
 @pytest_asyncio.fixture(scope="module")
-async def random_user(db: AsyncSession, mock_rgw_admin: MockRGWAdmin) -> AsyncGenerator:
+async def random_user(db: AsyncSession, mock_rgw_admin: MockRGWAdmin) -> AsyncGenerator[UserWithAuthHeader, None]:
     """
     Create a random user and deletes him afterwards.
     """
     user = await create_random_user(db)
     mock_rgw_admin.create_key(uid=user.uid)
-    yield user
+    yield UserWithAuthHeader(user=user, auth_headers=get_authorization_headers(uid=user.uid, secret=jwt_secret))
     mock_rgw_admin.delete_user(uid=user.uid)
     await db.delete(user)
     await db.commit()
 
 
 @pytest_asyncio.fixture(scope="module")
-async def random_second_user(db: AsyncSession, mock_rgw_admin: MockRGWAdmin) -> AsyncGenerator:
+async def random_second_user(
+    db: AsyncSession, mock_rgw_admin: MockRGWAdmin
+) -> AsyncGenerator[UserWithAuthHeader, None]:
     """
     Create a random second user and deletes him afterwards.
     """
     user = await create_random_user(db)
     mock_rgw_admin.create_key(uid=user.uid)
-    yield user
+    yield UserWithAuthHeader(user=user, auth_headers=get_authorization_headers(uid=user.uid, secret=jwt_secret))
     mock_rgw_admin.delete_user(uid=user.uid)
     await db.delete(user)
     await db.commit()
 
 
 @pytest_asyncio.fixture(scope="module")
-async def random_third_user(db: AsyncSession, mock_rgw_admin: MockRGWAdmin) -> AsyncGenerator:
+async def random_third_user(db: AsyncSession, mock_rgw_admin: MockRGWAdmin) -> AsyncGenerator[UserWithAuthHeader, None]:
     """
     Create a random third user and deletes him afterwards.
     """
     user = await create_random_user(db)
     mock_rgw_admin.create_key(uid=user.uid)
-    yield user
+    yield UserWithAuthHeader(user=user, auth_headers=get_authorization_headers(uid=user.uid, secret=jwt_secret))
     mock_rgw_admin.delete_user(uid=user.uid)
     await db.delete(user)
     await db.commit()
 
 
 @pytest_asyncio.fixture(scope="function")
-async def random_bucket(db: AsyncSession, random_user: User, mock_s3_service: MockS3ServiceResource) -> AsyncGenerator:
+async def random_bucket(
+    db: AsyncSession, random_user: UserWithAuthHeader, mock_s3_service: MockS3ServiceResource
+) -> AsyncGenerator[Bucket, None]:
     """
     Create a random user and deletes him afterwards.
     """
-    bucket = await create_random_bucket(db, random_user)
+    bucket = await create_random_bucket(db, random_user.user)
     mock_s3_service.Bucket(name=bucket.name).create()
     mock_s3_service.BucketPolicy(bucket.name).put(
         json.dumps(
@@ -143,7 +160,7 @@ async def random_bucket(db: AsyncSession, random_user: User, mock_s3_service: Mo
                     {
                         "Sid": "PseudoOwnerPerm",
                         "Effect": "Allow",
-                        "Principal": {"AWS": [f"arn:aws:iam:::user/{random_user.uid}"]},
+                        "Principal": {"AWS": [f"arn:aws:iam:::user/{random_user.user.uid}"]},
                         "Action": ["s3:GetObject", "s3:DeleteObject", "s3:PutObject", "s3:ListBucket"],
                         "Resource": [f"arn:aws:s3:::{bucket.name}/*", f"arn:aws:s3:::{bucket.name}"],
                     }
@@ -159,12 +176,15 @@ async def random_bucket(db: AsyncSession, random_user: User, mock_s3_service: Mo
 
 @pytest_asyncio.fixture(scope="function")
 async def random_bucket_permission(
-    db: AsyncSession, random_second_user: User, random_bucket: Bucket, mock_s3_service: MockS3ServiceResource
+    db: AsyncSession,
+    random_second_user: UserWithAuthHeader,
+    random_bucket: Bucket,
+    mock_s3_service: MockS3ServiceResource,
 ) -> BucketPermissionDB:
     """
     Create a bucket READ permission for the second user on a bucket.
     """
-    permission_db = BucketPermissionDB(user_id=random_second_user.uid, bucket_name=random_bucket.name)
+    permission_db = BucketPermissionDB(user_id=random_second_user.user.uid, bucket_name=random_bucket.name)
     db.add(permission_db)
     await db.commit()
     await db.refresh(permission_db)
@@ -173,7 +193,7 @@ async def random_bucket_permission(
             {
                 "Version": "2012-10-17",
                 "Statement": BucketPermissionSchema.from_db_model(permission_db).map_to_bucket_policy_statement(
-                    random_second_user.uid
+                    random_second_user.user.uid
                 ),
             }
         )
@@ -183,36 +203,10 @@ async def random_bucket_permission(
 
 @pytest_asyncio.fixture(scope="function")
 async def random_bucket_permission_schema(
-    random_bucket_permission: BucketPermissionDB, random_second_user: User
+    random_bucket_permission: BucketPermissionDB, random_second_user: UserWithAuthHeader
 ) -> BucketPermissionSchema:
     """
     Create a bucket READ permission for the second user on a bucket.
     """
 
-    return BucketPermissionSchema.from_db_model(random_bucket_permission, random_second_user.uid)
-
-
-@pytest_asyncio.fixture(autouse=True)
-async def multiple_random_users_buckets(
-    db: AsyncSession, mock_rgw_admin: MockRGWAdmin, mock_s3_service: MockS3ServiceResource
-) -> AsyncGenerator:
-    """
-    Create multiple random users and buckets to ensure that the database is not empty.
-    """
-    user1 = await create_random_user(db)
-    mock_rgw_admin.create_key(uid=user1.uid)
-    user2 = await create_random_user(db)
-    mock_rgw_admin.create_key(uid=user2.uid)
-    bucket1 = await create_random_bucket(db, user1)
-    bucket2 = await create_random_bucket(db, user2)
-    mock_s3_service.Bucket(name=bucket1.name).create()
-    mock_s3_service.Bucket(name=bucket2.name).create()
-    yield
-    mock_rgw_admin.delete_user(uid=user1.uid)
-    mock_rgw_admin.delete_user(uid=user2.uid)
-    await db.delete(user1)
-    await db.delete(user2)
-    await db.delete(bucket1)
-    await db.delete(bucket2)
-    mock_s3_service.delete_bucket(name=bucket1.name)
-    mock_s3_service.delete_bucket(name=bucket2.name)
+    return BucketPermissionSchema.from_db_model(random_bucket_permission, random_second_user.user.uid)
diff --git a/app/tests/crud/test_bucket.py b/app/tests/crud/test_bucket.py
index cd4467e1a1bbbaacfac062a3ee9bdddb90fde57f..2250e511e4da32a0dd27ea167e38f4de1b6cd687 100644
--- a/app/tests/crud/test_bucket.py
+++ b/app/tests/crud/test_bucket.py
@@ -1,29 +1,48 @@
 from datetime import datetime, timedelta
 
 import pytest
+from clowmdb.models import Bucket, BucketPermission
+from sqlalchemy import select
 from sqlalchemy.ext.asyncio import AsyncSession
-from sqlalchemy.future import select
 
+from app.crud import DuplicateError
 from app.crud.crud_bucket import CRUDBucket
-from app.models.bucket import Bucket
-from app.models.bucket_permission import PermissionEnum
-from app.models.user import User
 from app.schemas.bucket import BucketIn
 from app.tests.utils.bucket import add_permission_for_bucket
+from app.tests.utils.user import UserWithAuthHeader
 from app.tests.utils.utils import random_lower_string
 
 
 class TestBucketCRUDGet:
+    @pytest.mark.asyncio
+    async def test_get_all_bucket(self, db: AsyncSession, random_bucket: Bucket) -> None:
+        """
+        Test for getting all buckets from CRUD Repository.
+
+        Parameters
+        ----------
+        db : sqlalchemy.ext.asyncio.AsyncSession.
+            Async database session to perform query on. pytest fixture.
+        random_bucket : clowmdb.models.Bucket
+            Random bucket for testing. pytest fixture.
+        """
+        buckets = await CRUDBucket.get_all(db)
+        assert len(buckets) == 1
+        bucket = buckets[0]
+        assert bucket.name == random_bucket.name
+        assert bucket.public == random_bucket.public
+        assert bucket.description == random_bucket.description
+
     @pytest.mark.asyncio
     async def test_get_bucket_by_name(self, db: AsyncSession, random_bucket: Bucket) -> None:
         """
-        Test for getting a user by id from the User CRUD Repository.
+        Test for getting a bucket by name from CRUD Repository.
 
         Parameters
         ----------
         db : sqlalchemy.ext.asyncio.AsyncSession.
             Async database session to perform query on. pytest fixture.
-        random_bucket : app.models.bucket.Bucket
+        random_bucket : clowmdb.models.Bucket
             Random bucket for testing. pytest fixture.
         """
         bucket = await CRUDBucket.get(db, random_bucket.name)
@@ -46,7 +65,7 @@ class TestBucketCRUDGet:
         assert bucket is None
 
     @pytest.mark.asyncio
-    async def test_get_own_buckets(self, db: AsyncSession, random_bucket: Bucket) -> None:
+    async def test_get_only_own_buckets(self, db: AsyncSession, random_bucket: Bucket) -> None:
         """
         Test for getting only the buckets where a user is the owner from CRUD Repository.
 
@@ -54,21 +73,54 @@ class TestBucketCRUDGet:
         ----------
         db : sqlalchemy.ext.asyncio.AsyncSession.
             Async database session to perform query on. pytest fixture.
-        random_bucket : app.models.bucket.Bucket
+        random_bucket : clowmdb.models.Bucket
             Random bucket for testing. pytest fixture.
         """
-        buckets = await CRUDBucket.get_own_buckets(db, random_bucket.owner_id)
+        buckets = await CRUDBucket.get_for_user(db, random_bucket.owner_id, CRUDBucket.BucketType.OWN)
 
         assert len(buckets) == 1
         assert buckets[0].name == random_bucket.name
 
+    @pytest.mark.asyncio
+    async def test_get_only_foreign_bucket(
+        self, db: AsyncSession, random_bucket: Bucket, random_second_user: UserWithAuthHeader
+    ) -> None:
+        """
+        Test for getting only foreign buckets with permissions for a user from CRUD Repository.
+
+        Parameters
+        ----------
+        db : sqlalchemy.ext.asyncio.AsyncSession.
+            Async database session to perform query on. pytest fixture.
+        random_bucket : clowmdb.models.Bucket
+            Random bucket for testing. pytest fixture.
+        random_second_user : app.tests.utils.user.UserWithAuthHeader
+            Random second user for testing. pytest fixture.
+        """
+        bucket = Bucket(
+            name=random_lower_string(),
+            description=random_lower_string(127),
+            owner_id=random_second_user.user.uid,
+        )
+        db.add(bucket)
+        await db.commit()
+        await add_permission_for_bucket(
+            db, bucket.name, random_bucket.owner_id, permission=BucketPermission.Permission.READ
+        )
+
+        buckets = await CRUDBucket.get_for_user(db, random_bucket.owner_id, CRUDBucket.BucketType.PERMISSION)
+        assert len(buckets) == 1
+        assert buckets[0] != random_bucket
+        assert buckets[0].name == bucket.name
+
+        await db.delete(bucket)
+
     @pytest.mark.asyncio
     async def test_get_bucket_with_read_permission_and_own(
         self,
         db: AsyncSession,
         random_bucket: Bucket,
-        random_user: User,
-        random_second_user: User,
+        random_second_user: UserWithAuthHeader,
     ) -> None:
         """
         Test for getting the users own bucket and a foreign bucket with READ permissions from CRUD Repository.
@@ -77,23 +129,23 @@ class TestBucketCRUDGet:
         ----------
         db : sqlalchemy.ext.asyncio.AsyncSession.
             Async database session to perform query on. pytest fixture.
-        random_bucket : app.models.bucket.Bucket
+        random_bucket : clowmdb.models.Bucket
             Random bucket for testing. pytest fixture.
-        random_user : app.models.user.User
-            Random user for testing. pytest fixture.
-        random_second_user : app.models.user.User
+        random_second_user : app.tests.utils.user.UserWithAuthHeader
             Random second user for testing. pytest fixture.
         """
         bucket = Bucket(
             name=random_lower_string(),
             description=random_lower_string(127),
-            owner_id=random_second_user.uid,
+            owner_id=random_second_user.user.uid,
         )
         db.add(bucket)
         await db.commit()
-        await add_permission_for_bucket(db, bucket.name, random_user.uid, permission=PermissionEnum.READ)
+        await add_permission_for_bucket(
+            db, bucket.name, random_bucket.owner_id, permission=BucketPermission.Permission.READ
+        )
 
-        buckets = await CRUDBucket.get_for_user(db, random_user.uid)
+        buckets = await CRUDBucket.get_for_user(db, random_bucket.owner_id)
 
         assert len(buckets) == 2
         assert buckets[0].name == random_bucket.name or buckets[1].name == random_bucket.name
@@ -103,7 +155,7 @@ class TestBucketCRUDGet:
 
     @pytest.mark.asyncio
     async def test_get_bucket_with_read_permission(
-        self, db: AsyncSession, random_bucket: Bucket, random_second_user: User
+        self, db: AsyncSession, random_bucket: Bucket, random_second_user: UserWithAuthHeader
     ) -> None:
         """
         Test for getting a foreign bucket with READ permissions from CRUD Repository.
@@ -112,21 +164,23 @@ class TestBucketCRUDGet:
         ----------
         db : sqlalchemy.ext.asyncio.AsyncSession.
             Async database session to perform query on. pytest fixture.
-        random_bucket : app.models.bucket.Bucket
+        random_bucket : clowmdb.models.Bucket
             Random bucket for testing. pytest fixture.
-        random_second_user : app.models.user.User
+        random_second_user : app.tests.utils.user.UserWithAuthHeader
             Random second user for testing. pytest fixture.
         """
-        await add_permission_for_bucket(db, random_bucket.name, random_second_user.uid, permission=PermissionEnum.READ)
+        await add_permission_for_bucket(
+            db, random_bucket.name, random_second_user.user.uid, permission=BucketPermission.Permission.READ
+        )
 
-        buckets = await CRUDBucket.get_for_user(db, random_second_user.uid)
+        buckets = await CRUDBucket.get_for_user(db, random_second_user.user.uid)
 
         assert len(buckets) > 0
         assert buckets[0].name == random_bucket.name
 
     @pytest.mark.asyncio
     async def test_get_bucket_with_readwrite_permission(
-        self, db: AsyncSession, random_bucket: Bucket, random_second_user: User
+        self, db: AsyncSession, random_bucket: Bucket, random_second_user: UserWithAuthHeader
     ) -> None:
         """
         Test for getting a foreign bucket with READWRITE permissions from CRUD Repository.
@@ -135,23 +189,23 @@ class TestBucketCRUDGet:
         ----------
         db : sqlalchemy.ext.asyncio.AsyncSession.
             Async database session to perform query on. pytest fixture.
-        random_bucket : app.models.bucket.Bucket
+        random_bucket : clowmdb.models.Bucket
             Random bucket for testing. pytest fixture.
-        random_second_user : app.models.user.User
+        random_second_user : app.tests.utils.user.UserWithAuthHeader
             Random second user for testing. pytest fixture.
         """
         await add_permission_for_bucket(
-            db, random_bucket.name, random_second_user.uid, permission=PermissionEnum.READWRITE
+            db, random_bucket.name, random_second_user.user.uid, permission=BucketPermission.Permission.READWRITE
         )
 
-        buckets = await CRUDBucket.get_for_user(db, random_second_user.uid)
+        buckets = await CRUDBucket.get_for_user(db, random_second_user.user.uid)
 
         assert len(buckets) > 0
         assert buckets[0].name == random_bucket.name
 
     @pytest.mark.asyncio
     async def test_get_bucket_with_write_permission(
-        self, db: AsyncSession, random_bucket: Bucket, random_second_user: User
+        self, db: AsyncSession, random_bucket: Bucket, random_second_user: UserWithAuthHeader
     ) -> None:
         """
         Test for getting a foreign bucket with WRITE permissions from CRUD Repository.
@@ -160,20 +214,23 @@ class TestBucketCRUDGet:
         ----------
         db : sqlalchemy.ext.asyncio.AsyncSession.
             Async database session to perform query on. pytest fixture.
-        random_bucket : app.models.bucket.Bucket
+        random_bucket : clowmdb.models.Bucket
             Random bucket for testing. pytest fixture.
-        random_second_user : app.models.user.User
+        random_second_user : app.tests.utils.user.UserWithAuthHeader
             Random second user for testing. pytest fixture.
         """
-        await add_permission_for_bucket(db, random_bucket.name, random_second_user.uid, permission=PermissionEnum.WRITE)
+        await add_permission_for_bucket(
+            db, random_bucket.name, random_second_user.user.uid, permission=BucketPermission.Permission.WRITE
+        )
 
-        buckets = await CRUDBucket.get_for_user(db, random_second_user.uid)
+        buckets = await CRUDBucket.get_for_user(db, random_second_user.user.uid)
 
-        assert len(buckets) == 0
+        assert len(buckets) == 1
+        assert buckets[0] == random_bucket
 
     @pytest.mark.asyncio
     async def test_get_bucket_with_valid_time_permission(
-        self, db: AsyncSession, random_bucket: Bucket, random_second_user: User
+        self, db: AsyncSession, random_bucket: Bucket, random_second_user: UserWithAuthHeader
     ) -> None:
         """
         Test for getting a foreign bucket with valid time permission from CRUD Repository.
@@ -182,27 +239,27 @@ class TestBucketCRUDGet:
         ----------
         db : sqlalchemy.ext.asyncio.AsyncSession.
             Async database session to perform query on. pytest fixture.
-        random_bucket : app.models.bucket.Bucket
+        random_bucket : clowmdb.models.Bucket
             Random bucket for testing. pytest fixture.
-        random_second_user : app.models.user.User
+        random_second_user : app.tests.utils.user.UserWithAuthHeader
             Random second user for testing. pytest fixture.
         """
         await add_permission_for_bucket(
             db,
             random_bucket.name,
-            random_second_user.uid,
+            random_second_user.user.uid,
             from_=datetime.now() - timedelta(days=10),
             to=datetime.now() + timedelta(days=10),
         )
 
-        buckets = await CRUDBucket.get_for_user(db, random_second_user.uid)
+        buckets = await CRUDBucket.get_for_user(db, random_second_user.user.uid)
 
         assert len(buckets) > 0
         assert buckets[0].name == random_bucket.name
 
     @pytest.mark.asyncio
     async def test_get_bucket_with_invalid_from_time_permission(
-        self, db: AsyncSession, random_bucket: Bucket, random_second_user: User
+        self, db: AsyncSession, random_bucket: Bucket, random_second_user: UserWithAuthHeader
     ) -> None:
         """
         Test for getting a foreign bucket with invalid 'from' time permission from CRUD Repository.
@@ -211,22 +268,22 @@ class TestBucketCRUDGet:
         ----------
         db : sqlalchemy.ext.asyncio.AsyncSession.
             Async database session to perform query on. pytest fixture.
-        random_bucket : app.models.bucket.Bucket
+        random_bucket : clowmdb.models.Bucket
             Random bucket for testing. pytest fixture.
-        random_second_user : app.models.user.User
+        random_second_user : app.tests.utils.user.UserWithAuthHeader
             Random second user for testing. pytest fixture.
         """
         await add_permission_for_bucket(
-            db, random_bucket.name, random_second_user.uid, from_=datetime.now() + timedelta(days=10)
+            db, random_bucket.name, random_second_user.user.uid, from_=datetime.now() + timedelta(days=10)
         )
 
-        buckets = await CRUDBucket.get_for_user(db, random_second_user.uid)
+        buckets = await CRUDBucket.get_for_user(db, random_second_user.user.uid)
 
         assert len(buckets) == 0
 
     @pytest.mark.asyncio
     async def test_get_bucket_with_invalid_to_time_permission(
-        self, db: AsyncSession, random_bucket: Bucket, random_second_user: User
+        self, db: AsyncSession, random_bucket: Bucket, random_second_user: UserWithAuthHeader
     ) -> None:
         """
         Test for getting a foreign bucket with invalid 'to' time permission from CRUD Repository.
@@ -235,23 +292,23 @@ class TestBucketCRUDGet:
         ----------
         db : sqlalchemy.ext.asyncio.AsyncSession.
             Async database session to perform query on. pytest fixture.
-        random_bucket : app.models.bucket.Bucket
+        random_bucket : clowmdb.models.Bucket
             Random bucket for testing. pytest fixture.
-        random_second_user : app.models.user.User
+        random_second_user : app.tests.utils.user.UserWithAuthHeader
             Random second user for testing. pytest fixture.
         """
         await add_permission_for_bucket(
-            db, random_bucket.name, random_second_user.uid, to=datetime.now() - timedelta(days=10)
+            db, random_bucket.name, random_second_user.user.uid, to=datetime.now() - timedelta(days=10)
         )
 
-        buckets = await CRUDBucket.get_for_user(db, random_second_user.uid)
+        buckets = await CRUDBucket.get_for_user(db, random_second_user.user.uid)
 
         assert len(buckets) == 0
 
 
 class TestBucketCRUDCreate:
     @pytest.mark.asyncio
-    async def test_create_bucket(self, db: AsyncSession, random_user: User) -> None:
+    async def test_create_bucket(self, db: AsyncSession, random_user: UserWithAuthHeader) -> None:
         """
         Test for creating a bucket with the CRUD Repository.
 
@@ -259,22 +316,21 @@ class TestBucketCRUDCreate:
         ----------
         db : sqlalchemy.ext.asyncio.AsyncSession.
             Async database session to perform query on. pytest fixture.
-        random_user : app.models.user.User
+        random_user : app.tests.utils.user.UserWithAuthHeader
             Random user for testing. pytest fixture.
         """
         bucket_info = BucketIn(name=random_lower_string(), description=random_lower_string(127))
-        bucket = await CRUDBucket.create(db, bucket_info, random_user.uid)
-        assert bucket
+        bucket = await CRUDBucket.create(db, bucket_info, random_user.user.uid)
         assert bucket.name == bucket_info.name
-        assert bucket.owner_id == random_user.uid
+        assert bucket.owner_id == random_user.user.uid
         assert bucket.description == bucket_info.description
 
         stmt = select(Bucket).where(Bucket.name == bucket_info.name)
-        bucket_db = (await db.execute(stmt)).scalar()
+        bucket_db = await db.scalar(stmt)
 
         assert bucket_db
         assert bucket_db.name == bucket_info.name
-        assert bucket_db.owner_id == random_user.uid
+        assert bucket_db.owner_id == random_user.user.uid
         assert bucket_db.description == bucket_info.description
 
         await db.delete(bucket)
@@ -288,13 +344,12 @@ class TestBucketCRUDCreate:
         ----------
         db : sqlalchemy.ext.asyncio.AsyncSession.
             Async database session to perform query on. pytest fixture.
-        random_bucket : app.models.bucket.Bucket
+        random_bucket : clowmdb.models.Bucket
             Random bucket for testing. pytest fixture.
         """
         bucket_info = BucketIn(name=random_bucket.name, description=random_lower_string(127))
-        bucket = await CRUDBucket.create(db, bucket_info, random_bucket.owner_id)
-
-        assert bucket is None
+        with pytest.raises(DuplicateError):
+            await CRUDBucket.create(db, bucket_info, random_bucket.owner_id)
 
 
 class TestBucketCRUDDelete:
@@ -307,12 +362,12 @@ class TestBucketCRUDDelete:
         ----------
         db : sqlalchemy.ext.asyncio.AsyncSession.
             Async database session to perform query on. pytest fixture.
-        random_bucket : app.models.bucket.Bucket
+        random_bucket : clowmdb.models.Bucket
             Random bucket for testing. pytest fixture.
         """
-        await CRUDBucket.delete(db, random_bucket)
+        await CRUDBucket.delete(db, random_bucket.name)
 
         stmt = select(Bucket).where(Bucket.name == random_bucket.name)
-        bucket_db = (await db.execute(stmt)).scalar()
+        bucket_db = await db.scalar(stmt)
 
         assert bucket_db is None
diff --git a/app/tests/crud/test_bucket_permission.py b/app/tests/crud/test_bucket_permission.py
index 75099b7aaf5d528050153dcd45a07e69e6fec7fa..a92f97f6035cc385c2709ab5ecfc137706d77469 100644
--- a/app/tests/crud/test_bucket_permission.py
+++ b/app/tests/crud/test_bucket_permission.py
@@ -1,17 +1,17 @@
 from datetime import datetime, timedelta
 
 import pytest
-from sqlalchemy import and_
+from clowmdb.models import Bucket
+from clowmdb.models import BucketPermission as BucketPermissionDB
+from sqlalchemy import and_, select
 from sqlalchemy.ext.asyncio import AsyncSession
-from sqlalchemy.future import select
 
-from app.crud.crud_bucket_permission import CRUDBucketPermission, DuplicateError
-from app.models.bucket import Bucket
-from app.models.bucket_permission import BucketPermission as BucketPermissionDB
-from app.models.bucket_permission import PermissionEnum
-from app.models.user import User
+from app.crud import DuplicateError
+from app.crud.crud_bucket_permission import CRUDBucketPermission
 from app.schemas.bucket_permission import BucketPermissionIn as BucketPermissionSchema
 from app.schemas.bucket_permission import BucketPermissionParameters as BucketPermissionParametersSchema
+from app.tests.utils.bucket import add_permission_for_bucket
+from app.tests.utils.user import UserWithAuthHeader
 
 
 class TestBucketPermissionCRUDGet:
@@ -26,11 +26,11 @@ class TestBucketPermissionCRUDGet:
         ----------
         db : sqlalchemy.ext.asyncio.AsyncSession.
             Async database session to perform query on. pytest fixture.
-        random_bucket_permission : app.models.bucket_permission.BucketPermission
+        random_bucket_permission : clowmdb.models.BucketPermission
             Bucket permission for a random bucket for testing. pytest fixture.
         """
         bucket_permission = await CRUDBucketPermission.get(
-            db, bucket_name=random_bucket_permission.bucket_name, user_id=random_bucket_permission.user_id
+            db, bucket_name=random_bucket_permission.bucket_name, uid=random_bucket_permission.user_id
         )
         assert bucket_permission
         assert bucket_permission.user_id == random_bucket_permission.user_id
@@ -48,18 +48,293 @@ class TestBucketPermissionCRUDGet:
         ----------
         db : sqlalchemy.ext.asyncio.AsyncSession.
             Async database session to perform query on. pytest fixture.
-        random_bucket_permission : app.models.bucket_permission.BucketPermission
+        random_bucket_permission : clowmdb.models.BucketPermission
             Bucket permission for a random bucket for testing. pytest fixture.
         """
-        bucket_permissions = await CRUDBucketPermission.get_permissions_for_bucket(
-            db, bucket_name=random_bucket_permission.bucket_name
-        )
+        bucket_permissions = await CRUDBucketPermission.list(db, bucket_name=random_bucket_permission.bucket_name)
         assert len(bucket_permissions) == 1
         bucket_permission = bucket_permissions[0]
         assert bucket_permission.user_id == random_bucket_permission.user_id
         assert bucket_permission.bucket_name == random_bucket_permission.bucket_name
         assert bucket_permission.permissions == random_bucket_permission.permissions
 
+    @pytest.mark.asyncio
+    async def test_get_read_bucket_permissions_by_bucket_name(
+        self,
+        db: AsyncSession,
+        random_bucket: Bucket,
+        random_second_user: UserWithAuthHeader,
+        random_third_user: UserWithAuthHeader,
+    ) -> None:
+        """
+        Test for getting only 'READ' bucket permissions for a specific bucket from the CRUD Repository.
+
+        Parameters
+        ----------
+        db : sqlalchemy.ext.asyncio.AsyncSession.
+            Async database session to perform query on. pytest fixture.
+        random_second_user : app.tests.utils.user.UserWithAuthHeader
+            Random third user who has no permissions for the bucket. pytest fixture.
+        random_third_user : app.tests.utils.user.UserWithAuthHeader
+            Random third user who has no permissions for the bucket. pytest fixture.
+        """
+        await add_permission_for_bucket(db, random_bucket.name, random_second_user.user.uid)
+        await add_permission_for_bucket(
+            db, random_bucket.name, random_third_user.user.uid, permission=BucketPermissionDB.Permission.WRITE
+        )
+        bucket_permissions = await CRUDBucketPermission.list(
+            db, bucket_name=random_bucket.name, permission_types=[BucketPermissionDB.Permission.READ]
+        )
+        assert len(bucket_permissions) == 1
+        assert bucket_permissions[0].user_id == random_second_user.user.uid
+
+    @pytest.mark.asyncio
+    async def test_get_read_and_write_bucket_permissions_by_bucket_name(
+        self,
+        db: AsyncSession,
+        random_bucket: Bucket,
+        random_second_user: UserWithAuthHeader,
+        random_third_user: UserWithAuthHeader,
+    ) -> None:
+        """
+        Test for getting all 'READ' and 'WRITE' bucket permissions for a specific bucket from the CRUD Repository.
+
+        Parameters
+        ----------
+        db : sqlalchemy.ext.asyncio.AsyncSession.
+            Async database session to perform query on. pytest fixture.
+        random_second_user : app.tests.utils.user.UserWithAuthHeader
+            Random third user who has no permissions for the bucket. pytest fixture.
+        random_third_user : app.tests.utils.user.UserWithAuthHeader
+            Random third user who has no permissions for the bucket. pytest fixture.
+        """
+        await add_permission_for_bucket(db, random_bucket.name, random_second_user.user.uid)
+        await add_permission_for_bucket(
+            db, random_bucket.name, random_third_user.user.uid, permission=BucketPermissionDB.Permission.WRITE
+        )
+        bucket_permissions = await CRUDBucketPermission.list(
+            db,
+            bucket_name=random_bucket.name,
+            permission_types=[BucketPermissionDB.Permission.READ, BucketPermissionDB.Permission.WRITE],
+        )
+        assert len(bucket_permissions) == 2
+        assert random_second_user.user.uid in map(lambda x: x.user_id, bucket_permissions)
+        assert random_third_user.user.uid in map(lambda x: x.user_id, bucket_permissions)
+
+    @pytest.mark.asyncio
+    async def test_get_active_bucket_permissions_by_bucket_name1(
+        self,
+        db: AsyncSession,
+        random_bucket: Bucket,
+        random_second_user: UserWithAuthHeader,
+        random_third_user: UserWithAuthHeader,
+    ) -> None:
+        """
+        Test for getting all active bucket permissions for a specific bucket from the CRUD Repository.
+        Only the 'from' timestamp is set.
+
+        Parameters
+        ----------
+        db : sqlalchemy.ext.asyncio.AsyncSession.
+            Async database session to perform query on. pytest fixture.
+        random_second_user : app.tests.utils.user.UserWithAuthHeader
+            Random third user who has no permissions for the bucket. pytest fixture.
+        random_third_user : app.tests.utils.user.UserWithAuthHeader
+            Random third user who has no permissions for the bucket. pytest fixture.
+        """
+        await add_permission_for_bucket(
+            db, random_bucket.name, random_second_user.user.uid, from_=datetime.now() + timedelta(weeks=1)
+        )
+        await add_permission_for_bucket(
+            db, random_bucket.name, random_third_user.user.uid, from_=datetime.now() - timedelta(weeks=1)
+        )
+        bucket_permissions = await CRUDBucketPermission.list(
+            db, bucket_name=random_bucket.name, permission_status=CRUDBucketPermission.PermissionStatus.ACTIVE
+        )
+        assert len(bucket_permissions) == 1
+        assert bucket_permissions[0].user_id == random_third_user.user.uid
+
+    @pytest.mark.asyncio
+    async def test_get_active_bucket_permissions_by_bucket_name2(
+        self,
+        db: AsyncSession,
+        random_bucket: Bucket,
+        random_second_user: UserWithAuthHeader,
+        random_third_user: UserWithAuthHeader,
+    ) -> None:
+        """
+        Test for getting all active bucket permissions for a specific bucket from the CRUD Repository.
+        Only the 'to' timestamp is set.
+
+        Parameters
+        ----------
+        db : sqlalchemy.ext.asyncio.AsyncSession.
+            Async database session to perform query on. pytest fixture.
+        random_second_user : app.tests.utils.user.UserWithAuthHeader
+            Random third user who has no permissions for the bucket. pytest fixture.
+        random_third_user : app.tests.utils.user.UserWithAuthHeader
+            Random third user who has no permissions for the bucket. pytest fixture.
+        """
+        await add_permission_for_bucket(
+            db, random_bucket.name, random_second_user.user.uid, to=datetime.now() - timedelta(weeks=1)
+        )
+        await add_permission_for_bucket(
+            db, random_bucket.name, random_third_user.user.uid, to=datetime.now() + timedelta(weeks=1)
+        )
+        bucket_permissions = await CRUDBucketPermission.list(
+            db, bucket_name=random_bucket.name, permission_status=CRUDBucketPermission.PermissionStatus.ACTIVE
+        )
+        assert len(bucket_permissions) == 1
+        assert bucket_permissions[0].user_id == random_third_user.user.uid
+
+    @pytest.mark.asyncio
+    async def test_get_active_bucket_permissions_by_bucket_name3(
+        self,
+        db: AsyncSession,
+        random_bucket: Bucket,
+        random_second_user: UserWithAuthHeader,
+        random_third_user: UserWithAuthHeader,
+    ) -> None:
+        """
+        Test for getting all active bucket permissions for a specific bucket from the CRUD Repository.
+        The 'from' and 'to' timestamp are set.
+
+        Parameters
+        ----------
+        db : sqlalchemy.ext.asyncio.AsyncSession.
+            Async database session to perform query on. pytest fixture.
+        random_second_user : app.tests.utils.user.UserWithAuthHeader
+            Random third user who has no permissions for the bucket. pytest fixture.
+        random_third_user : app.tests.utils.user.UserWithAuthHeader
+            Random third user who has no permissions for the bucket. pytest fixture.
+        """
+        await add_permission_for_bucket(
+            db,
+            random_bucket.name,
+            random_second_user.user.uid,
+            to=datetime.now() - timedelta(weeks=1),
+            from_=datetime.now() - timedelta(weeks=2),
+        )
+        await add_permission_for_bucket(
+            db,
+            random_bucket.name,
+            random_third_user.user.uid,
+            to=datetime.now() + timedelta(weeks=1),
+            from_=datetime.now() - timedelta(weeks=1),
+        )
+        bucket_permissions = await CRUDBucketPermission.list(
+            db, bucket_name=random_bucket.name, permission_status=CRUDBucketPermission.PermissionStatus.ACTIVE
+        )
+        assert len(bucket_permissions) == 1
+        assert bucket_permissions[0].user_id == random_third_user.user.uid
+
+    @pytest.mark.asyncio
+    async def test_get_inactive_bucket_permissions_by_bucket_name1(
+        self,
+        db: AsyncSession,
+        random_bucket: Bucket,
+        random_second_user: UserWithAuthHeader,
+        random_third_user: UserWithAuthHeader,
+    ) -> None:
+        """
+        Test for getting all inactive bucket permissions for a specific bucket from the CRUD Repository.
+        Only the 'from' timestamp is set.
+
+        Parameters
+        ----------
+        db : sqlalchemy.ext.asyncio.AsyncSession.
+            Async database session to perform query on. pytest fixture.
+        random_second_user : app.tests.utils.user.UserWithAuthHeader
+            Random third user who has no permissions for the bucket. pytest fixture.
+        random_third_user : app.tests.utils.user.UserWithAuthHeader
+            Random third user who has no permissions for the bucket. pytest fixture.
+        """
+        await add_permission_for_bucket(
+            db, random_bucket.name, random_second_user.user.uid, from_=datetime.now() + timedelta(weeks=1)
+        )
+        await add_permission_for_bucket(
+            db, random_bucket.name, random_third_user.user.uid, from_=datetime.now() - timedelta(weeks=1)
+        )
+        bucket_permissions = await CRUDBucketPermission.list(
+            db, bucket_name=random_bucket.name, permission_status=CRUDBucketPermission.PermissionStatus.INACTIVE
+        )
+        assert len(bucket_permissions) == 1
+        assert bucket_permissions[0].user_id == random_second_user.user.uid
+
+    @pytest.mark.asyncio
+    async def test_get_inactive_bucket_permissions_by_bucket_name2(
+        self,
+        db: AsyncSession,
+        random_bucket: Bucket,
+        random_second_user: UserWithAuthHeader,
+        random_third_user: UserWithAuthHeader,
+    ) -> None:
+        """
+        Test for getting all inactive bucket permissions for a specific bucket from the CRUD Repository.
+        Only the 'to' timestamp is set.
+
+        Parameters
+        ----------
+        db : sqlalchemy.ext.asyncio.AsyncSession.
+            Async database session to perform query on. pytest fixture.
+        random_second_user : app.tests.utils.user.UserWithAuthHeader
+            Random third user who has no permissions for the bucket. pytest fixture.
+        random_third_user : app.tests.utils.user.UserWithAuthHeader
+            Random third user who has no permissions for the bucket. pytest fixture.
+        """
+        await add_permission_for_bucket(
+            db, random_bucket.name, random_second_user.user.uid, to=datetime.now() - timedelta(weeks=1)
+        )
+        await add_permission_for_bucket(
+            db, random_bucket.name, random_third_user.user.uid, to=datetime.now() + timedelta(weeks=1)
+        )
+        bucket_permissions = await CRUDBucketPermission.list(
+            db, bucket_name=random_bucket.name, permission_status=CRUDBucketPermission.PermissionStatus.INACTIVE
+        )
+        assert len(bucket_permissions) == 1
+        assert bucket_permissions[0].user_id == random_second_user.user.uid
+
+    @pytest.mark.asyncio
+    async def test_get_inactive_bucket_permissions_by_bucket_name3(
+        self,
+        db: AsyncSession,
+        random_bucket: Bucket,
+        random_second_user: UserWithAuthHeader,
+        random_third_user: UserWithAuthHeader,
+    ) -> None:
+        """
+        Test for getting all inactive bucket permissions for a specific bucket from the CRUD Repository.
+        The 'from' and 'to' timestamp are set.
+
+        Parameters
+        ----------
+        db : sqlalchemy.ext.asyncio.AsyncSession.
+            Async database session to perform query on. pytest fixture.
+        random_second_user : app.tests.utils.user.UserWithAuthHeader
+            Random third user who has no permissions for the bucket. pytest fixture.
+        random_third_user : app.tests.utils.user.UserWithAuthHeader
+            Random third user who has no permissions for the bucket. pytest fixture.
+        """
+        await add_permission_for_bucket(
+            db,
+            random_bucket.name,
+            random_second_user.user.uid,
+            to=datetime.now() - timedelta(weeks=1),
+            from_=datetime.now() - timedelta(weeks=2),
+        )
+        await add_permission_for_bucket(
+            db,
+            random_bucket.name,
+            random_third_user.user.uid,
+            to=datetime.now() + timedelta(weeks=1),
+            from_=datetime.now() - timedelta(weeks=1),
+        )
+        bucket_permissions = await CRUDBucketPermission.list(
+            db, bucket_name=random_bucket.name, permission_status=CRUDBucketPermission.PermissionStatus.INACTIVE
+        )
+        assert len(bucket_permissions) == 1
+        assert bucket_permissions[0].user_id == random_second_user.user.uid
+
     @pytest.mark.asyncio
     async def test_get_bucket_permissions_by_uid(
         self, db: AsyncSession, random_bucket_permission: BucketPermissionDB
@@ -71,18 +346,68 @@ class TestBucketPermissionCRUDGet:
         ----------
         db : sqlalchemy.ext.asyncio.AsyncSession.
             Async database session to perform query on. pytest fixture.
-        random_bucket_permission : app.models.bucket_permission.BucketPermission
+        random_bucket_permission : clowmdb.models.BucketPermission
             Bucket permission for a random bucket for testing. pytest fixture.
         """
-        bucket_permissions = await CRUDBucketPermission.get_permissions_for_user(
-            db, user_id=random_bucket_permission.user_id
-        )
+        bucket_permissions = await CRUDBucketPermission.list(db, uid=random_bucket_permission.user_id)
         assert len(bucket_permissions) == 1
         bucket_permission = bucket_permissions[0]
         assert bucket_permission.user_id == random_bucket_permission.user_id
         assert bucket_permission.bucket_name == random_bucket_permission.bucket_name
         assert bucket_permission.permissions == random_bucket_permission.permissions
 
+    @pytest.mark.asyncio
+    async def test_get_read_bucket_permissions_by_uid(
+        self,
+        db: AsyncSession,
+        random_bucket: Bucket,
+        random_second_user: UserWithAuthHeader,
+    ) -> None:
+        """
+        Test for getting only 'READ' bucket permissions for a specific user from the CRUD Repository..
+
+        Parameters
+        ----------
+        db : sqlalchemy.ext.asyncio.AsyncSession.
+            Async database session to perform query on. pytest fixture.
+        random_second_user : app.tests.utils.user.UserWithAuthHeader
+            Random third user who has no permissions for the bucket. pytest fixture.
+        """
+        await add_permission_for_bucket(
+            db, random_bucket.name, random_second_user.user.uid, permission=BucketPermissionDB.Permission.WRITE
+        )
+        bucket_permissions = await CRUDBucketPermission.list(
+            db, uid=random_second_user.user.uid, permission_types=[BucketPermissionDB.Permission.READ]
+        )
+        assert len(bucket_permissions) == 0
+
+    @pytest.mark.asyncio
+    async def test_get_active_bucket_permissions_by_uid(
+        self,
+        db: AsyncSession,
+        random_bucket: Bucket,
+        random_second_user: UserWithAuthHeader,
+    ) -> None:
+        """
+        Test for getting all active bucket permissions for a specific bucket from the CRUD Repository.
+        Only the 'from' timestamp is set.
+
+        Parameters
+        ----------
+        db : sqlalchemy.ext.asyncio.AsyncSession.
+            Async database session to perform query on. pytest fixture.
+        random_second_user : app.tests.utils.user.UserWithAuthHeader
+            Random third user who has no permissions for the bucket. pytest fixture.
+        """
+        await add_permission_for_bucket(
+            db, random_bucket.name, random_second_user.user.uid, from_=datetime.now() + timedelta(weeks=1)
+        )
+
+        bucket_permissions = await CRUDBucketPermission.list(
+            db, uid=random_second_user.user.uid, permission_status=CRUDBucketPermission.PermissionStatus.ACTIVE
+        )
+        assert len(bucket_permissions) == 0
+
 
 class TestBucketPermissionCRUDCreate:
     @pytest.mark.asyncio
@@ -94,7 +419,7 @@ class TestBucketPermissionCRUDCreate:
         ----------
         db : sqlalchemy.ext.asyncio.AsyncSession.
             Async database session to perform query on. pytest fixture.
-        random_bucket : app.models.bucket.Bucket
+        random_bucket : clowmdb.models.Bucket
             Random bucket for testing. pytest fixture.
         """
         permission = BucketPermissionSchema(bucket_name=random_bucket.name, uid="ImpossibleUser")
@@ -103,7 +428,7 @@ class TestBucketPermissionCRUDCreate:
 
     @pytest.mark.asyncio
     async def test_create_bucket_permissions_for_owner(
-        self, db: AsyncSession, random_user: User, random_bucket: Bucket
+        self, db: AsyncSession, random_user: UserWithAuthHeader, random_bucket: Bucket
     ) -> None:
         """
         Test for creating a bucket permission for the owner of the bucket.
@@ -112,12 +437,12 @@ class TestBucketPermissionCRUDCreate:
         ----------
         db : sqlalchemy.ext.asyncio.AsyncSession.
             Async database session to perform query on. pytest fixture.
-        random_user : app.models.user.User
+        random_user : app.tests.utils.user.UserWithAuthHeader
             Random user for testing who is owner of the bucket. pytest fixture.
-        random_bucket : app.models.bucket.Bucket
+        random_bucket : clowmdb.models.Bucket
             Random bucket for testing. pytest fixture.
         """
-        permission = BucketPermissionSchema(bucket_name=random_bucket.name, uid=random_user.uid)
+        permission = BucketPermissionSchema(bucket_name=random_bucket.name, uid=random_user.user.uid)
         with pytest.raises(ValueError):
             await CRUDBucketPermission.create(db, permission)
 
@@ -143,7 +468,7 @@ class TestBucketPermissionCRUDCreate:
 
     @pytest.mark.asyncio
     async def test_create_valid_bucket_permissions(
-        self, db: AsyncSession, random_second_user: User, random_bucket: Bucket
+        self, db: AsyncSession, random_second_user: UserWithAuthHeader, random_bucket: Bucket
     ) -> None:
         """
         Test for creating a valid bucket permission.
@@ -152,15 +477,15 @@ class TestBucketPermissionCRUDCreate:
         ----------
         db : sqlalchemy.ext.asyncio.AsyncSession.
             Async database session to perform query on. pytest fixture.
-        random_second_user : app.models.user.User
+        random_second_user : app.tests.utils.user.UserWithAuthHeader
             Random second user for testing. pytest fixture.
-        random_bucket : app.models.bucket.Bucket
+        random_bucket : clowmdb.models.Bucket
             Random bucket for testing. pytest fixture.
         """
-        permission = BucketPermissionSchema(bucket_name=random_bucket.name, uid=random_second_user.uid)
+        permission = BucketPermissionSchema(bucket_name=random_bucket.name, uid=random_second_user.user.uid)
         created_permission = await CRUDBucketPermission.create(db, permission)
 
-        assert created_permission.user_id == random_second_user.uid
+        assert created_permission.user_id == random_second_user.user.uid
         assert created_permission.bucket_name == random_bucket.name
 
 
@@ -176,10 +501,12 @@ class TestBucketPermissionCRUDDelete:
         ----------
         db : sqlalchemy.ext.asyncio.AsyncSession.
             Async database session to perform query on. pytest fixture.
-        random_bucket_permission : app.models.bucket_permission.BucketPermission
+        random_bucket_permission : clowmdb.models.BucketPermission
             Bucket permission for a random bucket for testing. pytest fixture.
         """
-        await CRUDBucketPermission.delete(db, random_bucket_permission)
+        await CRUDBucketPermission.delete(
+            db, bucket_name=random_bucket_permission.bucket_name, uid=random_bucket_permission.user_id
+        )
 
         stmt = select(BucketPermissionDB).where(
             and_(
@@ -187,7 +514,7 @@ class TestBucketPermissionCRUDDelete:
                 BucketPermissionDB.user_id == random_bucket_permission.user_id,
             )
         )
-        bucket_permission_db = (await db.execute(stmt)).scalar()
+        bucket_permission_db = await db.scalar(stmt)
 
         assert bucket_permission_db is None
 
@@ -204,14 +531,14 @@ class TestBucketPermissionCRUDUpdate:
         ----------
         db : sqlalchemy.ext.asyncio.AsyncSession.
             Async database session to perform query on. pytest fixture.
-        random_bucket_permission : app.models.bucket_permission.BucketPermission
+        random_bucket_permission : clowmdb.models.BucketPermission
             Bucket permission for a random bucket for testing. pytest fixture.
         """
-        new_from_time = datetime(2022, 1, 1, 0, 0)
+        new_from_time = round(datetime(2022, 1, 1, 0, 0).timestamp())
         new_params = BucketPermissionParametersSchema(
             from_timestamp=new_from_time,
-            to_timestamp=new_from_time + timedelta(days=1),
-            permission=PermissionEnum.READWRITE,
+            to_timestamp=new_from_time + 86400,  # plus one day
+            permission=BucketPermissionDB.Permission.READWRITE,
             file_prefix="pseudo/folder/",
         )
         new_permission = await CRUDBucketPermission.update_permission(db, random_bucket_permission, new_params)
diff --git a/app/tests/crud/test_user.py b/app/tests/crud/test_user.py
index 9166dda5300a0f3761cc97996b7dbf45b5028e3e..e387b67a4d2728ad89590917b3afb1c4944a0e31 100644
--- a/app/tests/crud/test_user.py
+++ b/app/tests/crud/test_user.py
@@ -1,37 +1,14 @@
-import random
-
 import pytest
 from sqlalchemy.ext.asyncio import AsyncSession
-from sqlalchemy.future import select
 
 from app.crud.crud_user import CRUDUser
-from app.models.user import User
+from app.tests.utils.user import UserWithAuthHeader
 from app.tests.utils.utils import random_lower_string
 
 
 class TestUserCRUD:
     @pytest.mark.asyncio
-    async def test_create_user(self, db: AsyncSession) -> None:
-        """
-        Test for creating a user in the User CRUD Repository.
-
-        Parameters
-        ----------
-        db : sqlalchemy.ext.asyncio.AsyncSession.
-            Async database session to perform query on. pytest fixture.
-        """
-        user = User(uid=random_lower_string(), display_name=random_lower_string())
-        await CRUDUser.create(db, user)
-
-        db_user = (await db.execute(select(User).where(User.uid == user.uid))).scalar()
-        assert db_user
-        assert db_user.uid == user.uid
-
-        await db.delete(db_user)
-        await db.commit()
-
-    @pytest.mark.asyncio
-    async def test_get_user_by_id(self, db: AsyncSession, random_user: User) -> None:
+    async def test_get_user_by_id(self, db: AsyncSession, random_user: UserWithAuthHeader) -> None:
         """
         Test for getting a user by id from the User CRUD Repository.
 
@@ -39,13 +16,13 @@ class TestUserCRUD:
         ----------
         db : sqlalchemy.ext.asyncio.AsyncSession.
             Async database session to perform query on. pytest fixture.
-        random_user : app.models.user.User
+        random_user : clowmdb.models.User
             Random user for testing. pytest fixture.
         """
-        user = await CRUDUser.get(db, random_user.uid)
+        user = await CRUDUser.get(db, random_user.user.uid)
         assert user
-        assert random_user.uid == user.uid
-        assert random_user.display_name == user.display_name
+        assert random_user.user.uid == user.uid
+        assert random_user.user.display_name == user.display_name
 
     @pytest.mark.asyncio
     async def test_get_unknown_user_by_id(
@@ -62,39 +39,3 @@ class TestUserCRUD:
         """
         user = await CRUDUser.get(db, random_lower_string(length=16))
         assert user is None
-
-    @pytest.mark.asyncio
-    async def test_search_successful_user_by_name(self, db: AsyncSession, random_user: User) -> None:
-        """
-        Test for searching a user by a substring of his name in the User CRUD Repository.
-
-        Parameters
-        ----------
-        db : sqlalchemy.ext.asyncio.AsyncSession.
-            Async database session to perform query on. pytest fixture.
-        random_user : app.models.user.User
-            Random user for testing. pytest fixture.
-        """
-        substring_indices = [0, 0]
-        while substring_indices[0] == substring_indices[1]:
-            substring_indices = sorted(random.choices(range(len(random_user.display_name)), k=2))
-
-        random_substring = random_user.display_name[substring_indices[0] : substring_indices[1]]
-        users = await CRUDUser.search_for_name(db, random_substring)
-        assert len(users) > 0
-        assert sum(1 for u in users if u.uid == random_user.uid) == 1
-
-    @pytest.mark.asyncio
-    async def test_search_non_existing_user_by_name(self, db: AsyncSession, random_user: User) -> None:
-        """
-        Test for searching a non-existing user by a substring of his name in the User CRUD Repository.
-
-        Parameters
-        ----------
-        db : sqlalchemy.ext.asyncio.AsyncSession.
-            Async database session to perform query on. pytest fixture.
-        random_user : app.models.user.User
-            Random user for testing. pytest fixture.
-        """
-        users = await CRUDUser.search_for_name(db, 2 * random_user.display_name)
-        assert sum(1 for u in users if u.uid == random_user.uid) == 0
diff --git a/app/tests/mocks/mock_rgw_admin.py b/app/tests/mocks/mock_rgw_admin.py
index fc2ba9460b253f223fc02e7a7fe9b358c8611a15..6100a232c7e4fb22c594b945322be26900f00464 100644
--- a/app/tests/mocks/mock_rgw_admin.py
+++ b/app/tests/mocks/mock_rgw_admin.py
@@ -1,3 +1,5 @@
+from typing import Dict, List
+
 from fastapi import status
 from rgwadmin.exceptions import RGWAdminException
 
@@ -10,9 +12,9 @@ class MockRGWAdmin:
 
     Functions
     ---------
-    get_user(uid: str, stats: bool = False) -> dict[str, list[dict[str, str]]]
+    get_user(uid: str, stats: bool = False) -> Dict[str, List[Dict[str, str]]]
         Returns a dict with only one key 'keys'.
-    create_key(uid: str, key_type: str = "s3", generate_key: bool = True) -> dict[str, list[dict[str, str]]]
+    create_key(uid: str, key_type: str = "s3", generate_key: bool = True) -> Dict[str, List[Dict[str, str]]]
         Creates a new key for a user.
     remove_key(access_key: str, uid: str) -> None
         Remove a key for a user.
@@ -20,7 +22,7 @@ class MockRGWAdmin:
         Deletes all keys for a user.
     """
 
-    _keys: dict[str, list[dict[str, str]]]
+    _keys: Dict[str, List[Dict[str, str]]]
 
     def __init__(self) -> None:
         self._keys = {}
@@ -28,7 +30,7 @@ class MockRGWAdmin:
     def create_user(self, uid: str, max_buckets: int, display_name: str) -> None:
         self.create_key(uid)
 
-    def get_user(self, uid: str, stats: bool = False) -> dict[str, list[dict[str, str]]]:  # noqa
+    def get_user(self, uid: str, stats: bool = False) -> Dict[str, List[Dict[str, str]]]:  # noqa
         """
         Get the keys from a user.
 
@@ -41,7 +43,7 @@ class MockRGWAdmin:
 
         Returns
         -------
-        user_keys : dict[str, list[dict[str, str]]]
+        user_keys : Dict[str, List[Dict[str, str]]]
             The user object with the associated keys. See Notes.
 
         Notes
@@ -62,7 +64,7 @@ class MockRGWAdmin:
             return {"keys": self._keys[uid]}
         return {"keys": []}
 
-    def create_key(self, uid: str, key_type: str = "s3", generate_key: bool = True) -> list[dict[str, str]]:  # noqa
+    def create_key(self, uid: str, key_type: str = "s3", generate_key: bool = True) -> List[Dict[str, str]]:  # noqa
         """
         Create a S3 key for a user.
 
@@ -77,7 +79,7 @@ class MockRGWAdmin:
 
         Returns
         -------
-        keys : list[dict[str, str]]
+        keys : List[Dict[str, str]]
             All keys for the user including the new one.
         """
         new_key = {"user": uid, "access_key": random_lower_string(20).upper(), "secret_key": random_lower_string(40)}
diff --git a/app/tests/mocks/mock_s3_resource.py b/app/tests/mocks/mock_s3_resource.py
index 3561daaabdd68f2d5994882208f499fe7d894816..2a12a5281833db305d05d3ad78af8b608aff6fcb 100644
--- a/app/tests/mocks/mock_s3_resource.py
+++ b/app/tests/mocks/mock_s3_resource.py
@@ -1,7 +1,13 @@
 from datetime import datetime
+from typing import TYPE_CHECKING, Dict, List, Optional
 
 from botocore.exceptions import ClientError
 
+if TYPE_CHECKING:
+    from mypy_boto3_s3.type_defs import CORSConfigurationTypeDef
+else:
+    CORSConfigurationTypeDef = object
+
 
 class MockS3Object:
     """
@@ -118,6 +124,55 @@ class MockS3BucketPolicy:
         """
         self.policy = Policy
 
+    def load(self) -> None:
+        pass
+
+
+class MockS3CorsRule:
+    """
+    Mock S3 Cors Configuration for the boto3 BucketCors for testing purposes.
+
+    Functions
+    ---------
+    put(CORSConfiguration: CORSConfig) -> None
+        Save a new bucket CORS rule.
+
+    Attributes
+    ----------
+    rules : str
+        List of all CORS rules on the bucket.
+    """
+
+    def __init__(self) -> None:
+        self.rules: Optional[CORSConfigurationTypeDef] = None
+
+    def put(self, CORSConfiguration: CORSConfigurationTypeDef) -> None:
+        """
+        Save a new bucket CORS rule.
+
+        Parameters
+        ----------
+        CORSConfiguration : mypy_boto3_s3.type_defs.CORSConfigurationTypeDef
+            The new policy as str.
+
+        Notes
+        -----
+        A configuration has the following form
+        {
+            "CORSRules": [
+                {
+                    "ID": string,
+                    "AllowedHeaders": List[string],
+                    "AllowedMethods": List[string],
+                    "AllowedOrigins": List[string],
+                    "ExposeHeaders": List[string],
+                    "MaxAgeSeconds": int,
+                },
+            ]
+        }
+        """
+        self.rules = CORSConfiguration
+
 
 class MockS3Bucket:
     """
@@ -131,9 +186,9 @@ class MockS3Bucket:
         Create the bucket in the mock service.
     delete() -> None
         Delete the bucket in the mock service
-    delete_objects(Delete: dict[str, list[dict[str, str]]]) -> None
+    delete_objects(Delete: Dict[str, List[Dict[str, str]]]) -> None
         Delete multiple objects in the bucket.
-    get_objects() -> list[app.tests.mocks.mock_s3_resource.MockS3ObjectSummary]
+    get_objects() -> List[app.tests.mocks.mock_s3_resource.MockS3ObjectSummary]
         List of MockS3ObjectSummary in the bucket.
     add_object(obj: app.tests.mocks.mock_s3_resource.MockS3ObjectSummary) -> None
         Add a MockS3ObjectSummary to the bucket.
@@ -157,7 +212,7 @@ class MockS3Bucket:
 
         Functions
         ---------
-        all() -> list[app.tests.mocks.mock_s3_resource.MockS3ObjectSummary]
+        all() -> List[app.tests.mocks.mock_s3_resource.MockS3ObjectSummary]
             Get the saved list.
         filter(Prefix: str) -> app.tests.mocks.mock_s3_resource.MockS3Bucket.MockS3ObjectList
             Filter the object in the list by the prefix all their keys should have.
@@ -167,16 +222,16 @@ class MockS3Bucket:
             Delete a MockS3ObjectSummary from the list
         """
 
-        def __init__(self, obj_list: list[MockS3ObjectSummary] | None = None) -> None:
-            self._objs: list[MockS3ObjectSummary] = [] if obj_list is None else obj_list
+        def __init__(self, obj_list: Optional[List[MockS3ObjectSummary]] = None) -> None:
+            self._objs: List[MockS3ObjectSummary] = [] if obj_list is None else obj_list
 
-        def all(self) -> list[MockS3ObjectSummary]:
+        def all(self) -> List[MockS3ObjectSummary]:
             """
             Get the saved list.
 
             Returns
             -------
-            objects : list[app.tests.mocks.mock_s3_resource.MockS3ObjectSummary]
+            objects : List[app.tests.mocks.mock_s3_resource.MockS3ObjectSummary]
                 List of MockS3ObjectSummary
             """
             return self._objs
@@ -235,6 +290,7 @@ class MockS3Bucket:
         self.objects = MockS3Bucket.MockS3ObjectList()
         self._parent_service: MockS3ServiceResource = parent_service
         self.policy = MockS3BucketPolicy(name)
+        self.cors = MockS3CorsRule()
 
     def Policy(self) -> MockS3BucketPolicy:
         """
@@ -247,6 +303,9 @@ class MockS3Bucket:
         """
         return self.policy
 
+    def Cors(self) -> MockS3CorsRule:
+        return self.cors
+
     def create(self) -> None:
         """
         Create the bucket in the mock S3 service.
@@ -259,13 +318,13 @@ class MockS3Bucket:
         """
         self._parent_service.delete_bucket(self.name)
 
-    def delete_objects(self, Delete: dict[str, list[dict[str, str]]]) -> None:
+    def delete_objects(self, Delete: Dict[str, List[Dict[str, str]]]) -> None:
         """
         Delete multiple objects in the bucket.
 
         Parameters
         ----------
-        Delete : dict[str, list[dict[str, str]]]
+        Delete : Dict[str, List[Dict[str, str]]]
             The keys of the objects to delete.
 
         Notes
@@ -280,14 +339,14 @@ class MockS3Bucket:
         for key_object in Delete["Objects"]:
             self.objects.delete(key=key_object["Key"])
 
-    def get_objects(self) -> list[MockS3ObjectSummary]:
+    def get_objects(self) -> List[MockS3ObjectSummary]:
         """
         Get the MockS3ObjectSummary in the bucket.
         Convenience function for testing.
 
         Returns
         -------
-        objects : list[app.tests.mocks.mock_s3_resource.MockS3ObjectSummary]
+        objects : List[app.tests.mocks.mock_s3_resource.MockS3ObjectSummary]
             List of MockS3ObjectSummary in the bucket.
         """
         return self.objects.all()
@@ -331,7 +390,7 @@ class MockS3ServiceResource:
     """
 
     def __init__(self) -> None:
-        self._buckets: dict[str, MockS3Bucket] = {}
+        self._buckets: Dict[str, MockS3Bucket] = {}
 
     def Bucket(self, name: str) -> MockS3Bucket:
         """
diff --git a/app/tests/unit/test_bucket_permission_scheme.py b/app/tests/unit/test_bucket_permission_scheme.py
index 817b149361d82b8b1bdc373c59292400eaa64dae..8eb79ec9168f8e802632ffea073f5d12c5f572d3 100644
--- a/app/tests/unit/test_bucket_permission_scheme.py
+++ b/app/tests/unit/test_bucket_permission_scheme.py
@@ -1,8 +1,8 @@
 from datetime import datetime
 
 import pytest
+from clowmdb.models import BucketPermission
 
-from app.models.bucket_permission import PermissionEnum
 from app.schemas.bucket_permission import BucketPermissionIn
 from app.tests.utils.utils import random_lower_string
 
@@ -14,7 +14,9 @@ class _TestPermissionPolicy:
         Generate a base READ bucket permission schema.
         """
         return BucketPermissionIn(
-            uid=random_lower_string(), bucket_name=random_lower_string(), permission=PermissionEnum.READ
+            uid=random_lower_string(),
+            bucket_name=random_lower_string(),
+            permission=BucketPermission.Permission.READ,
         )
 
 
@@ -61,9 +63,9 @@ class TestPermissionPolicyPermissionType(_TestPermissionPolicy):
         random_base_permission : app.schemas.bucket_permission.BucketPermissionOut
             Random base bucket permission for testing. pytest fixture.
         """
-        random_base_permission.permission = PermissionEnum.WRITE
+        random_base_permission.permission = BucketPermission.Permission.WRITE
         stmts = random_base_permission.map_to_bucket_policy_statement(user_id=random_lower_string())
-        assert len(stmts) == 1
+        assert len(stmts) == 2
 
         object_stmt = stmts[0]
         with pytest.raises(KeyError):
@@ -72,6 +74,13 @@ class TestPermissionPolicyPermissionType(_TestPermissionPolicy):
         assert "s3:PutObject" in object_stmt["Action"]
         assert "s3:DeleteObject" in object_stmt["Action"]
 
+        bucket_stmt = stmts[1]
+        with pytest.raises(KeyError):
+            assert bucket_stmt["Condition"]
+        assert len(bucket_stmt["Action"]) == 2
+        assert "s3:ListBucket" in bucket_stmt["Action"]
+        assert "s3:DeleteObject" in bucket_stmt["Action"]
+
     def test_READWRITE_permission(self, random_base_permission: BucketPermissionIn) -> None:
         """
         Test for converting a READWRITE Permission into a bucket policy statement.
@@ -81,7 +90,7 @@ class TestPermissionPolicyPermissionType(_TestPermissionPolicy):
         random_base_permission : app.schemas.bucket_permission.BucketPermissionOut
             Random base bucket permission for testing. pytest fixture.
         """
-        random_base_permission.permission = PermissionEnum.READWRITE
+        random_base_permission.permission = BucketPermission.Permission.READWRITE
         stmts = random_base_permission.map_to_bucket_policy_statement(user_id=random_lower_string())
         assert len(stmts) == 2
 
@@ -96,8 +105,9 @@ class TestPermissionPolicyPermissionType(_TestPermissionPolicy):
         bucket_stmt = stmts[1]
         with pytest.raises(KeyError):
             assert bucket_stmt["Condition"]
-        assert len(bucket_stmt["Action"]) == 1
-        assert bucket_stmt["Action"][0] == "s3:ListBucket"
+        assert len(bucket_stmt["Action"]) == 2
+        assert "s3:ListBucket" in bucket_stmt["Action"]
+        assert "s3:DeleteObject" in bucket_stmt["Action"]
 
 
 class TestPermissionPolicyCondition(_TestPermissionPolicy):
@@ -110,8 +120,9 @@ class TestPermissionPolicyCondition(_TestPermissionPolicy):
         random_base_permission : app.schemas.bucket_permission.BucketPermissionOut
             Random base bucket permission for testing. pytest fixture.
         """
-        time = datetime.now()
-        random_base_permission.to_timestamp = time
+        timestamp = round(datetime.now().timestamp())
+        time = datetime.fromtimestamp(timestamp)  # avoid rounding error
+        random_base_permission.to_timestamp = timestamp
 
         stmts = random_base_permission.map_to_bucket_policy_statement(user_id=random_lower_string())
         assert len(stmts) == 2
@@ -138,7 +149,9 @@ class TestPermissionPolicyCondition(_TestPermissionPolicy):
             Random base bucket permission for testing. pytest fixture.
         """
         time = datetime.now()
-        random_base_permission.from_timestamp = time
+        timestamp = round(datetime.now().timestamp())
+        time = datetime.fromtimestamp(timestamp)  # avoid rounding error
+        random_base_permission.from_timestamp = timestamp
 
         stmts = random_base_permission.map_to_bucket_policy_statement(user_id=random_lower_string())
         assert len(stmts) == 2
diff --git a/app/tests/utils/bucket.py b/app/tests/utils/bucket.py
index f4982fe5f0f6d1cfb4679cb22a7f5d5d26f0a5cf..76b5634a4f066010829961ce74aa5172d9319d10 100644
--- a/app/tests/utils/bucket.py
+++ b/app/tests/utils/bucket.py
@@ -1,12 +1,10 @@
 from datetime import datetime
+from typing import Optional
 
 import pytest
+from clowmdb.models import Bucket, BucketPermission, User
 from sqlalchemy.ext.asyncio import AsyncSession
 
-from app.models.bucket import Bucket
-from app.models.bucket_permission import BucketPermission, PermissionEnum
-from app.models.user import User
-
 from .utils import random_lower_string
 
 
@@ -19,12 +17,12 @@ async def create_random_bucket(db: AsyncSession, user: User) -> Bucket:
     ----------
     db : sqlalchemy.ext.asyncio.AsyncSession.
         Async database session to perform query on.
-    user : app.models.user.User
+    user : clowmdb.models.User
         Owner of the bucket.
 
     Returns
     -------
-    bucket : app.models.bucket.Bucket
+    bucket : clowmdb.models.Bucket
         Newly created bucket.
     """
     bucket = Bucket(
@@ -42,9 +40,9 @@ async def add_permission_for_bucket(
     db: AsyncSession,
     bucket_name: str,
     uid: str,
-    from_: datetime | None = None,
-    to: datetime | None = None,
-    permission: PermissionEnum = PermissionEnum.READ,
+    from_: Optional[datetime] = None,
+    to: Optional[datetime] = None,
+    permission: BucketPermission.Permission = BucketPermission.Permission.READ,
 ) -> None:
     """
     Creates Permission to a bucket for a user in the database.
@@ -61,14 +59,14 @@ async def add_permission_for_bucket(
         Time when from when the permission should be active.
     to : datetime.datetime | None, default None
         Time till when the permissions should be active.
-    permission : app.models.bucket_permission.PermissionEnum, default PermissionEnum.READ
+    permission : clowmdb.models.BucketPermission.Permission, default BucketPermission.Permission.READ  # noqa:E501
         The permission the user is granted.
     """
     perm = BucketPermission(
         user_id=uid,
         bucket_name=bucket_name,
-        from_=from_,
-        to=to,
+        from_=round(from_.timestamp()) if from_ is not None else None,
+        to=round(to.timestamp()) if to is not None else None,
         permissions=permission.name,
     )
     db.add(perm)
diff --git a/app/tests/utils/user.py b/app/tests/utils/user.py
index ad70cf1298f8c6fae6fc95950f28492b7ff963e5..109cc0a9e56bf546a3d23c060026f70ceb31f200 100644
--- a/app/tests/utils/user.py
+++ b/app/tests/utils/user.py
@@ -1,32 +1,73 @@
+from dataclasses import dataclass
+from datetime import datetime, timedelta
+from typing import Dict
+
 import pytest
+from authlib.jose import JsonWebToken
+from clowmdb.models import User
 from sqlalchemy.ext.asyncio import AsyncSession
 
-from app.core.security import create_access_token
-from app.models.user import User
-
 from .utils import random_lower_string
 
+_jwt = JsonWebToken(["HS256"])
 
-@pytest.mark.asyncio
-def get_authorization_headers(uid: str) -> dict[str, str]:
+
+@dataclass
+class UserWithAuthHeader:
+    auth_headers: Dict[str, str]
+    user: User
+
+
+def get_authorization_headers(uid: str, secret: str = "SuperSecret") -> Dict[str, str]:
     """
-    Login a user and return the correct headers for subsequent requests.
+    Create a valid JWT and return the correct headers for subsequent requests.
 
     Parameters
     ----------
     uid : str
         UID of the user who should be logged in.
-
+    secret : str
+        Secret to sign the JWT with
     Returns
     -------
-    headers : dict[str,str]
+    headers : Dict[str,str]
         HTTP Headers to authorize each request.
     """
-    a_token = create_access_token(uid)
-    headers = {"Authorization": f"Bearer {a_token}"}
+    to_encode = {"sub": uid, "exp": datetime.utcnow() + timedelta(hours=1)}
+    encoded_jwt = _jwt.encode(header={"alg": "HS256"}, payload=to_encode, key=secret)
+
+    headers = {"Authorization": f"Bearer {encoded_jwt.decode('utf-8')}"}
     return headers
 
 
+def decode_mock_token(token: str, secret: str = "SuperSecret") -> Dict[str, str]:
+    """
+    Decode and verify a test JWT token.
+
+    Parameters
+    ----------
+    token : str
+        The JWT to decode.
+    secret : str
+        Secret to use for signature verification
+
+    Returns
+    -------
+    decoded_token : Dict[str, str]
+        Payload of the decoded token.
+    """
+    claims = _jwt.decode(
+        s=token,
+        key=secret,
+        claims_options={
+            "sub": {"essential": True},
+            "exp": {"essential": True},
+        },
+    )
+    claims.validate()
+    return claims
+
+
 @pytest.mark.asyncio
 async def create_random_user(db: AsyncSession) -> User:
     """
@@ -39,7 +80,7 @@ async def create_random_user(db: AsyncSession) -> User:
 
     Returns
     -------
-    user : app.models.user.User
+    user : clowmdb.models.User
         Newly created user.
     """
     user = User(
diff --git a/app/tests/utils/utils.py b/app/tests/utils/utils.py
index 3699b14788955fcee9b41c76eac1d811e3491e47..e20970d9ce67751af07a4fac165f3527fa906569 100644
--- a/app/tests/utils/utils.py
+++ b/app/tests/utils/utils.py
@@ -1,7 +1,9 @@
 import random
 import string
 from datetime import datetime
-from typing import Any
+from typing import Any, Dict, Optional
+
+import httpx
 
 
 def random_lower_string(length: int = 32) -> str:
@@ -33,9 +35,9 @@ def random_ipv4_string() -> str:
     return ".".join(str(random.randint(0, 255)) for _ in range(4))
 
 
-def json_datetime_converter(obj: Any) -> str | None:
+def json_datetime_converter(obj: Any) -> Optional[str]:
     """
-    helper function for the json converter to covert the object into a string format if it is a datetime object.\n
+    Helper function for the json converter to covert the object into a string format if it is a datetime object.\n
     Parse a datetime object into the format YYYY-MM-DDTHH:MM:SS, e.g. 2022-01-01T00:00:00
 
     Parameters
@@ -51,3 +53,25 @@ def json_datetime_converter(obj: Any) -> str | None:
     if isinstance(obj, datetime):
         return obj.strftime("%Y-%m-%dT%H:%M:%S")
     return None
+
+
+def request_admin_permission(request: httpx.Request) -> bool:
+    """
+    Rudimentary helper function to determine if the authorization request needs the 'administrator' role.
+
+    Parameters
+    ----------
+    request : httpx.Request
+        Raw Request Object from httpx
+
+    Returns
+    -------
+    decision : bool
+        Flag if the request needs the 'administrator' role
+    """
+    request_body: Dict[str, str] = eval(request.content.decode("utf-8"))["input"]
+    operation = request_body["operation"]
+    checks = "any" in operation
+    if "bucket_permission" in request_body["resource"]:
+        checks = checks or "all" in operation
+    return checks
diff --git a/ceph/README.md b/ceph/README.md
deleted file mode 100644
index 3c417a3f7416598df2ac46c1695f1e9d3e7414e0..0000000000000000000000000000000000000000
--- a/ceph/README.md
+++ /dev/null
@@ -1,63 +0,0 @@
-Following mostly the [cephadm install guide](https://docs.ceph.com/en/pacific/cephadm/install/) to
-setup a ceph cluster within Openstack.
-
-# Requirements
-
-- Create 6 cloud instances (with additional ephemeral disk space) and adjust inventory file
-- Setup cloud instances.
-  `ansible-playbook  -i hosts  site.yml`
-- Clean up ephemeral disk. **This will remove an existing partition!**
-  `ansible all  -i hosts -b -m shell -a "parted -s /dev/vdb rm 1"`
-- Reboot (for kernel updates)
-  `ansible all  -i hosts -b -m shell -a "reboot"`
-
-# Install cephadm from URL
-
-```
-curl --silent --remote-name --location https://github.com/ceph/ceph/raw/pacific/src/cephadm/cephadm
-chmod +x cephadm
-sudo ./cephadm add-repo --release pacific
-sudo ./cephadm install
-```
-
-# Bootstrap
-
-- Bootstrap first node, make sure *NOT* using root as ssh user
-`sudo cephadm bootstrap --mon-ip 192.168.192.118 --ssh-user ubuntu`
-
-
-
-# Add more hosts
-
-```
-sudo cephadm shell -- ceph orch host add ceph-2 192.168.192.102 --labels _admin
-sudo cephadm shell -- ceph orch host add ceph-3 192.168.192.155 --labels _admin
-sudo cephadm shell -- ceph orch host add ceph-4 192.168.192.15
-sudo cephadm shell -- ceph orch host add ceph-5 192.168.192.96
-sudo cephadm shell -- ceph orch host add ceph-6 192.168.192.111
-```
-
-# Add additional monitors
-
-- Deploy 5 monitor nodes on random hosts
-`sudo cephadm shell -- ceph config set mon public_network 192.168.192.0/24`
-
-# Add storage
-
-- Add ephemeral disks as OSD
-`sudo cephadm shell -- ceph orch apply osd --all-available-devices`
-
-# Add RGWs
-
-- Add two rados gateways
-`sudo cephadm shell -- ceph orch apply rgw s3`
-- Start two listen processes on each rgw host
-`sudo cephadm shell -- ceph orch host label add ceph-1 rgw`
-`sudo cephadm shell -- ceph orch host label add ceph-2 rgw`
-`sudo cephadm shell -- ceph orch apply rgw s3 '--placement=label:rgw count-per-host:2' --port=8000`
-
-# Create a VPN tunnel
-With [sshuttle](https://github.com/sshuttle/sshuttle) you can easily create a VPN connection from your
-workstation/notebook to our cloud based ceph cluster. **sshuttle** can be installed using `pip`.
-
-`$ sshuttle -r  jkrueger@129.70.51.109:30118  192.168.192.0/24`
diff --git a/ceph/playbook/files/public_keys/dgoebel b/ceph/playbook/files/public_keys/dgoebel
deleted file mode 100644
index 45fc14759d7cbe8423dc08290b1dffdd9ac59d0f..0000000000000000000000000000000000000000
--- a/ceph/playbook/files/public_keys/dgoebel
+++ /dev/null
@@ -1 +0,0 @@
-ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAID7Cu6YaS6GanmMiL8pzFOCb8QXecUxFea51iBy97+OO daniel@daniel-thinkpad
diff --git a/ceph/playbook/files/public_keys/jkrueger b/ceph/playbook/files/public_keys/jkrueger
deleted file mode 100644
index 71eb3172c1ba3cf00d128932e2ab6bafe41a90e8..0000000000000000000000000000000000000000
--- a/ceph/playbook/files/public_keys/jkrueger
+++ /dev/null
@@ -1 +0,0 @@
-ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDTnooRQoKGIbdtZvnG/qsANHeU4qzD/iV/VdLBsbSo0KlJUvjh0kIJGxe2Ums5qh/CV3QA4xjq5A0rDUfU84k4iR8zGRnxIrCjWImfm5/Dd1OvokqorJ02PmRjM1krZhVZaWjERIzSHRJTVd4ivw8pSm080lv4uo9T/0xzWeyvBQ0ZWi6KjClJYA7gl0DGOgypIh54JCAIaWgoYXAcCw4a5wu2W8dpjCJWn4M1Ci6eiAFQooa2xrgFRJI6/BLa3GgI38e7W9IbAiWN224RWWkjVp49J2tBwlVFNsrWoIcbIpcGsjlRnZWtRCquRncq5KKTt2V2BdKJFF36OAJaHGIP
diff --git a/ceph/playbook/hosts b/ceph/playbook/hosts
deleted file mode 100644
index 7717a8a128ec5d81b2546e491de55798a2f3584c..0000000000000000000000000000000000000000
--- a/ceph/playbook/hosts
+++ /dev/null
@@ -1,7 +0,0 @@
-[ceph]
-ceph-1 ansible_host=129.70.51.109 ansible_port=30118
-ceph-2 ansible_host=129.70.51.109 ansible_port=30102
-ceph-3 ansible_host=129.70.51.109 ansible_port=30155
-ceph-4 ansible_host=129.70.51.109 ansible_port=30015
-ceph-5 ansible_host=129.70.51.109 ansible_port=30096
-ceph-6 ansible_host=129.70.51.109 ansible_port=30111
diff --git a/ceph/playbook/site.yml b/ceph/playbook/site.yml
deleted file mode 100644
index 6084591cd965239bad0918c406ed273782ca6226..0000000000000000000000000000000000000000
--- a/ceph/playbook/site.yml
+++ /dev/null
@@ -1,51 +0,0 @@
-- hosts: ceph
-  become: yes
-  tasks:
-    - name: update & upgrade
-      apt:
-        upgrade: true
-        update_cache: true
-
-    - name: Install common software package
-      apt:
-        name: [ 'software-properties-common',
-                'docker.io',
-                'python3',
-                'chrony',
-                'auditd',
-                'audispd-plugins',
-                'net-tools'
-        ]
-
-    - name: Create users according to included public_keys
-      user:
-        name: "{{ item.split('/')[-1] }}"
-        state: present
-        create_home: true
-        shell: /bin/bash
-      with_fileglob:
-        - "public_keys/*"
-
-    - name: Add public keys to user
-      authorized_key:
-        user: "{{ item.split('/')[-1] }}"
-        state: present
-        key: "{{ lookup('file', item) }}"
-      with_fileglob:
-        - "public_keys/*"
-
-    - name: Add users to sudoers
-      lineinfile:
-        dest: /etc/sudoers.d/50-denbi
-        state: present
-        regexp: "^{{ item.split('/')[-1] }}"
-        line: "{{ item.split('/')[-1] }} ALL=(ALL) NOPASSWD:ALL"
-        create: yes
-      with_fileglob:
-        - "public_keys/*"
-
-    - name: Clean up inital ephemeral setup (/dev/vdb -> /mnt)
-      mount:
-        path: /mnt
-        src: /dev/vdb
-        state: absent
diff --git a/oidc_dev_example/clients_config.json b/oidc_dev_example/clients_config.json
deleted file mode 100644
index afc8e5ea257ad11ea1e693fd8860d6833052e7f4..0000000000000000000000000000000000000000
--- a/oidc_dev_example/clients_config.json
+++ /dev/null
@@ -1,21 +0,0 @@
-[
-    {
-        "ClientId": "",
-        "ClientSecrets": [
-          ""
-        ],
-        "RedirectUris": ["http://localhost:8000/api/auth/callback", "http://localhost:9999/api/auth/callback",
-                         "http://127.0.0.1:8000/api/auth/callback", "http://127.0.0.1:9999/api/auth/callback"],
-        "Description": "Client for authorization code flow",
-        "AllowedGrantTypes": [
-            "authorization_code"
-        ],
-        "AlwaysIncludeUserClaimsInIdToken": false,
-        "AllowedScopes": [
-            "openid",
-            "profile",
-            "aarc"
-        ],
-        "RequirePkce": true
-    }
-]
diff --git a/oidc_dev_example/identity_resources.json b/oidc_dev_example/identity_resources.json
deleted file mode 100644
index e0db9cc12acd1ba78343b347ca5aabdd8c9da5db..0000000000000000000000000000000000000000
--- a/oidc_dev_example/identity_resources.json
+++ /dev/null
@@ -1,6 +0,0 @@
-[
-  {
-    "Name": "aarc",
-    "ClaimTypes": ["voperson_id"]
-  }
-]
diff --git a/oidc_dev_example/server_options.json b/oidc_dev_example/server_options.json
deleted file mode 100644
index 49137b4ed943246c6841a6b1f44fbdddcea3c748..0000000000000000000000000000000000000000
--- a/oidc_dev_example/server_options.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
-    "AccessTokenJwtType": "JWT",
-    "Discovery": {
-      "ShowKeySet": true
-    },
-    "Authentication": {
-      "CookieSameSiteMode": "Lax",
-      "CheckSessionCookieSameSiteMode": "Lax"
-    }
-}
diff --git a/oidc_dev_example/users_config.json b/oidc_dev_example/users_config.json
deleted file mode 100644
index 027a0494f87513edd4077a260bbcff79a6f2d991..0000000000000000000000000000000000000000
--- a/oidc_dev_example/users_config.json
+++ /dev/null
@@ -1,21 +0,0 @@
-[
-  {
-    "SubjectId": "1",
-    "Username": "skywalker",
-    "Password": "password",
-    "Claims": [
-      {
-        "Type": "name",
-        "Value": "Luke Skuwalker"
-      },
-      {
-        "Type": "voperson_id",
-        "Value": "4f127a515bf8a1056c67db90d751b1692ec33d8d4ba2d3f5611d15a23aa8a387@lifescience-ri.eu",
-      },
-      {
-        "Type": "eduperson_principal_name",
-        "Value": "skywalker"
-      }
-    ]
-  }
-]
diff --git a/pyproject.toml b/pyproject.toml
index d9d192261fac585fdca05b73dd32f5e8c23c8d71..7e3d3f75d952f63ed36f9363f22d4e03863e69d8 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -6,6 +6,10 @@ balanced_wrapping = true
 [tool.black]
 line-length = 120
 
+[tool.ruff]
+line-length = 120
+target-version = "py311"
+
 [tool.mypy]
 plugins = ["pydantic.mypy", "sqlalchemy.ext.mypy.plugin"]
 ignore_missing_imports = true
@@ -17,14 +21,11 @@ concurrency = [
     "thread"
 ]
 omit = [
-    "app/tests/*",
+    "app/tests/**",
     "app/check_database_connection.py",
     "app/check_ceph_connection.py",
     "app/check_oidc_connection.py",
-    "app/db/base*",
     "app/core/config.py",
-    "app/main.py",
-    "app/api/miscellaneous_endpoints.py"
 ]
 
 [tool.coverage.report]
diff --git a/requirements-dev.txt b/requirements-dev.txt
index 9ee5b04147344ef7f7a2644d3cb6e328e4ab1b7a..05c18639d37836dec2a6ad7ab360790a16f3c573 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -1,18 +1,16 @@
 # test packages
-pytest>=7.2.0,<7.3.0
-pytest-asyncio>=0.20.0,<0.21.0
-pytest-cov>=4.0.0,<4.1.0
-coverage[toml]>=6.5.0,<6.6.0
+pytest>=7.4.0,<7.5.0
+pytest-asyncio>=0.21.0,<0.22.0
+pytest-cov>=4.1.0,<4.2.0
+coverage[toml]>=7.3.0,<7.4.0
 # Linters
-flake8>=5.0.0,<5.1.0
-autoflake>=1.7.0,<1.8.0
-black>=22.10.0,<22.11.0
-isort>=5.10.0,<5.11.0
-mypy>=0.990,<0.999
+ruff>=0.1.0,<0.2.0
+black>=23.11.0,<23.12.0
+isort>=5.12.0,<5.13.0
+mypy>=1.7.0,<1.8.0
 # stubs for mypy
-boto3-stubs-lite[s3]>=1.26.0,<1.27.0
-sqlalchemy2-stubs
+boto3-stubs-lite[s3]>=1.33.0,<1.34.0
 types-requests
 # Miscellaneous
-pre-commit>=2.20.0,<2.21.0
+pre-commit>=3.5.0,<3.6.0
 python-dotenv
diff --git a/requirements.txt b/requirements.txt
index 6b971d09cda581e1ffc33fa2b30a881f3d7b9947..bf948033cd93090ac324ede1bb973d3524f3dc08 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,19 +1,25 @@
+--extra-index-url https://gitlab.ub.uni-bielefeld.de/api/v4/projects/5493/packages/pypi/simple
+clowmdb>=2.3.0,<2.4.0
+
 # Webserver packages
-anyio>=3.6.0,<3.7.0
-fastapi>=0.87.0,<0.88.0
-pydantic>=1.9.0,<2.0.0
-uvicorn>=0.19.0,<0.20.0
+anyio>=3.7.0,<4.0.0
+fastapi>=0.104.0,<0.105.0
+pydantic>=2.5.0,<2.6.0
+pydantic-settings>=2.1.0,<2.2.0
+uvicorn>=0.24.0,<0.25.0
 # Database packages
-PyMySQL>=1.0.2,<1.1.0
-SQLAlchemy>=1.4.0,<1.5.0
-alembic>=1.8.0,<1.9.0
-aiomysql>=0.1.0,<0.2.0
+PyMySQL>=1.1.0,<1.2.0
+SQLAlchemy>=2.0.0,<2.1.0
+aiomysql>=0.2.0,<0.3.0
 # Security packages
-authlib>=1.1.0,<1.2.0
+authlib>=1.2.0,<1.3.0
 # Ceph and S3 packages
-boto3>=1.26.0,<1.27.0
-rgwadmin>=2.3.0,<2.4.0
+boto3>=1.33.0,<1.34.0
+rgwadmin>=2.4.0,<2.5.0
 # Miscellaneous
-tenacity>=8.1.0,<8.2.0
-httpx>=0.23.0,<0.24.0
+tenacity>=8.2.0,<8.3.0
+httpx>=0.25.0,<0.26.0
 itsdangerous
+# Monitoring
+opentelemetry-instrumentation-fastapi
+opentelemetry-exporter-otlp-proto-grpc
diff --git a/scripts/format-imports.sh b/scripts/format-imports.sh
deleted file mode 100755
index da788e464e6e4f7eaa4d7ba9e8084cbf206dac26..0000000000000000000000000000000000000000
--- a/scripts/format-imports.sh
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/bin/sh -e
-set -x
-
-# Sort imports one per line, so autoflake can remove unused imports
-isort --force-single-line-imports app
-sh ./scripts/format.sh
diff --git a/scripts/format.sh b/scripts/format.sh
index 2670b18649ec35a4cb6f9914e36210f61d09fe11..1c5f45d44046e7f093cf64e34bdfdb713182731e 100755
--- a/scripts/format.sh
+++ b/scripts/format.sh
@@ -1,6 +1,7 @@
 #!/bin/sh -e
 set -x
 
-autoflake --remove-all-unused-imports --recursive --remove-unused-variables --in-place app --exclude=__init__.py
+isort --force-single-line-imports app
+ruff check --fix --show-fixes app
 black app
 isort app
diff --git a/scripts/lint.sh b/scripts/lint.sh
index 2ae13873d90b0b4ba889c7de7f290b28def2f579..5246a7ff93a6c8e930a3069db441f0b66b2d5504 100755
--- a/scripts/lint.sh
+++ b/scripts/lint.sh
@@ -2,7 +2,7 @@
 
 set -x
 
-mypy app
+ruff check app
 black app --check
 isort -c app
-flake8 app
+mypy app
diff --git a/scripts/prestart.sh b/scripts/prestart.sh
new file mode 100755
index 0000000000000000000000000000000000000000..d0fbfb3441bb310ef30cda4625cb99fcbc2e6ae0
--- /dev/null
+++ b/scripts/prestart.sh
@@ -0,0 +1,6 @@
+#! /usr/bin/env bash
+
+# Check Connection to Ceph RGW
+python app/check_ceph_connection.py
+# Let the DB start
+python app/check_database_connection.py
diff --git a/scripts/test.sh b/scripts/test.sh
deleted file mode 100755
index 19f94b5fc57f629f047e11b9ba21ce51bd5068a7..0000000000000000000000000000000000000000
--- a/scripts/test.sh
+++ /dev/null
@@ -1,9 +0,0 @@
-#!/usr/bin/env bash
-
-set -e
-set -x
-
-alembic downgrade base
-alembic upgrade head
-
-pytest --cov=app --cov-report=term-missing app/tests "${@}"
diff --git a/start_service.sh b/start_service.sh
index d91930b65fb6249c633d2c55668fb7963ce07e7d..9fca45763981e431c287f06b937e0385f59892a5 100755
--- a/start_service.sh
+++ b/start_service.sh
@@ -1,14 +1,6 @@
 #! /usr/bin/env bash
 
-# Check Connection to Ceph RGW
-python app/check_ceph_connection.py
-# Check Connection to OIDC provider
-python app/check_oidc_connection.py
-# Let the DB start
-python app/check_database_connection.py
-
-# Run migrations
-alembic upgrade head
+./scripts/prestart
 
 # Start webserver
-uvicorn app.main:app --host 0.0.0.0 --port 80
+uvicorn app.main:app --host 0.0.0.0 --port 8000 --no-server-header
diff --git a/tests-start.sh b/tests-start.sh
deleted file mode 100755
index eed174eb12d42c2f6d1d5352a362b3c059a2b943..0000000000000000000000000000000000000000
--- a/tests-start.sh
+++ /dev/null
@@ -1,6 +0,0 @@
-#! /usr/bin/env bash
-set -e
-
-python app/check_database_connection.py
-
-bash scripts/test.sh "$@"
diff --git a/traefik_dev/routes.toml b/traefik_dev/routes.toml
deleted file mode 100644
index eee1645616c5e125ab533fadbea1c78ebc6072d3..0000000000000000000000000000000000000000
--- a/traefik_dev/routes.toml
+++ /dev/null
@@ -1,49 +0,0 @@
-[http]
-  [http.middlewares]
-    [http.middlewares.api-stripprefix.stripPrefix]
-      prefixes = ["/api"]
-    [http.middlewares.cors-header.headers]
-      accessControlAllowMethods= ["GET", "OPTIONS", "PUT", "POST", "DELETE"]
-      accessControlAllowOriginList = ["http://localhost:9999"]
-      accessControlAllowHeaders = ["amz-sdk-invocation-id","amz-sdk-request","authorization","content-type","x-amz-content-sha256","x-amz-copy-source","x-amz-date","x-amz-user-agent", "content-md5"]
-      accessControlExposeHeaders = ["Etag"]
-      accessControlMaxAge = 100
-      addVaryHeader = true
-      isDevelopment = true
-      [http.middlewares.cors-header.headers.customResponseHeaders]
-        Content-Disposition = "attachment"
-
-  [http.routers]
-    [http.routers.api-http]
-      entryPoints = ["http"]
-      service = "proxyapi"
-      rule = "PathPrefix(`/api`)"
-      middlewares = ["api-stripprefix"]
-    [http.routers.rgw-http]
-      entryPoints = ["rgw"]
-      service = "rgw"
-      rule = "Host(`localhost`)"
-      middlewares = ["cors-header"]
-    [http.routers.api-ui]
-      entryPoints = ["http"]
-      service = "proxyapi-ui"
-      rule = "!PathPrefix(`/api`)"
-
-  [http.services]
-    [http.services.proxyapi]
-      [http.services.proxyapi.loadBalancer]
-        [[http.services.proxyapi.loadBalancer.servers]]
-          url = "http://127.0.0.1:8000"
-    [http.services.proxyapi-ui]
-      [http.services.proxyapi-ui.loadBalancer]
-        [[http.services.proxyapi-ui.loadBalancer.servers]]
-          url = "http://127.0.0.1:5173"
-          #url = "http://127.0.0.1:4173"
-    [http.services.rgw]
-      [http.services.rgw.loadBalancer]
-        [[http.services.rgw.loadBalancer.servers]]
-          url = "http://192.168.192.102:8000/"
-        [[http.services.rgw.loadBalancer.servers]]
-          url = "http://192.168.192.118:8000/"
-        [http.services.rgw.loadBalancer.healthCheck]
-          path = "/"
diff --git a/traefik_dev/traefik.toml b/traefik_dev/traefik.toml
deleted file mode 100644
index 69c9913c055a7f92f0aae19c729ca9469e37d69a..0000000000000000000000000000000000000000
--- a/traefik_dev/traefik.toml
+++ /dev/null
@@ -1,13 +0,0 @@
-[entryPoints]
-  [entryPoints.http]
-    address = ":9999"
-  [entryPoints.rgw]
-    address = ":9998"
-  [entryPoints.rgw.forwardedHeaders]
-    insecure = true
-
-[providers]
-  [providers.file]
-    filename = "routes.toml"
-
-[accessLog]