Source code for datamasque.client.models.user
import secrets
import string
from enum import Enum
from typing import NewType, Optional
from pydantic import BaseModel, ConfigDict, Field
UserId = NewType("UserId", int)
GENERATED_PASSWORD_LENGTH = 16
[docs]
class UserRole(Enum):
"""
List of supported user roles.
`ruleset_library_manager` can be optionally included alongside `mask_builder`.
It is not valid as a standalone role.
"""
superuser = "admin"
mask_builder = "mask_builder"
ruleset_library_manager = "ruleset_library_managers"
mask_runner = "mask_runner"
[docs]
class User(BaseModel):
"""Represents a DataMasque user account."""
model_config = ConfigDict(extra="allow", populate_by_name=True)
username: str
email: str
roles: list[UserRole] = Field(alias="user_roles")
id: Optional[UserId] = None
password: Optional[str] = Field(default=None, exclude=True)
[docs]
@staticmethod
def generate_password() -> str:
"""
Generates a password suitable for DataMasque authentication.
The password consists of 16 characters
without the same character occurring three times in a row
and without any three consecutive characters forming an increasing or decreasing sequence.
"""
def is_sequential(s: str) -> bool:
"""Check if the last three characters are in an increasing or decreasing sequence."""
if len(s) < 3:
return False
return (ord(s[-1]) == ord(s[-2]) + 1 == ord(s[-3]) + 2) or (ord(s[-1]) == ord(s[-2]) - 1 == ord(s[-3]) - 2)
chars = string.ascii_letters + string.digits
result = secrets.choice(chars)
while len(result) < GENERATED_PASSWORD_LENGTH:
next_char = secrets.choice(chars)
if len(result) >= 2 and next_char == result[-1] == result[-2]:
continue
if is_sequential(result + next_char):
continue
result += next_char
return result
def __str__(self) -> str:
return self.username