Skip to content

auth

Module with classes and functions used for authentication and password handling.

Modules:

Name Description
models

Module with database tables definitions.

Classes:

Name Description
DataLunchLoginHandler

Custom Panel login Handler.

DataLunchProvider

Custom Panel auth provider.

PasswordEncrypt

Class that store the encrypted value of a password.

PasswordHash

Class that store the hashed value of a password.

Functions:

Name Description
add_privileged_user

Add user id to privileged_users table.

add_user_hashed_password

Add user credentials to credentials table.

authorize

Authorization callback: read config, user info and the target path of the

force_logout

Redirect the browser to the logout endpoint

generate_password

summary

get_hash_from_user

Query the database to retrieve the hashed password for a certain user.

is_admin

Check if a user is an admin by checking the privileged_users table

is_auth_active

Check configuration dictionary and return True if basic authentication or OAuth is active.

is_basic_auth_active

Check config object and return True if basic authentication is active.

is_guest

Check if a user is a guest by checking if it is listed inside the privileged_users table.

list_users

List only privileged users (from privileged_users table).

list_users_guests_and_privileges

Join privileged_users and credentials tables to list normal users,

open_backend

Redirect the browser to the backend endpoint

pn_user

Return the user from Panel state object.

remove_user

Remove user from the database.

set_app_auth_and_encryption

Setup Panel authorization and encryption.

Attributes:

Name Type Description
log Logger

Module logger.

pwd_context CryptContext

Crypt context with configurations for passlib (selected algorithm, etc.).

log module-attribute

log: Logger = getLogger(__name__)

Module logger.

pwd_context module-attribute

pwd_context: CryptContext = CryptContext(
    schemes=["pbkdf2_sha256"], deprecated="auto"
)

Crypt context with configurations for passlib (selected algorithm, etc.).

DataLunchLoginHandler

Bases: RequestHandler

Custom Panel login Handler.

This class run the user authentication process for Data-Lunch when basic authentication is selected in configuration options.

It is responsible of rendering the login page, validate the user (and update its password hash if the hashing protocol is superseeded) and set the current user once validated.

Methods:

Name Description
check_permission

Validate user.

get

Render the login template.

post

Validate user and set the current user if valid.

set_current_user

Set secure cookie for the selected user.

Source code in dlunch/auth.py
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
class DataLunchLoginHandler(RequestHandler):
    """Custom Panel login Handler.

    This class run the user authentication process for Data-Lunch when basic authentication
    is selected in configuration options.

    It is responsible of rendering the login page, validate the user (and update its
    password hash if the hashing protocol is superseeded) and set the current user once validated.
    """

    def get(self) -> None:
        """Render the login template."""
        try:
            errormessage = self.get_argument("error")
        except Exception:
            errormessage = ""
        html = self._login_template.render(errormessage=errormessage)
        self.write(html)

    def check_permission(self, user: str, password: str) -> bool:
        """Validate user.

        Automatically update the password hash if it was generated by an old hashing protocol.

        Args:
            user (str): username.
            password (str): password (not hashed).

        Returns:
            bool: user authentication flag (`True` if authenticated)
        """
        password_hash = get_hash_from_user(user, self.config)
        if password_hash == password:
            # Check if hash needs update
            valid, new_hash = password_hash.verify_and_update(password)
            if valid and new_hash:
                # Update to new hash
                add_user_hashed_password(user, password, config=self.config)
            # Return the OK value
            return True
        # Return the NOT OK value
        return False

    def post(self) -> None:
        """Validate user and set the current user if valid."""
        username = self.get_argument("username", "")
        password = self.get_argument("password", "")
        auth = self.check_permission(username, password)
        if auth:
            self.set_current_user(username)
            self.redirect("/")
        else:
            error_msg = "?error=" + tornado.escape.url_escape(
                "Login incorrect"
            )
            self.redirect("/login" + error_msg)

    def set_current_user(self, user: str):
        """Set secure cookie for the selected user.

        Args:
            user (str): username.
        """
        if not user:
            self.clear_cookie("user")
            return
        self.set_secure_cookie(
            "user",
            user,
            expires_days=pn.config.oauth_expiry,
            **self.config.auth.cookie_kwargs,
        )
        id_token = base64url_encode(json.dumps({"user": user}))
        if pn.state.encryption:
            id_token = pn.state.encryption.encrypt(id_token.encode("utf-8"))
        self.set_secure_cookie(
            "id_token",
            id_token,
            expires_days=pn.config.oauth_expiry,
            **self.config.auth.cookie_kwargs,
        )

check_permission

check_permission(user: str, password: str) -> bool

Validate user.

Automatically update the password hash if it was generated by an old hashing protocol.

Parameters:

Name Type Description Default
user str

username.

required
password str

password (not hashed).

required

Returns:

Type Description
bool

user authentication flag (True if authenticated)

Source code in dlunch/auth.py
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
def check_permission(self, user: str, password: str) -> bool:
    """Validate user.

    Automatically update the password hash if it was generated by an old hashing protocol.

    Args:
        user (str): username.
        password (str): password (not hashed).

    Returns:
        bool: user authentication flag (`True` if authenticated)
    """
    password_hash = get_hash_from_user(user, self.config)
    if password_hash == password:
        # Check if hash needs update
        valid, new_hash = password_hash.verify_and_update(password)
        if valid and new_hash:
            # Update to new hash
            add_user_hashed_password(user, password, config=self.config)
        # Return the OK value
        return True
    # Return the NOT OK value
    return False

