make apps abstract
This commit is contained in:
parent
b0112e05b5
commit
104a576908
26 changed files with 342 additions and 99 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -1,2 +1,4 @@
|
|||
env
|
||||
.env
|
||||
__pycache__
|
||||
.vscode/
|
||||
|
|
7
Makefile
7
Makefile
|
@ -5,6 +5,7 @@ init:
|
|||
pip3 install -r requirements.txt
|
||||
|
||||
up:
|
||||
docker context use default
|
||||
docker-compose -f compose.authentik.yml up -d
|
||||
docker-compose -f compose.wekan.yml up -d
|
||||
|
||||
|
@ -15,13 +16,15 @@ down:
|
|||
run:
|
||||
./env/bin/uvicorn app.main:app --reload --host 0.0.0.0
|
||||
|
||||
run integration:
|
||||
./env/bin/uvicorn app.main:app --reload --host 0.0.0.0
|
||||
|
||||
test:
|
||||
./env/bin/pytest app
|
||||
|
||||
integration:
|
||||
./env/bin/uvicorn app.main:app --reload --host 0.0.0.0
|
||||
|
||||
requirements:
|
||||
./env/bin/pip freeze -l > requirements.txt
|
||||
|
||||
|
||||
.PHONY: init run test
|
20
README.md
20
README.md
|
@ -16,6 +16,26 @@ make down
|
|||
```
|
||||
|
||||
|
||||
# notes
|
||||
|
||||
**Provider:** a leading system with userdata and a notification channel integrate can connect with (e.g. Authentik)
|
||||
**Apps:** Have API that integrate can interact with and e.g. can create user (e.g. Nextcloud, Wekan)
|
||||
|
||||
|
||||
## challenges
|
||||
|
||||
|
||||
|
||||
* How to handle errors in connected Apps
|
||||
|
||||
* Apps can have different unique constrains than the Provider
|
||||
e.g. Wekan requires a unique Mail addr and authentik doesn't
|
||||
the specifig module for the apps api needs to know how to handle these errors
|
||||
|
||||
* User in App could already exist
|
||||
*
|
||||
|
||||
|
||||
|
||||
https://pydantic-docs.helpmanual.io/
|
||||
https://jsontopydantic.com/
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
from .wekan import main
|
||||
from .nextcloud import main
|
|
@ -3,16 +3,19 @@ from typing import Dict, Optional
|
|||
import requests
|
||||
from requests import Request
|
||||
import structlog
|
||||
|
||||
from app.authentik.settings import AuthentikSettings
|
||||
from .models import User
|
||||
|
||||
logging = structlog.get_logger()
|
||||
|
||||
|
||||
class Authentik:
|
||||
|
||||
def __init__(self, token, base):
|
||||
self.base = f"{base}api/v3/"
|
||||
self.token = token
|
||||
self.headers = {"Authorization": f"Bearer {token}"}
|
||||
def __init__(self, settings: AuthentikSettings):
|
||||
self.base = f"{settings.baseurl}api/v3/"
|
||||
self.token = settings.token
|
||||
self.headers = {"Authorization": f"Bearer {self.token}"}
|
||||
self.hook_endpoint = None
|
||||
self.property_mapping = None
|
||||
self.event_matcher_policy = None
|
||||
|
@ -22,10 +25,10 @@ class Authentik:
|
|||
self.event_rule_link = None
|
||||
|
||||
# check connection
|
||||
r= requests.get(url=f"{self.base}/admin/version", headers=self.headers)
|
||||
r = requests.get(
|
||||
url=f"{self.base}/admin/version", headers=self.headers)
|
||||
assert r.status_code == 200
|
||||
|
||||
|
||||
def get(self, endpoint: str, params: Dict = {}) -> Request:
|
||||
return requests.get(url=f"{self.base}{endpoint}", params=params, headers=self.headers)
|
||||
|
||||
|
@ -57,8 +60,11 @@ class Authentik:
|
|||
"webhook_mapping": property_mapping_pk,
|
||||
"webhook_url": hook_endpoint
|
||||
}
|
||||
print(data)
|
||||
# TODO: add check if model with same name already exists
|
||||
|
||||
for result in self.get(url).json()["results"]:
|
||||
if result["name"] == "my hook":
|
||||
raise Exception("Event Rule already exists")
|
||||
|
||||
r: Request = self.post(url, data)
|
||||
if r.status_code == 201:
|
||||
return r.json()
|
||||
|
@ -74,7 +80,11 @@ class Authentik:
|
|||
event_transport
|
||||
]
|
||||
}
|
||||
# TODO: add check if model with same name already exists
|
||||
|
||||
for result in self.get(url).json()["results"]:
|
||||
if result["name"] == "event-rule":
|
||||
raise Exception("Event Rule already exists")
|
||||
|
||||
r = self.post(url, data)
|
||||
if r.status_code == 201:
|
||||
return r.json()
|
||||
|
@ -143,8 +153,6 @@ class Authentik:
|
|||
return User(**r)
|
||||
raise Exception(r)
|
||||
|
||||
|
||||
|
||||
def get_user(self, user: User) -> Optional[User]:
|
||||
if user.pk:
|
||||
r = self.get(f"core/users/{user.pk}").json()
|
||||
|
|
|
@ -1,15 +1,5 @@
|
|||
from pydantic import BaseSettings, Field
|
||||
|
||||
|
||||
class WekanSettings(BaseSettings):
|
||||
baseurl: str = ""
|
||||
user: str = ""
|
||||
password: str = ""
|
||||
|
||||
class Config:
|
||||
env_file = '.env'
|
||||
env_prefix = 'WEKAN_'
|
||||
|
||||
class AuthentikSettings(BaseSettings):
|
||||
baseurl: str = ""
|
||||
token: str = ""
|
|
@ -13,6 +13,24 @@ def api() -> Authentik:
|
|||
pytest.skip("API not reachable? Did you start docker-compose?")
|
||||
|
||||
|
||||
|
||||
def test_create_event_transport_already_exists(api: Authentik):
|
||||
exception = None
|
||||
try:
|
||||
api.create_event_transport("/","asd")
|
||||
except Exception as e:
|
||||
exception = e
|
||||
assert not exception == None # TODO create exception types
|
||||
|
||||
|
||||
def test_create_event_rule_already_exists(api: Authentik):
|
||||
exception = None
|
||||
try:
|
||||
api.create_event_rule({"pk" : "asd"},"webhook")
|
||||
except Exception as e:
|
||||
exception = e
|
||||
assert not exception == None # TODO create exception types
|
||||
|
||||
def test_get_user_by_username(api: Authentik):
|
||||
u = User(username="akadmin",
|
||||
name="",
|
||||
|
@ -42,3 +60,5 @@ def test_create_user(api: Authentik):
|
|||
c = api.create_user(u)
|
||||
assert u.username == c.username
|
||||
assert not c.pk == None
|
||||
|
||||
assert api.delete_user(c) == True
|
0
app/dependencies.py
Normal file
0
app/dependencies.py
Normal file
|
@ -1,10 +1,15 @@
|
|||
import structlog
|
||||
from typing import List
|
||||
from pydantic import BaseModel
|
||||
from fastapi import FastAPI
|
||||
from fastapi import FastAPI, Depends
|
||||
from app.authentik.api import Authentik
|
||||
from app.authentik.models import User
|
||||
from app.wekan.api import Wekan
|
||||
from app.settings import AuthentikSettings, WekanSettings
|
||||
from app.sink import Sink
|
||||
from app.wekan.api import WekanApi
|
||||
from app.authentik.settings import AuthentikSettings
|
||||
from app.wekan.main import WekanSink
|
||||
|
||||
logging = structlog.get_logger()
|
||||
|
||||
class Authentik_Hook_Model(BaseModel):
|
||||
pk: str
|
||||
|
@ -20,29 +25,30 @@ class Http_request(BaseModel):
|
|||
|
||||
|
||||
authentikSettings = AuthentikSettings()
|
||||
wekanSettings = WekanSettings()
|
||||
|
||||
|
||||
class EventController:
|
||||
|
||||
def __init__(self):
|
||||
try:
|
||||
self.authentik = Authentik(
|
||||
authentikSettings.token, authentikSettings.baseurl)
|
||||
self.wekan = Wekan(wekanSettings.baseurl,
|
||||
wekanSettings.user, wekanSettings.password)
|
||||
except Exception as e:
|
||||
raise Exception("Failed to init Api", e)
|
||||
def __init__(self, authentik_api: Authentik):
|
||||
# try:
|
||||
self._authentik = authentik_api
|
||||
self._sinks = []
|
||||
for sc in Sink.__subclasses__():
|
||||
obj = sc()
|
||||
self._sinks.append(obj)
|
||||
|
||||
|
||||
# except Exception as e:
|
||||
# raise Exception("Failed to init Api", e)
|
||||
self.jobs = []
|
||||
pass
|
||||
|
||||
def register_api(self, authentik: Authentik, wekan: Wekan):
|
||||
self.authentik = authentik
|
||||
self.wekan = wekan
|
||||
def register_api(self, authentik: Authentik, sinks: List[Sink]):
|
||||
self._authentik = authentik
|
||||
self._sinks = sinks
|
||||
|
||||
def handle_model_created_event(self, model: Authentik_Hook_Model):
|
||||
user: User = self.authentik.get_user_by_pk(model.pk)
|
||||
if not self.wekan.get_user(user.name):
|
||||
self.wekan.create_user(
|
||||
username=user.username, email=user.email, password="")
|
||||
user: User = self._authentik.get_user_by_pk(model.pk)
|
||||
for sink in self._sinks:
|
||||
logging.info(f"Creating User {user.username} in {sink.__class__}")
|
||||
sink.create_user(user)
|
||||
return True
|
||||
|
|
19
app/main.py
19
app/main.py
|
@ -1,34 +1,29 @@
|
|||
import logging
|
||||
import structlog
|
||||
from unicodedata import name
|
||||
from urllib.error import HTTPError
|
||||
from fastapi import Depends, FastAPI, Request, BackgroundTasks
|
||||
from pydantic import BaseModel
|
||||
from app.sink import BaseUser, Sink
|
||||
from app.authentik.api import Authentik
|
||||
from app.authentik.models import User
|
||||
from app.event_controller import Authentik_Hook_Model, EventController, Http_request
|
||||
from app.settings import AuthentikSettings
|
||||
from .wekan.api import Wekan
|
||||
from app.authentik.settings import AuthentikSettings
|
||||
from .wekan.api import WekanApi
|
||||
from .routers import identityProvider
|
||||
import json
|
||||
|
||||
logging = structlog.get_logger()
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
app.include_router(identityProvider.router)
|
||||
|
||||
@app.get("/")
|
||||
async def root():
|
||||
return {'message': 'Hello World'}
|
||||
|
||||
@app.post("/authentik/hook/")
|
||||
async def hook(model: Authentik_Hook_Model,
|
||||
http_request: Http_request,
|
||||
ec: EventController = Depends()):
|
||||
logging.info(model)
|
||||
logging.info(http_request)
|
||||
if http_request.path == "/api/v3/core/users/":
|
||||
ec.handle_model_created_event(model)
|
||||
return 200
|
||||
|
||||
### for testing purposes
|
||||
@app.get("/authentik/create_hook/")
|
||||
async def hook(request: Request):
|
||||
a = Authentik(base="http://localhost:9000/", token="foobar123")
|
||||
|
|
0
app/nextcloud/__init__.py
Normal file
0
app/nextcloud/__init__.py
Normal file
26
app/nextcloud/main.py
Normal file
26
app/nextcloud/main.py
Normal file
|
@ -0,0 +1,26 @@
|
|||
from unittest.mock import MagicMock
|
||||
from app.sink import BaseGroup, BaseUser, Sink
|
||||
|
||||
class Api:
|
||||
def create_user(self):
|
||||
pass
|
||||
|
||||
class NextcloudSink(Sink):
|
||||
|
||||
def __init__(self):
|
||||
self._api: Api = Api()
|
||||
|
||||
@property
|
||||
def api(self):
|
||||
return self._api
|
||||
|
||||
@api.setter
|
||||
def api(self, api):
|
||||
self._api = api
|
||||
|
||||
def create_user(self, user: BaseUser): # Wekan):
|
||||
return self.api.create_user()
|
||||
|
||||
def create_group(self, group: BaseGroup):
|
||||
print("Create Nextcloud Group: ", group)
|
||||
pass
|
0
app/routers/__init__.py
Normal file
0
app/routers/__init__.py
Normal file
20
app/routers/identityProvider.py
Normal file
20
app/routers/identityProvider.py
Normal file
|
@ -0,0 +1,20 @@
|
|||
import logging
|
||||
from app import dependencies
|
||||
from app.authentik.api import Authentik
|
||||
from app.event_controller import Authentik_Hook_Model, EventController, Http_request
|
||||
from fastapi import APIRouter, Depends
|
||||
|
||||
from app.authentik.settings import AuthentikSettings
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@router.post("/authentik/hook/")
|
||||
async def hook(model: Authentik_Hook_Model,
|
||||
http_request: Http_request,
|
||||
):
|
||||
logging.info(model)
|
||||
logging.info(http_request)
|
||||
ec = EventController(Authentik(AuthentikSettings()))
|
||||
if http_request.path == "/api/v3/core/users/":
|
||||
ec.handle_model_created_event(model)
|
||||
return 200
|
27
app/sink.py
Normal file
27
app/sink.py
Normal file
|
@ -0,0 +1,27 @@
|
|||
from abc import ABC, abstractclassmethod, abstractproperty
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class BaseUser(BaseModel):
|
||||
email: str
|
||||
id: str
|
||||
name: str
|
||||
username: str
|
||||
|
||||
class BaseGroup(BaseModel):
|
||||
name: str
|
||||
|
||||
|
||||
class Sink(ABC):
|
||||
|
||||
@abstractproperty
|
||||
def api(self):
|
||||
pass
|
||||
|
||||
@abstractclassmethod
|
||||
def create_user(user: BaseUser) -> BaseUser:
|
||||
pass
|
||||
|
||||
@abstractclassmethod
|
||||
def create_group(group: BaseGroup) -> BaseGroup:
|
||||
pass
|
|
@ -11,12 +11,11 @@ def test_handle_model_created_event(mocker: MockerFixture):
|
|||
authentik_mock = mocker.MagicMock()
|
||||
authentik_mock.get_user_by_pk.return_value = mock_user
|
||||
model = Authentik_Hook_Model(pk=mock_user.pk, app="authentik_core", name=mock_user.name, model_name="user")
|
||||
ec = EventController()
|
||||
ec.register_api(authentik_mock, wekan_mock)
|
||||
ec = EventController(authentik_mock)
|
||||
ec.register_api(authentik_mock, [wekan_mock])
|
||||
ec.handle_model_created_event(model)
|
||||
ec.authentik.get_user_by_pk.assert_called()
|
||||
ec.authentik.get_user_by_pk.assert_called_with("5")
|
||||
ec.wekan.get_user.assert_called()
|
||||
ec.wekan.get_user.assert_called_with("asd")
|
||||
ec.wekan.create_user.assert_called()
|
||||
ec.wekan.create_user.assert_called_with(username=mock_user.username, email=mock_user.email, password="")
|
||||
ec._authentik.get_user_by_pk.assert_called()
|
||||
ec._authentik.get_user_by_pk.assert_called_with("5")
|
||||
|
||||
wekan_mock.create_user.assert_called()
|
||||
wekan_mock.create_user.assert_called_with(mock_user)
|
||||
|
|
58
app/test_integration.py
Normal file
58
app/test_integration.py
Normal file
|
@ -0,0 +1,58 @@
|
|||
from cmath import log
|
||||
from email.mime import base
|
||||
import logging
|
||||
from re import A
|
||||
from time import sleep
|
||||
import pytest
|
||||
|
||||
import requests
|
||||
from app import event_controller
|
||||
|
||||
from app.authentik.api import Authentik
|
||||
from app.authentik.models import User
|
||||
from app.authentik.settings import AuthentikSettings
|
||||
from app.wekan.models import User as WekanUser
|
||||
from app.wekan.api import WekanApi
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def wekan_api():
|
||||
w = None
|
||||
try:
|
||||
r = requests.post("http://localhost:3000/users/register", json={"username": "api", "password": "foobar123", "email": "foo@example.org"})
|
||||
w = WekanApi("http://localhost:3000","api", "foobar123")
|
||||
except Exception as e:
|
||||
logging.error(e)
|
||||
return w
|
||||
|
||||
@pytest.fixture()
|
||||
def authentik_api():
|
||||
|
||||
settings = AuthentikSettings(baseurl="http://localhost:9000/", token="foobar123")
|
||||
a = Authentik(settings)
|
||||
try:
|
||||
r = a.create_web_hook(hook_endpoint="http://172.17.0.1:8000/authentik/hook/") # docker localhost
|
||||
except Exception as e:
|
||||
logging.info(e)
|
||||
return a
|
||||
|
||||
|
||||
|
||||
def test_create_user(wekan_api: WekanApi, authentik_api: Authentik):
|
||||
user = authentik_api.create_user(User(username="banane43", email="banane@example.org", name="Herr Banane"))
|
||||
print(user)
|
||||
sleep(5)
|
||||
authentik_api.delete_user(user)
|
||||
# authentik username == wekan username
|
||||
# user must be created from authentik user and noch api to trigger the notiftication rule?
|
||||
logging.error("authentik notifcation rule doesn't work with api?? , plz create user in authentik")
|
||||
assert False
|
||||
|
||||
@pytest.mark.skip()
|
||||
def test_create_user_with_same_email(wekan_api, authentik_api):
|
||||
logging.error("authentik notifcation rule doesn't work with api?? , create two user with identical email in authentik")
|
||||
assert False
|
||||
|
||||
@pytest.mark.skip()
|
||||
def test_user_already_exists_excepts():
|
||||
assert False
|
|
@ -1,7 +1,7 @@
|
|||
from fastapi.testclient import TestClient
|
||||
import pytest
|
||||
|
||||
from app.wekan.api import Wekan
|
||||
from app.wekan.api import WekanApi
|
||||
|
||||
from .main import Authentik_Hook_Model, app
|
||||
|
||||
|
@ -25,7 +25,7 @@ def test_hook_fails_for_wrong_input():
|
|||
assert response.status_code == 422
|
||||
|
||||
def test_hook_model_created(mocker):
|
||||
mock = mocker.patch("app.event_controller.Event_Controller.handle_model_created_event")
|
||||
mock = mocker.patch("app.event_controller.EventController.handle_model_created_event")
|
||||
d = """
|
||||
{"model": {"pk": 5, "app": "authentik_core", "name": "asd", "model_name": "user"}, "http_request": {"args": {}, "path": "/api/v3/core/users/", "method": "POST"}}
|
||||
"""
|
||||
|
|
28
app/test_sink.py
Normal file
28
app/test_sink.py
Normal file
|
@ -0,0 +1,28 @@
|
|||
from .nextcloud.main import BaseUser, Sink
|
||||
from .wekan import main
|
||||
from .nextcloud import main
|
||||
from pytest_mock import MockerFixture
|
||||
import pytest
|
||||
|
||||
# TODO how to import all sink from smth like a "addons" folder?
|
||||
|
||||
@pytest.fixture
|
||||
def testUser():
|
||||
return BaseUser(email="foo", id="asd", name="sd", username="sdfsfd")
|
||||
|
||||
def test_get_subclasses():
|
||||
l = []
|
||||
for sc in Sink.__subclasses__():
|
||||
l.append(sc.__name__)
|
||||
assert "NextcloudSink" in l
|
||||
assert "WekanSink" in l
|
||||
|
||||
@pytest.mark.skip()
|
||||
def test_create_user(mocker: MockerFixture, testUser: BaseUser):
|
||||
u = []
|
||||
for sc in Sink.__subclasses__():
|
||||
print(sc.__name__)
|
||||
# app = sc()
|
||||
# app.api = mocker.MagicMock()
|
||||
# app.create_user(testUser)
|
||||
# app.api.create_user.assert_called()
|
|
@ -1,3 +1,4 @@
|
|||
import logging
|
||||
from optparse import Option
|
||||
import string
|
||||
import requests
|
||||
|
@ -5,19 +6,20 @@ from requests import Request
|
|||
from pydantic import BaseModel, Field
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from app.wekan.models import User, UserBase
|
||||
from .models import User, UserBase
|
||||
from .settings import WekanSettings
|
||||
|
||||
|
||||
class Wekan:
|
||||
class WekanApi:
|
||||
|
||||
def __init__(self, base: str, api_user: str, password: str):
|
||||
self.base = base
|
||||
r = requests.post(f'{base}/users/login', data={
|
||||
"username": api_user,
|
||||
"password": password
|
||||
def __init__(self, settings: WekanSettings):
|
||||
self.base = settings.baseurl
|
||||
r = requests.post(f'{self.base}/users/login', data={
|
||||
"username": settings.user,
|
||||
"password": settings.password
|
||||
}, )
|
||||
if not r.status_code == 200:
|
||||
raise Exception(r.url, r.text)
|
||||
raise Exception("[WekanAPI] Failed to init")
|
||||
|
||||
t = r.json()
|
||||
self.token = {
|
||||
|
@ -60,7 +62,7 @@ class Wekan:
|
|||
data = {
|
||||
"username": username,
|
||||
"email": email,
|
||||
"password": ""
|
||||
"password": password
|
||||
}
|
||||
r = self.post("users/", data).json()
|
||||
if "error" in r:
|
||||
|
|
30
app/wekan/main.py
Normal file
30
app/wekan/main.py
Normal file
|
@ -0,0 +1,30 @@
|
|||
|
||||
from unittest.mock import MagicMock
|
||||
from .settings import WekanSettings
|
||||
from app.sink import BaseGroup, BaseUser, Sink
|
||||
from .api import WekanApi
|
||||
from .models import User
|
||||
|
||||
|
||||
class WekanSink(Sink):
|
||||
|
||||
def __init__(self):
|
||||
self._settings = WekanSettings()
|
||||
self._api = WekanApi(self._settings)
|
||||
|
||||
@property
|
||||
def api(self):
|
||||
return self._api
|
||||
|
||||
@api.setter
|
||||
def api(self, api):
|
||||
self._api = api
|
||||
|
||||
def create_user(self, user: BaseUser):
|
||||
# TODO if not self.wekan.get_user(user.name):
|
||||
return self._api.create_user(username=user.username, email=user.email, password="")
|
||||
|
||||
def create_group(self, group: BaseGroup):
|
||||
print("Create Wekan Group: ", group)
|
||||
pass
|
||||
|
10
app/wekan/settings.py
Normal file
10
app/wekan/settings.py
Normal file
|
@ -0,0 +1,10 @@
|
|||
from pydantic import BaseSettings
|
||||
|
||||
class WekanSettings(BaseSettings):
|
||||
baseurl: str = ""
|
||||
user: str = ""
|
||||
password: str = ""
|
||||
|
||||
class Config:
|
||||
env_file = '.env'
|
||||
env_prefix = 'WEKAN_'
|
0
app/wekan/sources.py
Normal file
0
app/wekan/sources.py
Normal file
|
@ -1,18 +1,18 @@
|
|||
import requests
|
||||
from app.wekan.models import User, UserBase
|
||||
from .api import Wekan
|
||||
from .api import WekanApi
|
||||
import pytest
|
||||
|
||||
@pytest.fixture
|
||||
def api() -> Wekan:
|
||||
def api() -> WekanApi:
|
||||
try:
|
||||
r = requests.post("http://localhost:3000/users/register", json={"username": "api", "password": "foobar123", "email": "foo@example.org"})
|
||||
return Wekan("http://localhost:3000", "api", "foobar123")
|
||||
return WekanApi("http://localhost:3000", "api", "foobar123")
|
||||
except:
|
||||
pytest.skip("API not reachable? Did you start docker-compose?")
|
||||
|
||||
|
||||
def test_get_user(api: Wekan):
|
||||
def test_get_user(api: WekanApi):
|
||||
user = api.get_user("api")
|
||||
assert user.username == "api"
|
||||
assert type(user) == User
|
||||
|
@ -20,15 +20,15 @@ def test_get_user(api: Wekan):
|
|||
user = api.get_user("doesnotexist")
|
||||
assert user == None
|
||||
|
||||
def test_get_users(api: Wekan):
|
||||
def test_get_users(api: WekanApi):
|
||||
assert True if "api" in [u.username for u in api.get_all_users()] else False
|
||||
|
||||
def test_create_user(api: Wekan):
|
||||
def test_create_user(api: WekanApi):
|
||||
user = api.create_user("foo", "foo@bar.com", "")
|
||||
assert api.get_user("foo").username == "foo"
|
||||
assert type(user) is User
|
||||
|
||||
def test_delete_user(api: Wekan):
|
||||
def test_delete_user(api: WekanApi):
|
||||
api.create_user("foo", "foo@bar.com", "")
|
||||
api.delete_user("foo") # TODO: doesn't work?
|
||||
pytest.skip("smth wrong with wekan api")
|
||||
|
|
|
@ -15,7 +15,7 @@ services:
|
|||
image: redis:alpine
|
||||
restart: unless-stopped
|
||||
server:
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2022.2.1}
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2022.4.1}
|
||||
restart: unless-stopped
|
||||
command: server
|
||||
environment:
|
||||
|
@ -38,7 +38,7 @@ services:
|
|||
- "0.0.0.0:${AUTHENTIK_PORT_HTTP:-9000}:9000"
|
||||
- "0.0.0.0:${AUTHENTIK_PORT_HTTPS:-9443}:9443"
|
||||
worker:
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2022.2.1}
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2022.4.1}
|
||||
restart: unless-stopped
|
||||
command: worker
|
||||
environment:
|
||||
|
|
|
@ -1,29 +1,26 @@
|
|||
anyio==3.5.0
|
||||
asgiref==3.5.0
|
||||
attrs==21.4.0
|
||||
autopep8==1.6.0
|
||||
certifi==2021.10.8
|
||||
charset-normalizer==2.0.12
|
||||
click==8.0.4
|
||||
fastapi==0.74.1
|
||||
click==8.1.2
|
||||
fastapi==0.75.2
|
||||
h11==0.13.0
|
||||
idna==3.3
|
||||
iniconfig==1.1.1
|
||||
packaging==21.3
|
||||
pluggy==1.0.0
|
||||
py==1.11.0
|
||||
pycodestyle==2.8.0
|
||||
pydantic==1.9.0
|
||||
pyparsing==3.0.7
|
||||
pytest==7.0.1
|
||||
pyparsing==3.0.8
|
||||
pytest==7.1.2
|
||||
pytest-mock==3.7.0
|
||||
python-dotenv==0.19.2
|
||||
python-dotenv==0.20.0
|
||||
requests==2.27.1
|
||||
sniffio==1.2.0
|
||||
starlette==0.17.1
|
||||
structlog==21.5.0
|
||||
toml==0.10.2
|
||||
tomli==2.0.1
|
||||
typing_extensions==4.1.1
|
||||
urllib3==1.26.8
|
||||
uvicorn==0.17.5
|
||||
typing_extensions==4.2.0
|
||||
urllib3==1.26.9
|
||||
uvicorn==0.17.6
|
||||
|
|
Loading…
Reference in a new issue