Daily Tips - Control Permissions Precisely ๐Ÿ›

Hi there,

This week I saw someone working on how flask-login could be used in dash. Iโ€™m curious if anyone cares about permissions after login.

When dash is used in an organization, we have needs from different users, and their demands may overlap but not the same. If I can meet their needs in the same dashboard, then I can save a lot of time and cost.

There is a framework that helps us do this.

I did some preliminary exploration.
Hereโ€™s the code.

app.py

from doctest import OutputChecker
from dash import Dash, html, dcc, Input, Output
import dash_auth
from oso import Oso, NotFoundError
from models import User, Component
from flask import request

# Initialize the Oso object. This object is usually used globally throughout
# an application.
oso = Oso()


# Tell Oso about the data you will authorize. These types can be referenced
# in the policy.
oso.register_class(User)
oso.register_class(Component)

# Load your policy files.
oso.load_files(["main.polar"])

VALID_USERNAME_PASSWORD_PAIRS = {
    "larry": "abc",
    "anne": "abc",
    "graham": "abc",
    "ivy": "abc",
}

app = Dash(__name__)

auth = dash_auth.BasicAuth(app, VALID_USERNAME_PASSWORD_PAIRS)


def is_visible(comp, comp_type):
    try:
        oso.authorize(
            User.get_current_user(request.authorization["username"]),
            "visible",
            Component.get(comp_type),
        )
        return comp
    except NotFoundError:
        return html.Div("Unauthorized.")


app.layout = html.Div([html.Div(id="content"), html.Button(id="btn")])


@app.callback(Output("content", "children"), Input("btn", "n_clicks"))
def update(n):
    return [
        is_visible(html.Div("This is for public."), "public"),
        is_visible(html.Div("This is for private."), "private"),
        is_visible(html.Div("This is for admin."), "admin"),
    ]


if __name__ == "__main__":
    app.run_server(debug=True)

model.py

from dataclasses import dataclass

from typing import List

@dataclass

class Component:

    name: str

    is_public: bool = False

    @staticmethod

    def get(name):

        return comp_db.get(name)

@dataclass

class Role:

    name: str

    component: Component

@dataclass

class User:

    roles: List[Role]

    @staticmethod

    def get_current_user(name):

        return users_db[name]

comp_db = {

    "public": Component("public", is_public=True),

    "admin": Component("admin"),

    "private": Component("private"),

}

users_db = {

    "larry": User(

        [

            Role(

                name="admin",

                component=comp_db["admin"],

            ),

            Role(

                name="admin",

                component=comp_db["private"],

            ),

        ]

    ),

    "anne": User(

        [

            Role(

                name="maintainer",

                component=comp_db["private"],

            )

        ]

    ),

    "graham": User(

        [

            Role(

                name="contributor",

                component=comp_db["private"],

            )

        ]

    ),

    "ivy": User(

        [

            Role(

                name="visitor",

                component=comp_db["public"],

            ),

        ]

    ),

}

main.polar

actor User {}

resource Component {

  permissions = ["visible"];

  roles = ["contributor", "maintainer", "admin", "visitor"];

  "visible" if "visitor";

  "maintainer" if "admin";

  "contributor" if "maintainer";

  "visitor" if "contributor";

}

has_role(actor: User, role_name: String, component:Component) if

  role in actor.roles and

  role_name = role.name and

  role.component = component;

has_permission(_actor: User, "visible", component:Component) if

  component.is_public;

allow(actor, action, resource) if

  has_permission(actor, action, resource);

As you can see, theyโ€™ve defined a new permission configuration language called polar. However, their documentation mentions that these definitions can also be written in py files.
like this

allow(actor, _action, _resource) if actor.username.endswith("example.com");

\searrow

user = User()
user.username = "alice@example.com"
assert(oso.is_allowed(user, "foo", "bar))

Iโ€™m still figuring it out, and the end result I want to achieve is, for examples, a manager from the Region A can only visit the data for his areas. And there is a drop-down with ten options in total. A table related to other tables in a dashboard is only meaningful to the finance department, and I can only let colleagues in their department see it. A lower rank manager can only see 4 or 5 options, or the drop-down component is disabled for him, but a higher rank manager can see all those options, that is, users see different content based on their role. Or you can give different levels of access to different users based on their position. Then I donโ€™t have to develop redundant dashboards separately for different departments or employees.


As you may have noticed, I used authorization validation in the callback because the query of userid requires an active HTTP request. In fact, OSO also supports node.js, so maybe there could be a dash component can be used to query authorization.

app.layout = dash_oso.Authorize([html.Div(), html.Div()], "", "")

Considering the characteristics of dash, I was thinking that maybe a permission component of dash could be integrated so that it can be used on both the front-end and server-side. It is also possible to implement a decorator for callbacks.

@dash_oso.authorize(resource="table")
@app.callback(Output(), Input())
def submit():
    db.insert()
    return "success"

Implementing these may help Dash be very competitive in authorization feature.

Since Iโ€™m not a professional developer, I may have to spend a long time thinking about the feasibility of this stuff. Or if anyone is interested in helping and discussing it would be great.






Hope you like this. XD

Keywords: OSO, RBAC, ABAC, Flask-login, Flask-dance, Data Filtering, SQLAlchemy, Auth

Other Daily Tips series:
Daily Tips - If I have a Bunch of Triggers
Daily Tips - Share the Function
Daily Tips - How many Ways to share Dash in a company?
Daily Tips - Give your Component a Name
Daily Tips - Share Dash to Social Media
Daily Tips - Double-click to open Dash
Daily Tips - What rows of data have I modified?
Daily Tips - Write the Simplest Client-side Callback
Daily Tips - Some simple Expressions
Daily Tips - IoT? Real Real-time Data and Live Update
Daily Tips - Which formatter is your favorite?
Daily Tips - Convert a React Component to Dash
Daily Tips - Real-time Logs on Dash

5 Likes

Progress: They are interested in integration. We may soon have a battery-included authorization system for Dash. @dash-beginner

I hope Dash developers who are interested in this will also actively participate in this discussion. Thanks!

2 Likes

Wow that would be incredible!! Great work and please do keep me posted on any updates :slight_smile: (Iโ€™ll try to follow along, but I anticipate that it will quickly get a bit out of my realm of knowledge, hence the moniker on this forum haha).

1 Like