get

get() -> None

Render the login template.

Source code in dlunch/auth.py
67
68
69
70
71
72
73
74
def get(self) -> None:
    """Render the login template."""
    try:
        errormessage = self.get_argument("error")
    except Exception:
        errormessage = ""
    html = self._login_template.render(errormessage=errormessage)
    self.write(html)

post

post() -> None

Validate user and set the current user if valid.

Source code in dlunch/auth.py
100
101
102
103
104
105
106
107
108
109
110
111
112
def post(self) -> None:
    """Validate user and set the current user if valid."""
    username = self.get_argument("username", "")
    password = self.get_argument("password", "")
    auth = self.check_permission(username, password)
    if auth:
        self.set_current_user(username)
        self.redirect("/")
    else:
        error_msg = "?error=" + tornado.escape.url_escape(
            "Login incorrect"
        )
        self.redirect("/login" + error_msg)

set_current_user

set_current_user(user: str)

Set secure cookie for the selected user.

Parameters:

Name Type Description Default
user str

username.

required
Source code in dlunch/auth.py
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
def set_current_user(self, user: str):
    """Set secure cookie for the selected user.

    Args:
        user (str): username.
    """
    if not user:
        self.clear_cookie("user")
        return
    self.set_secure_cookie(
        "user",
        user,
        expires_days=pn.config.oauth_expiry,
        **self.config.auth.cookie_kwargs,
    )
    id_token = base64url_encode(json.dumps({"user": user}))
    if pn.state.encryption:
        id_token = pn.state.encryption.encrypt(id_token.encode("utf-8"))
    self.set_secure_cookie(
        "id_token",
        id_token,
        expires_days=pn.config.oauth_expiry,
        **self.config.auth.cookie_kwargs,
    )

DataLunchProvider

Bases: OAuthProvider

Custom Panel auth provider.

It's a simple login page with a form that interacts with authentication tables.

It is used only if basic authentication is selected in Data-Lunch configuration options.

Attributes:

Name Type Description
config DictConfig

Hydra configuration dictionary.

Parameters:

Name Type Description Default
config DictConfig

Hydra configuration dictionary.

required
login_template str | None

path to login template. Defaults to None.

None
logout_template str | None

path to logout template. Defaults to None.

None

Methods:

Name Description
__init__

Attributes:

Name Type Description
config
login_handler

DataLunchLoginHandler: Data-Lunch custom login handler.

login_url

str: Login url (/login).

Source code in dlunch/auth.py
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
class DataLunchProvider(OAuthProvider):
    """Custom Panel auth provider.

    It's a simple login page with a form that interacts with authentication tables.

    It is used only if basic authentication is selected in Data-Lunch configuration options.

    Attributes:
        config (DictConfig): Hydra configuration dictionary.

    Args:
        config (DictConfig): Hydra configuration dictionary.
        login_template (str | None, optional): path to login template. Defaults to None.
        logout_template (str | None, optional): path to logout template. Defaults to None.
    """

    def __init__(
        self,
        config: DictConfig,
        login_template: str | None = None,
        logout_template: str | None = None,
    ) -> None:
        # Set Hydra config info
        self.config = config

        super().__init__(
            login_template=login_template, logout_template=logout_template
        )

    @property
    def login_url(self):
        """str: Login url (`/login`)."""
        return "/login"

    @property
    def login_handler(self):
        """DataLunchLoginHandler: Data-Lunch custom login handler."""
        # Set basic template
        DataLunchLoginHandler._login_template = self._login_template
        # Set Hydra config info
        DataLunchLoginHandler.config = self.config

        return DataLunchLoginHandler

config instance-attribute

config = config

login_handler property

login_handler

DataLunchLoginHandler: Data-Lunch custom login handler.

login_url property

login_url

str: Login url (/login).

__init__

__init__(
    config: DictConfig,
    login_template: str | None = None,
    logout_template: str | None = None,
) -> None
Source code in dlunch/auth.py
156
157
158
159
160
161
162
163
164
165
166
167
def __init__(
    self,
    config: DictConfig,
    login_template: str | None = None,
    logout_template: str | None = None,
) -> None:
    # Set Hydra config info
    self.config = config

    super().__init__(
        login_template=login_template, logout_template=logout_template
    )

PasswordEncrypt

Class that store the encrypted value of a password.

The encryption is based on Panel encryption system.

The class has methods to encrypt and decrypt a string.

The encrypted password may be passed to instantiate the new object. If the encrypted password is not aviailable use the class method PasswordEncrypt.from_str to create an istance with the string properly encrypted.

Parameters:

Name Type Description Default
encrypted_password str

encrypted password.

required

Methods:

Name Description
__eq__

Decrypt the candidate string and compares it to the stored encrypted value.

__init__
__repr__

Simple object representation.

decrypt

Return decrypted password.

encrypt

Return encrypted password.

from_str

Creates a PasswordEncrypt from the given string.

Attributes:

Name Type Description
encrypted_password

str: encrypted password.

