Example: aiohttpΒΆ
The following is an example of using flaat with aiohttp. Using flaat with Flask or fastapi is basically the same. You can use the example using:
# run the server:
python ./examples/example_aio.py
# Do the following commands in a different shell
# With oidc agent:
curl http://localhost:8080/info -H "Authorization:Bearer $(oidc-token <account name>)"
# With access token:
curl http://localhost:8080/info -H "Authorization:Bearer eyJraWQ..."
from aiohttp import web
from examples.logsetup import setup_logging
from flaat import AuthWorkflow
from flaat.aio import Flaat
from flaat.exceptions import FlaatException
from flaat.requirements import CheckResult, get_claim_requirement, get_vo_requirement
from flaat.user_infos import UserInfos
# do some log setup so we can see something
setup_logging()
##########
# aio application
app = web.Application()
routes = web.RouteTableDef()
# Our FLAAT instance
flaat = Flaat()
# Insert OPs that you trust here
flaat.set_trusted_OP_list(
[
"https://b2access.eudat.eu/oauth2/",
"https://b2access-integration.fz-juelich.de/oauth2",
"https://unity.helmholtz-data-federation.de/oauth2/",
"https://login.helmholtz-data-federation.de/oauth2/",
"https://login-dev.helmholtz.de/oauth2/",
"https://login.helmholtz.de/oauth2/",
"https://unity.eudat-aai.fz-juelich.de/oauth2/",
"https://services.humanbrainproject.eu/oidc/",
"https://accounts.google.com/",
"https://login.elixir-czech.org/oidc/",
"https://iam-test.indigo-datacloud.eu/",
"https://iam.deep-hybrid-datacloud.eu/",
"https://iam.extreme-datacloud.eu/",
"https://aai.egi.eu/oidc/",
"https://aai-demo.egi.eu/oidc",
"https://aai-dev.egi.eu/oidc",
"https://oidc.scc.kit.edu/auth/realms/kit/",
"https://proxy.demo.eduteams.org",
"https://wlcg.cloud.cnaf.infn.it/",
]
)
# verbosity:
# 0: Errors
# 1: Warnings
# 2: Infos
# 3: Debug output
flaat.set_verbosity(3)
# # Required for using token introspection endpoint:
# flaat.set_client_id('')
# flaat.set_client_secret('')
# this will customize error responses for the user (used down below)
def my_on_failure(exception: FlaatException, user_infos: UserInfos = None):
user = "no auth"
if user_infos is not None:
user = str(user_infos)
return web.Response(
text=f"Custom my_on_failure invoked:\nError Message: {exception}\nUser: {user}"
)
@routes.get("/")
async def root(request):
text = """This is an example for useing flaat with AIO. These endpoints are available:
/info General info about the access_token (if provided)
/authenticated Requires a valid user
/authenticated_callback Requires a valid user, uses a custom callback on error
/authorized_claim Requires user to have one of two claims
/authorized_vo Requires user to have an entitlement
/full_custom Fully custom auth handling
"""
return web.Response(text=text)
@routes.get("/info")
@flaat.inject_user_infos(
strict=False, # we don't fail if there is no user
)
async def info(
request,
user_infos: UserInfos = None, # here is the variable that gets injected, this should have a default value
):
message = "No userinfo"
if user_infos is not None:
message = user_infos.toJSON()
return web.Response(text=message)
@routes.get("/authenticated")
@flaat.is_authenticated()
async def authenticated_user(request):
return web.Response(text="This worked: there was a valid login")
@routes.get("/authenticated_callback")
@flaat.is_authenticated(
on_failure=my_on_failure, # called if there is no authentication
)
async def valid_user_own_callback(request):
"""instead of giving an error this will return the custom error response from `my_on_failure`"""
return web.Response(text="This worked: there was a valid login")
@routes.get("/authorized_claim")
@flaat.requires(
get_claim_requirement( # the user needs to satisfy this requirement (having one of the email claims)
["admin@foo.org", "dev@foo.org"],
claim="email",
match=1,
),
)
async def authorized_claim(request):
return web.Response(text="This worked: User has the claim")
@routes.get("/authorized_vo")
@flaat.requires(
get_vo_requirement(
[
"urn:geant:h-df.de:group:m-team:feudal-developers",
"urn:geant:h-df.de:group:MyExampleColab#unity.helmholtz.de",
],
"eduperson_entitlement",
match=1,
)
)
async def authorized_vo(request):
return web.Response(text="This worked: user has the required entitlement")
# If you need maxmimum customizability, there is AuthWorkflow:
# User requirements
user_reqs = get_claim_requirement("bar", "foo")
def my_request_check(user_infos: UserInfos, *args, **kwargs):
if len(args) != 1:
return CheckResult(False, "Missing request object")
return CheckResult(True, "The request is allowed")
def my_process_args(user_infos: UserInfos, *args, **kwargs):
"""
We can manipulate the view functions arguments here
The user is already authenticated at this point, therefore we have `user_infos`,
therefore we can base our manipulations on the users identity.
"""
kwargs["email"] = user_infos.get("email", "")
return (args, kwargs)
custom = AuthWorkflow(
flaat, # needs the flaat instance
user_requirements=user_reqs,
request_requirements=my_request_check,
process_arguments=my_process_args,
on_failure=my_on_failure,
ignore_no_authn=True, # Don't fail if there is no authentication
)
@routes.get("/full_custom")
@custom.decorate_view_func # invoke the workflow here
async def full_custom(request, email=""):
return web.Response(
text=f"This worked: The custom workflow did succeed\nThe users email is: {email}"
)
app.add_routes(routes)
##########
# Main
if __name__ == "__main__":
web.run_app(app, host="0.0.0.0", port=8080)