question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Improved RBAC support for authentication.requires

See original GitHub issue

The requires scope uses an all check which can be problematic for certain endpoints using RBAC. This is because for endpoints where, say, Data-Science and Sales both have access it can start to get complicated. I wrote this and am currently using it as a solution. It’s backward compatible. I would’ve forked and made a PR but I’m not in it for the glory hah

import asyncio
import functools
import inspect
import typing

from starlette.exceptions import HTTPException
from starlette.requests import HTTPConnection, Request
from starlette.responses import RedirectResponse, Response
from starlette.websockets import WebSocket


def has_required_scope(conn: HTTPConnection, scopes: typing.Sequence[str], match_all: bool = True) -> bool:
    if match_all:
        return all(scope in conn.auth.scopes for scope in scopes)
    else:
        return any(scope in conn.auth.scopes for scope in scopes)


def requires(
    scopes: typing.Union[str, typing.Sequence[str]],
    status_code: int = 403,
    redirect: str = None,
    requires_all: bool = True
) -> typing.Callable:
    scopes_list = [scopes] if isinstance(scopes, str) else list(scopes)

    def decorator(func: typing.Callable) -> typing.Callable:
        type = None
        sig = inspect.signature(func)
        for idx, parameter in enumerate(sig.parameters.values()):
            if parameter.name == "request" or parameter.name == "websocket":
                type = parameter.name
                break
        else:
            raise Exception(
                f'No "request" or "websocket" argument on function "{func}"'
            )

        if type == "websocket":
            # Handle websocket functions. (Always async)
            @functools.wraps(func)
            async def websocket_wrapper(
                *args: typing.Any, **kwargs: typing.Any
            ) -> None:
                websocket = kwargs.get("websocket", args[idx])
                assert isinstance(websocket, WebSocket)

                if not has_required_scope(websocket, scopes_list, requires_all):
                    await websocket.close()
                else:
                    await func(*args, **kwargs)

            return websocket_wrapper

        elif asyncio.iscoroutinefunction(func):
            # Handle async request/response functions.
            @functools.wraps(func)
            async def async_wrapper(
                *args: typing.Any, **kwargs: typing.Any
            ) -> Response:
                request = kwargs.get("request", args[idx])
                assert isinstance(request, Request)

                if not has_required_scope(request, scopes_list, requires_all):
                    if redirect is not None:
                        return RedirectResponse(url=request.url_for(redirect))
                    raise HTTPException(status_code=status_code)
                return await func(*args, **kwargs)

            return async_wrapper

        else:
            # Handle sync request/response functions.
            @functools.wraps(func)
            def sync_wrapper(*args: typing.Any, **kwargs: typing.Any) -> Response:
                request = kwargs.get("request", args[idx])
                assert isinstance(request, Request)

                if not has_required_scope(request, scopes_list, requires_all):
                    if redirect is not None:
                        return RedirectResponse(url=request.url_for(redirect))
                    raise HTTPException(status_code=status_code)
                return func(*args, **kwargs)

            return sync_wrapper

    return decorator

Example:

@app.route('/v1/customer/{customer_id}/reports/unique-files')
@requires(['DataScience', 'Manager', 'Sales'], requires_all=False)
async def unique_file_report(request):

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Reactions:3
  • Comments:10 (4 by maintainers)

github_iconTop GitHub Comments

1reaction
tomchristiecommented, Feb 8, 2022

This seems very sensible. Auth can be arbitrarily complex, but that can’t be expressed declaratively in something like OpenAPI. So it makes sense that there’s a “declarative” level that interacts w/ the routing table and such, and another “programmatic” layer that holds any more advanced auth logic.

Exactly yes.

should the declarative layer serve only for documentation / routing purposes, or should those scope requirements also be enforced

If we did do something here, then we’d also want to enforce it yes.

However I wouldn’t want us to consider anything here until we feel like the issue backlog is very much in hand. Let’s keep the focus right now on stability, bug-fixes, and getting everything to a really nicely sustainable workflow.

1reaction
taybincommented, Sep 29, 2021

I think if we were just able to pass in our own has_required_scope() to requires, that would open up a lot of flexibility.

Read more comments on GitHub >

github_iconTop Results From Across the Web

RBAC (Role-Based Access Control): What is it and why use it?
RBAC defined three basic requirements for access control: ... Security: RBAC improves overall security as it relates to compliance, ...
Read more >
Authorization using Role-Based Access Control
Before implementing RBAC you should evaluate the security needs of the users in your organization and, based on the resources they require to...
Read more >
What is Role-Based Access Control | RBAC vs ACL & ABAC
Access control measures regulate who can view or use resources in a computing system, often relying on authentication or authorization based on log-in ......
Read more >
What is Role-Based Access Control (RBAC)? - Varonis
Role-Based Access Control (RBAC) is a security paradigm whereby users are granted access to resources based on their role in the company.
Read more >
Authentication and Authorization with RBAC - Couchbase
In March's developer build, you can start to see some major changes to authentication and authorization within Role Based Access Control ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found