Source code in dlunch/auth.py
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
class PasswordEncrypt:
    """Class that store the encrypted value of a password.

    The encryption is based on Panel encryption system.

    The class has methods to encrypt and decrypt a string.

    The encrypted password may be passed to instantiate the new object.
    If the encrypted password is not aviailable use the class method
    `PasswordEncrypt.from_str` to create an istance with the string properly
    encrypted.

    Args:
        encrypted_password (str): encrypted password.
    """

    def __init__(self, encrypted_password: str) -> None:
        # Consistency checks
        assert (
            len(encrypted_password) <= 150
        ), "encrypted string should have less than 150 chars."
        # Attributes
        self.encrypted_password = encrypted_password
        """str: encrypted password."""

    def __eq__(self, candidate: str) -> bool:
        """Decrypt the candidate string and compares it to the stored encrypted value.

        Args:
            candidate (str): candidate string.

        Returns:
            bool: `True` if equal.
        """
        # If string check hash, otherwise return False
        if isinstance(candidate, str):
            # Replace hashed_password if the algorithm changes
            valid = self.decrypt() == candidate
        else:
            valid = False

        return valid

    def __repr__(self) -> str:
        """Simple object representation.

        Returns:
            str: string representation.
        """
        return f"<{type(self).__name__}>"

    @staticmethod
    def encrypt(password: str) -> str:
        """Return encrypted password.

        Args:
            password (str): plain password (not encrypted).

        Returns:
            str: encrypted password.
        """
        if pn.state.encryption:
            encrypted_password = pn.state.encryption.encrypt(
                password.encode("utf-8")
            ).decode("utf-8")
        else:
            encrypted_password = password
        return encrypted_password

    def decrypt(self) -> str:
        """Return decrypted password.

        Returns:
            str: plain password (not encrypted).
        """
        if pn.state.encryption:
            password = pn.state.encryption.decrypt(
                self.encrypted_password.encode("utf-8")
            ).decode("utf-8")
        else:
            password = self.encrypted_password
        return password

    @classmethod
    def from_str(cls, password: str) -> Self:
        """Creates a PasswordEncrypt from the given string.

        Args:
            password (str): plain password (not encrypted).

        Returns:
            PasswordEncrypt: new class instance with encrypted value already stored.
        """
        return cls(cls.encrypt(password))

encrypted_password instance-attribute

encrypted_password = encrypted_password

str: encrypted password.

__eq__

__eq__(candidate: str) -> bool

Decrypt the candidate string and compares it to the stored encrypted value.

Parameters:

Name Type Description Default
candidate str

candidate string.

required

Returns:

Type Description
bool

True if equal.

Source code in dlunch/auth.py
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
def __eq__(self, candidate: str) -> bool:
    """Decrypt the candidate string and compares it to the stored encrypted value.

    Args:
        candidate (str): candidate string.

    Returns:
        bool: `True` if equal.
    """
    # If string check hash, otherwise return False
    if isinstance(candidate, str):
        # Replace hashed_password if the algorithm changes
        valid = self.decrypt() == candidate
    else:
        valid = False

    return valid

__init__

__init__(encrypted_password: str) -> None
Source code in dlunch/auth.py
308
309
310
311
312
313
314
315
def __init__(self, encrypted_password: str) -> None:
    # Consistency checks
    assert (
        len(encrypted_password) <= 150
    ), "encrypted string should have less than 150 chars."
    # Attributes
    self.encrypted_password = encrypted_password
    """str: encrypted password."""

__repr__

__repr__() -> str

Simple object representation.

Returns:

Type Description
str

string representation.

Source code in dlunch/auth.py
335
336
337
338
339
340
341
def __repr__(self) -> str:
    """Simple object representation.

    Returns:
        str: string representation.
    """
    return f"<{type(self).__name__}>"

decrypt

decrypt() -> str

Return decrypted password.

Returns:

Type Description
str

plain password (not encrypted).

Source code in dlunch/auth.py
361
362
363
364
365
366
367
368
369
370
371
372
373
def decrypt(self) -> str:
    """Return decrypted password.

    Returns:
        str: plain password (not encrypted).
    """
    if pn.state.encryption:
        password = pn.state.encryption.decrypt(
            self.encrypted_password.encode("utf-8")
        ).decode("utf-8")
    else:
        password = self.encrypted_password
    return password

encrypt staticmethod

encrypt(password: str) -> str

Return encrypted password.

Parameters:

Name Type Description Default
password str

plain password (not encrypted).

required

Returns:

Type Description
str

encrypted password.

Source code in dlunch/auth.py
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
@staticmethod
def encrypt(password: str) -> str:
    """Return encrypted password.

    Args:
        password (str): plain password (not encrypted).

    Returns:
        str: encrypted password.
    """
    if pn.state.encryption:
        encrypted_password = pn.state.encryption.encrypt(
            password.encode("utf-8")
        ).decode("utf-8")
    else:
        encrypted_password = password
    return encrypted_password

from_str classmethod

from_str(password: str) -> Self

Creates a PasswordEncrypt from the given string.

Parameters:

Name Type Description Default
password str

plain password (not encrypted).

required

Returns:

Type Description
PasswordEncrypt

new class instance with encrypted value already stored.

