Add session store and login endpoint and auth middleware
This commit is contained in:
parent
714f3221f9
commit
ef08fe5997
|
@ -3,6 +3,8 @@ core:
|
||||||
listen:
|
listen:
|
||||||
host: 127.0.0.1
|
host: 127.0.0.1
|
||||||
port: 8080
|
port: 8080
|
||||||
|
# generated with: openssl rand -hex 16
|
||||||
|
cookiestore_secret: "e41133b5cfdd8660815b8d5cc2c74843"
|
||||||
|
|
||||||
|
|
||||||
ldap:
|
ldap:
|
||||||
|
@ -14,11 +16,12 @@ ldap:
|
||||||
validate: False # Can either be True or False. Default: False
|
validate: False # Can either be True or False. Default: False
|
||||||
ca_certs: openldap/cert.pem
|
ca_certs: openldap/cert.pem
|
||||||
|
|
||||||
username: uid=phi,ou=Services,dc=unit,dc=macaomilano,dc=org
|
# username: uid=phi,ou=Services,dc=unit,dc=macaomilano,dc=org
|
||||||
password: phi
|
# password: phi
|
||||||
|
# username: cn=root,dc=unit,dc=macaomilano,dc=org
|
||||||
|
# password: root
|
||||||
|
|
||||||
base_dn: dc=unit,dc=macaomilano,dc=org
|
base_dn: dc=unit,dc=macaomilano,dc=org
|
||||||
attribute_id: uid
|
|
||||||
|
|
||||||
|
|
||||||
logging:
|
logging:
|
||||||
|
|
|
@ -2,24 +2,19 @@
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
|
|
||||||
from phi.logging import get_logger
|
from phi.logging import get_logger
|
||||||
from phi.async_ldap.client import AsyncClient
|
|
||||||
from phi.async_ldap.model import Hackers, Robots, Congregations
|
|
||||||
from phi.api.routes import api_routes
|
from phi.api.routes import api_routes
|
||||||
|
from phi.auth_middleware import authenticated
|
||||||
|
|
||||||
|
|
||||||
log = get_logger(__name__)
|
log = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def api_app(config):
|
def api_app(store):
|
||||||
log.info("Initializing API sub-app.")
|
log.info("Initializing API sub-app.")
|
||||||
|
|
||||||
app = web.Application()
|
app = web.Application(middlewares=[authenticated])
|
||||||
|
|
||||||
ldap_client = AsyncClient(**config.get("ldap", {}))
|
app["store"] = store
|
||||||
app["ldap_client"] = ldap_client
|
|
||||||
app["users"] = Hackers(ldap_client)
|
|
||||||
app["services"] = Robots(ldap_client)
|
|
||||||
app["groups"] = Congregations(ldap_client)
|
|
||||||
app["log"] = log
|
app["log"] = log
|
||||||
|
|
||||||
app.router.add_routes(api_routes)
|
app.router.add_routes(api_routes)
|
||||||
|
|
|
@ -9,8 +9,8 @@ from aiohttp.web import (
|
||||||
)
|
)
|
||||||
|
|
||||||
from phi.logging import get_logger
|
from phi.logging import get_logger
|
||||||
from phi.api.utils import serialize
|
# from phi.api.utils import serialize
|
||||||
from phi.async_ldap.model import Hackers, User
|
from phi.async_ldap.model import User
|
||||||
from phi.exceptions import (
|
from phi.exceptions import (
|
||||||
PhiEntryDoesNotExist,
|
PhiEntryDoesNotExist,
|
||||||
PhiUnexpectedRuntimeValue,
|
PhiUnexpectedRuntimeValue,
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
from aiohttp.web import route
|
from aiohttp.web import view
|
||||||
|
|
||||||
from phi.api.rest import UserView
|
from phi.api.rest import UserView
|
||||||
|
|
||||||
api_routes = [
|
api_routes = [
|
||||||
route("*", "/user", UserView),
|
view("/user", UserView),
|
||||||
route("*", "/user/", UserView),
|
view("/user/", UserView),
|
||||||
route("*", "/user/{uid}", UserView),
|
view("/user/{uid}", UserView),
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,23 +1,34 @@
|
||||||
# -*- encoding: utf-8 -*-
|
# -*- encoding: utf-8 -*-
|
||||||
import pkg_resources
|
|
||||||
|
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
|
from aiohttp_session import setup
|
||||||
|
from aiohttp_session.cookie_storage import EncryptedCookieStorage
|
||||||
import click
|
import click
|
||||||
from pprint import pformat as pp
|
from pprint import pformat as pp
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
from phi.config import get_config, merge_config
|
from phi.config import get_config, merge_config, extract_secret
|
||||||
from phi.logging import setup_logging, get_logger
|
from phi.logging import setup_logging, get_logger
|
||||||
from phi.api.app import api_app
|
from phi.api.app import api_app
|
||||||
|
from phi.client_store import ClientStore
|
||||||
|
from phi.login import login
|
||||||
|
|
||||||
log = get_logger(__name__)
|
log = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
LOGIN_ROUTE = "/login"
|
||||||
|
|
||||||
|
|
||||||
def setup_app(config):
|
def setup_app(config):
|
||||||
app = web.Application()
|
app = web.Application()
|
||||||
app["config"] = config
|
setup(app, EncryptedCookieStorage(extract_secret(config)))
|
||||||
|
|
||||||
api = api_app(config)
|
app["config"] = config
|
||||||
|
app["store"] = ClientStore(LOGIN_ROUTE)
|
||||||
|
app["log"] = log
|
||||||
|
|
||||||
|
app.add_routes([web.post(LOGIN_ROUTE, login)])
|
||||||
|
|
||||||
|
api = api_app(app["store"])
|
||||||
app.add_subapp("/api", api)
|
app.add_subapp("/api", api)
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
@ -109,7 +120,11 @@ def run_app(app):
|
||||||
help="Path to a yaml configuration for the logger.",
|
help="Path to a yaml configuration for the logger.",
|
||||||
)
|
)
|
||||||
@click.option(
|
@click.option(
|
||||||
"--debug", "debug", is_flag=True, default=False, help="Set the log level to debug.",
|
"--debug",
|
||||||
|
"debug",
|
||||||
|
is_flag=True,
|
||||||
|
default=False,
|
||||||
|
help="Set the log level to debug.",
|
||||||
)
|
)
|
||||||
def cli(
|
def cli(
|
||||||
host,
|
host,
|
||||||
|
@ -141,6 +156,7 @@ def cli(
|
||||||
debug,
|
debug,
|
||||||
)
|
)
|
||||||
config_file, file_config = get_config(config_path)
|
config_file, file_config = get_config(config_path)
|
||||||
|
log.debug(f"FILE: {pp(file_config)}")
|
||||||
config = merge_config(cli_config, file_config)
|
config = merge_config(cli_config, file_config)
|
||||||
if debug:
|
if debug:
|
||||||
set_to_debug(config)
|
set_to_debug(config)
|
||||||
|
|
|
@ -86,7 +86,7 @@ class AsyncClient(LDAPClient):
|
||||||
host=None,
|
host=None,
|
||||||
port=None,
|
port=None,
|
||||||
encryption=None,
|
encryption=None,
|
||||||
cyphers=None,
|
ciphers=None,
|
||||||
validate=False,
|
validate=False,
|
||||||
ca_cert=None,
|
ca_cert=None,
|
||||||
username=None,
|
username=None,
|
||||||
|
@ -130,3 +130,6 @@ class AsyncClient(LDAPClient):
|
||||||
|
|
||||||
self.set_auto_page_acquire(True)
|
self.set_auto_page_acquire(True)
|
||||||
self.set_credentials(self.method, user=self.username, password=self.password)
|
self.set_credentials(self.method, user=self.username, password=self.password)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"AsyncClient[{self.full_uri}]<{self.username}>"
|
||||||
|
|
23
src/phi/auth_middleware.py
Normal file
23
src/phi/auth_middleware.py
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
from aiohttp.web import middleware, HTTPFound
|
||||||
|
|
||||||
|
from phi import app
|
||||||
|
from phi.async_ldap.model import Hackers, Robots, Congregations
|
||||||
|
|
||||||
|
|
||||||
|
@middleware
|
||||||
|
async def authenticated(request, handler):
|
||||||
|
try:
|
||||||
|
store = request.app["store"]
|
||||||
|
except KeyError:
|
||||||
|
raise HTTPFound(app.LOGIN_ROUTE)
|
||||||
|
|
||||||
|
client = await store.get_client(request)
|
||||||
|
|
||||||
|
request.app["ldap_client"] = client
|
||||||
|
request.app["users"] = Hackers(client)
|
||||||
|
request.app["services"] = Robots(client)
|
||||||
|
request.app["groups"] = Congregations(client)
|
||||||
|
resp = await handler(request)
|
||||||
|
|
||||||
|
return resp
|
35
src/phi/client_store.py
Normal file
35
src/phi/client_store.py
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
import secrets
|
||||||
|
|
||||||
|
from aiohttp.web import HTTPFound
|
||||||
|
from aiohttp_session import get_session
|
||||||
|
|
||||||
|
|
||||||
|
class ClientStore(dict):
|
||||||
|
"""
|
||||||
|
This class is responsible to hold the clients used by the active connections.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, login_route):
|
||||||
|
self.login_route = login_route
|
||||||
|
self.store = dict()
|
||||||
|
|
||||||
|
async def get_client(self, request):
|
||||||
|
session = await get_session(request)
|
||||||
|
client_id = session.get("client_id")
|
||||||
|
if client_id is None:
|
||||||
|
raise HTTPFound(self.login_route)
|
||||||
|
|
||||||
|
client = self.store.get(client_id)
|
||||||
|
if client is None:
|
||||||
|
raise HTTPFound(self.login_route)
|
||||||
|
|
||||||
|
return client
|
||||||
|
|
||||||
|
async def set_client(self, request, client):
|
||||||
|
session = await get_session(request)
|
||||||
|
|
||||||
|
client_id = secrets.token_hex(16)
|
||||||
|
|
||||||
|
self.store[client_id] = client
|
||||||
|
session["client_id"] = client_id
|
|
@ -5,7 +5,7 @@ import yaml
|
||||||
NAME = "phi"
|
NAME = "phi"
|
||||||
|
|
||||||
DEFAULT_CONFIG = {
|
DEFAULT_CONFIG = {
|
||||||
"core": {"listen": {"host": "localhost", "port": 8080}},
|
"core": {"listen": {"host": "localhost", "port": 8080}, "cookiestore_secret": None},
|
||||||
"ldap": {
|
"ldap": {
|
||||||
"host": "localhost",
|
"host": "localhost",
|
||||||
"port": 389,
|
"port": 389,
|
||||||
|
@ -13,10 +13,7 @@ DEFAULT_CONFIG = {
|
||||||
"ciphers": "HIGH",
|
"ciphers": "HIGH",
|
||||||
"validate": True,
|
"validate": True,
|
||||||
"ca_certs": pkg_resources.resource_filename(NAME, "openldap/cert.pem"),
|
"ca_certs": pkg_resources.resource_filename(NAME, "openldap/cert.pem"),
|
||||||
"username": None,
|
|
||||||
"password": None,
|
|
||||||
"base_dn": None,
|
"base_dn": None,
|
||||||
"attribute_id": "uid",
|
|
||||||
"attribute_mail": "mail",
|
"attribute_mail": "mail",
|
||||||
},
|
},
|
||||||
"logging": {
|
"logging": {
|
||||||
|
@ -35,9 +32,18 @@ DEFAULT_CONFIG = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"loggers": {
|
"loggers": {
|
||||||
"phi": {"level": "INFO", "handlers": ["console", "file"],},
|
"phi": {
|
||||||
"aiohttp": {"level": "INFO", "handlers": ["console", "file"],},
|
"level": "INFO",
|
||||||
"bonsai": {"level": "WARNING", "handlers": ["console", "file"],},
|
"handlers": ["console", "file"],
|
||||||
|
},
|
||||||
|
"aiohttp": {
|
||||||
|
"level": "INFO",
|
||||||
|
"handlers": ["console", "file"],
|
||||||
|
},
|
||||||
|
"bonsai": {
|
||||||
|
"level": "WARNING",
|
||||||
|
"handlers": ["console", "file"],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -123,3 +129,16 @@ def recursive_merge(main_config, aux_config):
|
||||||
_recursive_merge(main_config, aux_config, DEFAULT_CONFIG, "ROOT", _config)
|
_recursive_merge(main_config, aux_config, DEFAULT_CONFIG, "ROOT", _config)
|
||||||
|
|
||||||
return _config["ROOT"]
|
return _config["ROOT"]
|
||||||
|
|
||||||
|
|
||||||
|
def extract_secret(config):
|
||||||
|
try:
|
||||||
|
secret = config["core"]["cookiestore_secret"].encode("utf-8")
|
||||||
|
if len(secret) != 32:
|
||||||
|
raise ValueError(
|
||||||
|
"The provided core.cookiestore_secret must be 32 bytes long"
|
||||||
|
)
|
||||||
|
|
||||||
|
return secret
|
||||||
|
except KeyError:
|
||||||
|
raise RuntimeError("You must provide a core.cookiestore_secret")
|
||||||
|
|
38
src/phi/login.py
Normal file
38
src/phi/login.py
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
from aiohttp.web import HTTPBadRequest, HTTPOk
|
||||||
|
|
||||||
|
from phi.async_ldap.client import AsyncClient
|
||||||
|
|
||||||
|
|
||||||
|
async def login(request):
|
||||||
|
log = request.app["log"]
|
||||||
|
log.debug("login")
|
||||||
|
|
||||||
|
store = request.app["store"]
|
||||||
|
config = request.app["config"]
|
||||||
|
|
||||||
|
body = await request.json()
|
||||||
|
|
||||||
|
tag = body.get("tag", "uid")
|
||||||
|
|
||||||
|
ou = body.get("ou")
|
||||||
|
if ou is not None:
|
||||||
|
config["ldap"]["ou"] = ou
|
||||||
|
|
||||||
|
try:
|
||||||
|
user = body["user"]
|
||||||
|
password = body["password"]
|
||||||
|
except KeyError as e:
|
||||||
|
text = f"Missing key: {e}"
|
||||||
|
log.warn(text)
|
||||||
|
raise HTTPBadRequest(text=text)
|
||||||
|
|
||||||
|
client = AsyncClient(
|
||||||
|
attribute_id=tag, username=user, password=password, **config.get("ldap")
|
||||||
|
)
|
||||||
|
|
||||||
|
log.debug(f"Client: {client}")
|
||||||
|
|
||||||
|
await store.set_client(request, client)
|
||||||
|
|
||||||
|
raise HTTPOk
|
Loading…
Reference in New Issue
Block a user