Source code in dlunch/auth.py
375
376
377
378
379
380
381
382
383
384
385
@classmethod
def from_str(cls, password: str) -> Self:
    """Creates a PasswordEncrypt from the given string.

    Args:
        password (str): plain password (not encrypted).

    Returns:
        PasswordEncrypt: new class instance with encrypted value already stored.
    """
    return cls(cls.encrypt(password))

PasswordHash

Class that store the hashed value of a password.

The password hash may be passed to instantiate the new object. If the hash is not aviailable use the class method PasswordHash.from_str to create an istance with the string properly hashed.

Parameters:

Name Type Description Default
hashed_password str

password hash.

required

Methods:

Name Description
__eq__

Hashes the candidate string and compares it to the stored hash.

__init__
__repr__

Simple object representation.

from_str

Creates a PasswordHash from the given string.

hash

Return hash of the given password.

verify

Check a password against its hash and return True if check passes,

verify_and_update

Check a password against its hash and return True if check passes,

Attributes:

Name Type Description
hashed_password

str: Password hash.

Source code in dlunch/auth.py
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
class PasswordHash:
    """Class that store the hashed value of a password.

    The password hash may be passed to instantiate the new object.
    If the hash is not aviailable use the class method
    `PasswordHash.from_str` to create an istance with the string properly
    hashed.

    Args:
        hashed_password (str): password hash.
    """

    def __init__(self, hashed_password: str) -> None:
        # Consistency checks
        assert (
            len(hashed_password) <= 150
        ), "hash should have less than 150 chars."
        # Attributes
        self.hashed_password = hashed_password
        """str: Password hash."""

    def __eq__(self, candidate: str) -> bool:
        """Hashes the candidate string and compares it to the stored hash.

        Args:
            candidate (str): candidate string.

        Returns:
            bool: `True` if equal.
        """
        # If string check hash, otherwise return False
        if isinstance(candidate, str):
            # Replace hashed_password if the algorithm changes
            valid = self.verify(candidate)
        else:
            valid = False

        return valid

    def __repr__(self) -> str:
        """Simple object representation.

        Returns:
            str: string representation.
        """
        return f"<{type(self).__name__}>"

    def verify(self, password: str) -> bool:
        """Check a password against its hash and return `True` if check passes,
        `False` otherwise.

        Args:
            password (str): plain password (not hashed).

        Returns:
            bool: `True` if password and hash match.
        """
        valid = pwd_context.verify(saslprep(password), self.hashed_password)

        return valid

    def verify_and_update(self, password: str) -> tuple[bool, str | None]:
        """Check a password against its hash and return `True` if check passes,
        `False` otherwise. Return also a new hash if the original hashing  method
        is superseeded

        Args:
            password (str): plain password (not hashed).

        Returns:
            tuple[bool, str | None]: return a tuple with two elements (valid, new_hash).
                valid: `True` if password and hash match.
                new_hash: new hash to replace the one generated with an old algorithm.
        """
        valid, new_hash = pwd_context.verify_and_update(
            saslprep(password), self.hashed_password
        )
        if valid and new_hash:
            self.hashed_password = new_hash

        return valid, new_hash

    @staticmethod
    def hash(password: str) -> str:
        """Return hash of the given password.

        Args:
            password (str): plain password (not hashed).

        Returns:
            str: hashed password.
        """
        return pwd_context.hash(saslprep(password))

    @classmethod
    def from_str(cls, password: str) -> Self:
        """Creates a PasswordHash from the given string.

        Args:
            password (str): plain password (not hashed).

        Returns:
            PasswordHash: new class instance with hashed value already stored.
        """
        return cls(cls.hash(password))

hashed_password instance-attribute

hashed_password = hashed_password

str: Password hash.

__eq__

__eq__(candidate: str) -> bool

Hashes the candidate string and compares it to the stored hash.

Parameters:

Name Type Description Default
candidate str

candidate string.

required

Returns:

Type Description
bool

True if equal.

Source code in dlunch/auth.py
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
def __eq__(self, candidate: str) -> bool:
    """Hashes the candidate string and compares it to the stored hash.

    Args:
        candidate (str): candidate string.

    Returns:
        bool: `True` if equal.
    """
    # If string check hash, otherwise return False
    if isinstance(candidate, str):
        # Replace hashed_password if the algorithm changes
        valid = self.verify(candidate)
    else:
        valid = False

    return valid

__init__

__init__(hashed_password: str) -> None
Source code in dlunch/auth.py
197
198
199
200
201
202
203
204
def __init__(self, hashed_password: str) -> None:
    # Consistency checks
    assert (
        len(hashed_password) <= 150
    ), "hash should have less than 150 chars."
    # Attributes
    self.hashed_password = hashed_password
    """str: Password hash."""

__repr__

__repr__() -> str

Simple object representation.

Returns:

Type Description
str

string representation.

Source code in dlunch/auth.py
224
225
226
227
228
229
230
def __repr__(self) -> str:
    """Simple object representation.

    Returns:
        str: string representation.
    """
    return f"<{type(self).__name__}>"

from_str classmethod

from_str(password: str) -> Self

Creates a PasswordHash from the given string.

Parameters:

Name Type Description Default
password str

plain password (not hashed).

required

Returns:

Type Description
PasswordHash

new class instance with hashed value already stored.

Source code in dlunch/auth.py
279
280
281
282
283
284
285
286
287
288
289
@classmethod
def from_str(cls, password: str) -> Self:
    """Creates a PasswordHash from the given string.

    Args:
        password (str): plain password (not hashed).

    Returns:
        PasswordHash: new class instance with hashed value already stored.
    """
    return cls(cls.hash(password))

hash staticmethod

hash(password: str) -> str

Return hash of the given password.

Parameters:

Name Type Description Default
password str

plain password (not hashed).

required

Returns:

Type Description
str

hashed password.

Source code in dlunch/auth.py
267
268
269
270
271
272
273
274
275
276
277
@staticmethod
def hash(password: str) -> str:
    """Return hash of the given password.

    Args:
        password (str): plain password (not hashed).

    Returns:
        str: hashed password.
    """
    return pwd_context.hash(saslprep(password))

verify

verify(password: str) -> bool

Check a password against its hash and return True if check passes, False otherwise.

Parameters:

Name Type Description Default
password str

plain password (not hashed).

required

Returns:

Type Description
bool

True if password and hash match.

Source code in dlunch/auth.py
232
233
234
235
236
237
238
239
240
241
242
243
244
def verify(self, password: str) -> bool:
    """Check a password against its hash and return `True` if check passes,
    `False` otherwise.

    Args:
        password (str): plain password (not hashed).

    Returns:
        bool: `True` if password and hash match.
    """
    valid = pwd_context.verify(saslprep(password), self.hashed_password)

    return valid

verify_and_update

verify_and_update(password: str) -> tuple[bool, str | None]

Check a password against its hash and return True if check passes, False otherwise. Return also a new hash if the original hashing method is superseeded

Parameters:

Name Type Description Default
password str

plain password (not hashed).

required

Returns:

Type Description
tuple[bool, str | None]

return a tuple with two elements (valid, new_hash). valid: True if password and hash match. new_hash: new hash to replace the one generated with an old algorithm.

Source code in dlunch/auth.py
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
def verify_and_update(self, password: str) -> tuple[bool, str | None]:
    """Check a password against its hash and return `True` if check passes,
    `False` otherwise. Return also a new hash if the original hashing  method
    is superseeded

    Args:
        password (str): plain password (not hashed).

    Returns:
        tuple[bool, str | None]: return a tuple with two elements (valid, new_hash).
            valid: `True` if password and hash match.
            new_hash: new hash to replace the one generated with an old algorithm.
    """
    valid, new_hash = pwd_context.verify_and_update(
        saslprep(password), self.hashed_password
    )
    if valid and new_hash:
        self.hashed_password = new_hash

    return valid, new_hash

add_privileged_user

add_privileged_user(
    user: str, is_admin: bool, config: DictConfig
) -> None

Add user id to privileged_users table.

The table is used by every authentication methods to understand which users are privileged and which ones are guests.

Parameters:

Name Type Description Default
user str

username.

required
is_admin bool

admin flag. Set to True if the new user has admin privileges.

required
config DictConfig

Hydra configuration dictionary.

required
Source code in dlunch/auth.py
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
def add_privileged_user(user: str, is_admin: bool, config: DictConfig) -> None:
    """Add user id to `privileged_users` table.

    The table is used by every authentication methods to understand which users are
    privileged and which ones are guests.

    Args:
        user (str): username.
        is_admin (bool): admin flag.
            Set to `True` if the new user has admin privileges.
        config (DictConfig): Hydra configuration dictionary.
    """
    # Create session
    session = models.create_session(config)
    # New credentials
    new_privileged_user = models.PrivilegedUsers(user=user, admin=is_admin)

    # Update credentials
    # Use an upsert for postgresql, a simple session add otherwise
    models.session_add_with_upsert(
        session=session,
        constraint="privileged_users_pkey",
        new_record=new_privileged_user,
    )
    session.commit()

add_user_hashed_password

add_user_hashed_password(
    user: str, password: str, config: DictConfig
) -> None

Add user credentials to credentials table.

Used only by basic authentication.

Parameters:

Name Type Description Default
user str

username

required
password str

plain password (not hashed).

required
config DictConfig

Hydra configuration dictionary.

required
Source code in dlunch/auth.py
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
def add_user_hashed_password(
    user: str, password: str, config: DictConfig
) -> None:
    """Add user credentials to `credentials` table.

    Used only by basic authentication.

    Args:
        user (str): username
        password (str): plain password (not hashed).
        config (DictConfig): Hydra configuration dictionary.
    """
    # Create session
    session = models.create_session(config)
    # New credentials
    # For the user named "guest" add also the encrypted password so that panel
    # can show the decrypted guest password to logged users
    # Can't use is_guest to determine the user that need encription, because
    # only the user named guest is shown in the guest user password widget
    if user == "guest":
        new_user_credential = models.Credentials(
            user=user, password_hash=password, password_encrypted=password
        )
    else:
        new_user_credential = models.Credentials(
            user=user, password_hash=password
        )

    # Update credentials
    # Use an upsert for postgresql, a simple session add otherwise
    models.session_add_with_upsert(
        session=session,
        constraint="credentials_pkey",
        new_record=new_user_credential,
    )
    session.commit()

authorize

authorize(
    config: DictConfig,
    user_info: dict,
    target_path: str,
    authorize_guest_users=False,
) -> bool

Authorization callback: read config, user info and the target path of the requested resource.

Return True (authorized) or False (not authorized) by checking current user and target path.

Parameters:

Name Type Description Default
config DictConfig

Hydra configuration dictionary.

required
user_info dict

dictionary with user info passed by Panel to the authorization handle.

required
target_path str

path of the requested resource.

required
authorize_guest_users bool

Set to True to enable the main page to guest users. Defaults to False.

False

Returns:

Type Description
bool

authorization flag. True if authorized.

Source code in dlunch/auth.py
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
def authorize(
    config: DictConfig,
    user_info: dict,
    target_path: str,
    authorize_guest_users=False,
) -> bool:
    """Authorization callback: read config, user info and the target path of the
    requested resource.

    Return `True` (authorized) or `False` (not authorized) by checking current user
    and target path.

    Args:
        config (DictConfig): Hydra configuration dictionary.
        user_info (dict): dictionary with user info passed by Panel to the authorization handle.
        target_path (str): path of the requested resource.
        authorize_guest_users (bool, optional): Set to `True` to enable the main page to guest users.
            Defaults to `False`.

    Returns:
        bool: authorization flag. `True` if authorized.
    """

    # If authorization is not active authorize every user
    if not is_auth_active(config=config):
        return True

    # Set current user from panel state
    current_user = pn_user(config)
    privileged_users = list_users(config=config)
    log.debug(f"target path: {target_path}")
    # If user is not authenticated block it
    if not current_user:
        return False
    # All privileged users can reach backend (but the backend will have
    # controls only for admins)
    if current_user in privileged_users:
        return True
    # If the target is the mainpage always authorized (if authenticated)
    if authorize_guest_users and (target_path == "/"):
        return True

    # In all other cases, don't authorize and logout
    pn.state.location.pathname.split("/")[0] + "/logout"
    return False

force_logout

force_logout() -> None

Redirect the browser to the logout endpoint

Source code in dlunch/auth.py
807
808
809
810
811
812
813
def force_logout() -> None:
    """Redirect the browser to the logout endpoint"""
    # Edit pathname to force logout
    pn.state.location.pathname = (
        pn.state.location.pathname.split("/")[0] + "/logout"
    )
    pn.state.location.reload = True

generate_password

generate_password(
    alphabet: str | None = None,
    special_chars: str | None = "",
    length: int = 12,
) -> str

summary

Parameters:

Name Type Description Default
alphabet str | None

list of charachters to use as alphabet to generate the password. Defaults to None.

None
special_chars str | None

special charachters to include inside the password string. Defaults to "".

''
length int

length of the random password. Defaults to 12.

12

Returns:

Type Description
str

random password.

Source code in dlunch/auth.py
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
def generate_password(
    alphabet: str | None = None,
    special_chars: str | None = "",
    length: int = 12,
) -> str:
    """_summary_

    Args:
        alphabet (str | None, optional): list of charachters to use as alphabet to generate the password.
            Defaults to None.
        special_chars (str | None, optional): special charachters to include inside the password string.
            Defaults to "".
        length (int, optional): length of the random password.
            Defaults to 12.

    Returns:
        str: random password.
    """
    # If alphabet is not avilable use a default one
    if alphabet is None:
        alphabet = string.ascii_letters + string.digits + special_chars
    # Infinite loop for finding a valid password
    while True:
        password = "".join(secrets.choice(alphabet) for i in range(length))
        # Create special chars condition only if special chars is non-empty
        if special_chars:
            special_chars_condition = any(c in special_chars for c in password)
        else:
            special_chars_condition = True
        if (
            any(c.islower() for c in password)
            and any(c.isupper() for c in password)
            and any(c.isdigit() for c in password)
            and special_chars_condition
        ):
            break

    return password

get_hash_from_user

get_hash_from_user(
    user: str, config: DictConfig
) -> PasswordHash | None

Query the database to retrieve the hashed password for a certain user.

Parameters:

Name Type Description Default
user str

username.

required
config DictConfig

Hydra configuration dictionary.

required

Returns:

Type Description
PasswordHash | None

returns password object if the user exist, None otherwise.

Source code in dlunch/auth.py
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
def get_hash_from_user(user: str, config: DictConfig) -> PasswordHash | None:
    """Query the database to retrieve the hashed password for a certain user.

    Args:
        user (str): username.
        config (DictConfig): Hydra configuration dictionary.

    Returns:
        PasswordHash | None: returns password object if the user exist, `None` otherwise.
    """
    # Create session
    session = models.create_session(config)
    # Load user from database
    with session:
        user_credential = session.get(models.Credentials, user)

    # Get the hashed password
    if user_credential:
        hash = user_credential.password_hash or None
    else:
        hash = None

    return hash

is_admin

is_admin(user: str, config: DictConfig) -> bool

Check if a user is an admin by checking the privileged_users table

Parameters:

Name Type Description Default
user str

username.

required
config DictConfig

Hydra configuration dictionary.

required

Returns:

Type Description
bool

admin flag. True if the user is an admin.

Source code in dlunch/auth.py
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
def is_admin(user: str, config: DictConfig) -> bool:
    """Check if a user is an admin by checking the `privileged_users` table

    Args:
        user (str): username.
        config (DictConfig): Hydra configuration dictionary.

    Returns:
        bool: admin flag. `True` if the user is an admin.
    """

    # If authorization is not active always return false (ther is no admin)
    if not is_auth_active(config=config):
        return False

    # Create session
    session = models.create_session(config)

    with session:
        admin_users = session.scalars(
            select(models.PrivilegedUsers).where(
                models.PrivilegedUsers.admin == sql_true()
            )
        ).all()
    admin_list = [u.user for u in admin_users]

    is_admin = user in admin_list

    return is_admin

is_auth_active

is_auth_active(config: DictConfig) -> bool

Check configuration dictionary and return True if basic authentication or OAuth is active. Return False otherwise.

Parameters:

Name Type Description Default
config DictConfig

Hydra configuration dictionary.

required

Returns:

Type Description
bool

True if authentication (basic or OAuth) is active, False otherwise.

Source code in dlunch/auth.py
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
def is_auth_active(config: DictConfig) -> bool:
    """Check configuration dictionary and return `True` if basic authentication or OAuth is active.
    Return `False` otherwise.

    Args:
        config (DictConfig): Hydra configuration dictionary.

    Returns:
        bool: `True` if authentication (basic or OAuth) is active, `False` otherwise.
    """

    # Check if a valid auth key exists
    auth_provider = is_basic_auth_active(config=config)
    oauth_provider = config.server.get("oauth_provider", None)

    return auth_provider or oauth_provider

is_basic_auth_active

is_basic_auth_active(config: DictConfig) -> bool

Check config object and return True if basic authentication is active. Return False otherwise.

Parameters:

Name Type Description Default
config DictConfig

Hydra configuration dictionary.

required

Returns:

Type Description
bool

True if basic authentication is active, False otherwise.

Source code in dlunch/auth.py
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
def is_basic_auth_active(config: DictConfig) -> bool:
    """Check config object and return `True` if basic authentication is active.
    Return `False` otherwise.

    Args:
        config (DictConfig): Hydra configuration dictionary.

    Returns:
        bool: `True` if basic authentication is active, `False` otherwise.
    """

    # Check if a valid auth key exists
    auth_provider = config.get("basic_auth", None)

    return auth_provider is not None

is_guest

is_guest(
    user: str,
    config: DictConfig,
    allow_override: bool = True,
) -> bool

Check if a user is a guest by checking if it is listed inside the privileged_users table.

The guest override chached value (stored in flags table, on a per-user basis) can force the function to always return True.

If allow_override is set to False the guest override value is ignored.

Parameters:

Name Type Description Default
user str

username.

required
config DictConfig

Hydra configuration dictionary.

required
allow_override bool

override enablement flag, set to False to ignore guest override value. Defaults to True.

True

Returns:

Type Description
bool

guest flag. True if the user is a guest.

Source code in dlunch/auth.py
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
def is_guest(
    user: str, config: DictConfig, allow_override: bool = True
) -> bool:
    """Check if a user is a guest by checking if it is listed inside the `privileged_users` table.

    The guest override chached value (stored in `flags` table, on a per-user basis) can force
    the function to always return True.

    If `allow_override` is set to `False` the guest override value is ignored.

    Args:
        user (str): username.
        config (DictConfig): Hydra configuration dictionary.
        allow_override (bool, optional): override enablement flag, set to `False` to ignore guest override value.
            Defaults to True.

    Returns:
        bool: guest flag. `True` if the user is a guest.
    """

    # If authorization is not active always return false (user is not guest)
    if not is_auth_active(config=config):
        return False

    # Load guest override from flag table (if the button is pressed its value
    # is True). If not available use False.
    guest_override = models.get_flag(
        config=config,
        id=f"{pn_user(config)}_guest_override",
        value_if_missing=False,
    )

    # If guest override is active always return true (user act like guest)
    if guest_override and allow_override:
        return True

    # Otherwise check if user is not included in privileged users
    privileged_users = list_users(config)

    is_guest = user not in privileged_users

    return is_guest

list_users

list_users(config: DictConfig) -> list[str]

List only privileged users (from privileged_users table).

Parameters:

Name Type Description Default
config DictConfig

Hydra configuration dictionary.

required

Returns:

Type Description
list[str]

list of usernames.

Source code in dlunch/auth.py
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
def list_users(config: DictConfig) -> list[str]:
    """List only privileged users (from `privileged_users` table).

    Args:
        config (DictConfig): Hydra configuration dictionary.

    Returns:
        list[str]: list of usernames.
    """
    # Create session
    session = models.create_session(config)

    with session:
        privileged_users = session.scalars(
            select(models.PrivilegedUsers)
        ).all()

    # Return users
    users_list = [u.user for u in privileged_users]
    users_list.sort()

    return users_list

list_users_guests_and_privileges

list_users_guests_and_privileges(
    config: DictConfig,
) -> DataFrame

Join privileged_users and credentials tables to list normal users, admins and guests.

credentials table is populated only if basic authetication is active (in configuration files). A is considered a guest if it is not listed in privileged_users table but it is available in credentials table.

Returns a dataframe.

Parameters:

Name Type Description Default
config DictConfig

Hydra configuration dictionary.

required

Returns:

Type Description
DataFrame

dataframe with users and privileges.

Source code in dlunch/auth.py
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
def list_users_guests_and_privileges(config: DictConfig) -> pd.DataFrame:
    """Join `privileged_users` and `credentials` tables to list normal users,
    admins and guests.

    `credentials` table is populated only if basic authetication is active (in configuration files).
    A is considered a guest if it is not listed in `privileged_users` table
    but it is available in `credentials` table.

    Returns a dataframe.

    Args:
        config (DictConfig): Hydra configuration dictionary.

    Returns:
        pd.DataFrame: dataframe with users and privileges.
    """

    # Query tables required to understand users and guests
    df_privileged_users = models.PrivilegedUsers.read_as_df(
        config=config,
        index_col="user",
    )
    df_credentials = models.Credentials.read_as_df(
        config=config,
        index_col="user",
    )
    # Change admin column to privileges (used after join)
    df_privileged_users["group"] = df_privileged_users.admin.map(
        {True: "admin", False: "user"}
    )
    df_user_guests_privileges = df_privileged_users.join(
        df_credentials, how="outer"
    )[["group"]]
    df_user_guests_privileges = df_user_guests_privileges.fillna("guest")

    return df_user_guests_privileges

open_backend

open_backend() -> None

Redirect the browser to the backend endpoint

Source code in dlunch/auth.py
798
799
800
801
802
803
804
def open_backend() -> None:
    """Redirect the browser to the backend endpoint"""
    # Edit pathname to open backend
    pn.state.location.pathname = (
        pn.state.location.pathname.split("/")[0] + "/backend"
    )
    pn.state.location.reload = True

pn_user

pn_user(config: DictConfig) -> str

Return the user from Panel state object.

If config.auth.remove_email_domain is True, remove the email domain from username.

Parameters:

Name Type Description Default
config DictConfig

Hydra configuration dictionary.

required

Returns:

Type Description
str

username.

Source code in dlunch/auth.py
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
def pn_user(config: DictConfig) -> str:
    """Return the user from Panel state object.

    If `config.auth.remove_email_domain` is `True`, remove the email domain from username.

    Args:
        config (DictConfig): Hydra configuration dictionary.

    Returns:
        str: username.
    """
    # Store user
    user = pn.state.user

    if user:
        # Check if username is an email
        if re.fullmatch(r"[^@]+@[^@]+\.[^@]+", user):
            # Remove domain from username
            if config.auth.remove_email_domain:
                user = user.split("@")[0]

    return user

remove_user

remove_user(user: str, config: DictConfig) -> dict

Remove user from the database.

User is removed from privileged_users and credentials tables.

Parameters:

Name Type Description Default
user str

username.

required
config DictConfig

Hydra configuration dictionary.

required

Returns:

Type Description
dict

dictionary with privileged_users_deleted and credentials_deleted with deleted rows from each table.

Source code in dlunch/auth.py
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
def remove_user(user: str, config: DictConfig) -> dict:
    """Remove user from the database.

    User is removed from `privileged_users` and `credentials` tables.

    Args:
        user (str): username.
        config (DictConfig): Hydra configuration dictionary.

    Returns:
        dict: dictionary with `privileged_users_deleted` and `credentials_deleted`
            with deleted rows from each table.
    """
    # Create session
    session = models.create_session(config)

    with session:
        # Delete user from privileged_users table
        privileged_users_deleted = session.execute(
            delete(models.PrivilegedUsers).where(
                models.PrivilegedUsers.user == user
            )
        )
        session.commit()

        # Delete user from credentials table
        credentials_deleted = session.execute(
            delete(models.Credentials).where(models.Credentials.user == user)
        )
        session.commit()

    return {
        "privileged_users_deleted": privileged_users_deleted.rowcount,
        "credentials_deleted": credentials_deleted.rowcount,
    }

set_app_auth_and_encryption

set_app_auth_and_encryption(config: DictConfig) -> None

Setup Panel authorization and encryption.

Parameters:

Name Type Description Default
config DictConfig

Hydra configuration dictionary.

required

Raises:

Type Description
ImportError

missing library (cryptography).

Source code in dlunch/auth.py
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
def set_app_auth_and_encryption(config: DictConfig) -> None:
    """Setup Panel authorization and encryption.

    Args:
        config (DictConfig): Hydra configuration dictionary.

    Raises:
        ImportError: missing library (cryptography).
    """
    try:
        if config.auth.oauth_encryption_key:
            try:
                from cryptography.fernet import Fernet
            except ImportError:
                raise ImportError(
                    "Using Data-Lunch authentication requires the "
                    "cryptography library. Install it with `pip install "
                    "cryptography` or `conda install cryptography`."
                )
            pn.config.oauth_encryption_key = (
                config.auth.oauth_encryption_key.encode("ascii")
            )
            pn.state.encryption = Fernet(pn.config.oauth_encryption_key)
    except ConfigAttributeError:
        log.warning(
            "missing authentication encryption key, generate a key with the `panel oauth-secret` CLI command and then provide it to hydra using the DATA_LUNCH_OAUTH_ENC_KEY environment variable"
        )
    # Cookie expiry date
    try:
        if config.auth.oauth_expiry:
            pn.config.oauth_expiry = config.auth.oauth_expiry
    except ConfigAttributeError:
        log.warning(
            "missing explicit authentication expiry date for cookies, defaults to 1 day"
        )