import hashlib
from datetime import datetime, timedelta
import bcrypt
from timApp.auth.accesstype import AccessType
from timApp.auth.auth_models import AccessTypeModel, BlockAccess
from timApp.document.docinfo import DocInfo
from timApp.document.specialnames import TEMPLATE_FOLDER_NAME
from timApp.folder.folder import Folder
from timApp.item.block import BlockType, Block
from timApp.item.item import ItemBase
from timApp.timdb.exceptions import TimDbException
from timApp.user.special_group_names import ANONYMOUS_GROUPNAME, ANONYMOUS_USERNAME
from timApp.user.usergroup import UserGroup
from timApp.util.utils import get_current_time
ANON_USER_ID: int | None = None
LOGGED_USER_ID: int | None = None
ANON_GROUP_ID: int | None = None
LOGGED_GROUP_ID: int | None = None
ADMIN_GROUP_ID: int | None = None
KORPPI_GROUP_ID: int | None = None
DOC_DEFAULT_RIGHT_NAME = "DefaultDocumentRights"
FOLDER_DEFAULT_RIGHT_NAME = "DefaultFolderRights"
access_type_map: dict[str, int] = {}
default_right_paths = {
BlockType.Document: f"{TEMPLATE_FOLDER_NAME}/{DOC_DEFAULT_RIGHT_NAME}",
BlockType.Folder: f"{TEMPLATE_FOLDER_NAME}/{FOLDER_DEFAULT_RIGHT_NAME}",
}
[docs]class NoSuchUserException(TimDbException):
def __init__(self, user_id: int) -> None:
super().__init__(f"No such user: {user_id}")
self.user_id = user_id
[docs]class DeletedUserException(Exception):
pass
[docs]def get_anon_group_id() -> int:
global ANON_GROUP_ID
if ANON_GROUP_ID is not None:
return ANON_GROUP_ID
ANON_GROUP_ID = get_usergroup_by_name(ANONYMOUS_GROUPNAME)
assert ANON_GROUP_ID is not None
return ANON_GROUP_ID
[docs]def get_anon_user_id() -> int:
global ANON_USER_ID
if ANON_USER_ID is not None:
return ANON_USER_ID
ANON_USER_ID = get_user_id_by_name(ANONYMOUS_USERNAME)
assert ANON_USER_ID is not None
return ANON_USER_ID
[docs]def get_access_type_id(access_type: str) -> int:
if not access_type_map:
result = AccessTypeModel.query.all()
for row in result:
access_type_map[row.name] = row.id
return access_type_map[access_type]
[docs]def grant_access(
group: UserGroup,
block: ItemBase | Block,
access_type: AccessType,
accessible_from: datetime | None = None,
accessible_to: datetime | None = None,
duration_from: datetime | None = None,
duration_to: datetime | None = None,
duration: timedelta | None = None,
require_confirm: bool | None = None,
replace_active_duration: bool = True,
) -> BlockAccess:
"""Grants access to a group for a block.
:param require_confirm: Whether this access needs to be later confirmed by someone with manage access.
:param duration_from: The optional start time for duration unlock.
:param duration_to: The optional end time for duration unlock.
:param accessible_from: The optional start time for the permission.
:param accessible_to: The optional end time for the permission.
:param duration: The optional duration for the permission.
:param group: The group to which to grant view access.
:param block: The block for which to grant view access.
:param access_type: The kind of access. Possible values are listed in accesstype table.
:param replace_active_duration: If true, replaces any existing access with the new one.
If false and the access being granted is duration, modifies the end time of the permission
to account for new duration.
:return: The BlockAccess object.
"""
if accessible_from is None and duration is None and not require_confirm:
# the delta is to ease testing; the clocks of container and PostgreSQL are not perfectly in sync
accessible_from = get_current_time() - timedelta(milliseconds=50)
access_id = access_type.value
block = block if isinstance(block, Block) else block.block
key = (block.id, access_id)
b = group.accesses_alt.get(key)
if b:
if (
not replace_active_duration
and not b.expired
and not b.unlockable
and b.accessible_from is not None
and duration is not None
):
b.accessible_to = b.accessible_from + duration
if duration_to:
b.accessible_to = min(b.accessible_to, duration_to)
return b
b.accessible_from = accessible_from
b.accessible_to = accessible_to
b.duration = duration
b.duration_from = duration_from
b.duration_to = duration_to
b.require_confirm = require_confirm
return b
ba = BlockAccess(
block_id=block.id,
type=access_id,
accessible_from=accessible_from,
accessible_to=accessible_to,
duration_from=duration_from,
duration_to=duration_to,
duration=duration,
require_confirm=require_confirm,
)
group.accesses_alt[key] = ba
return ba
[docs]def get_usergroup_by_name(name: str) -> int | None:
from timApp.user.usergroup import UserGroup
ug = UserGroup.get_by_name(name)
if ug:
return ug.id
return None
[docs]def get_user_id_by_name(name: str) -> int | None:
"""Gets the id of the specified username.
:param name: The name of the user.
:returns: The id of the user or None if the user does not exist.
"""
from timApp.user.user import User
u = User.get_by_name(name)
if u:
return u.id
return None
[docs]def get_or_create_default_right_document(
folder: Folder,
object_type: BlockType,
) -> DocInfo:
d = get_default_right_document(folder, object_type, create_if_not_exist=True)
assert d is not None
return d
[docs]def is_some_default_right_document(doc: DocInfo) -> bool:
for r in default_right_paths.values():
if doc.path.endswith(r):
return True
return False
[docs]def get_default_right_document(
folder: Folder,
object_type: BlockType,
create_if_not_exist: bool = False,
) -> DocInfo | None:
right_doc_path = default_right_paths.get(object_type)
if right_doc_path is None:
raise TimDbException(f"Unsupported object type: {object_type}")
# we don't want to have an owner in the default rights by default
doc = folder.get_document(
right_doc_path, create_if_not_exist=create_if_not_exist, creator_group=None
)
return doc
[docs]def grant_default_access(
groups: list[UserGroup],
folder: Folder,
access_type: AccessType,
object_type: BlockType,
accessible_from: datetime | None = None,
accessible_to: datetime | None = None,
duration_from: datetime | None = None,
duration_to: datetime | None = None,
duration: timedelta | None = None,
) -> list[BlockAccess]:
doc = get_or_create_default_right_document(folder, object_type)
accesses = []
for group in groups:
accesses.append(
grant_access(
group,
doc,
access_type,
accessible_from=accessible_from,
accessible_to=accessible_to,
duration_from=duration_from,
duration_to=duration_to,
duration=duration,
)
)
return accesses
[docs]def hash_password_old(password: str) -> str:
return hashlib.sha256(password.encode()).hexdigest()
[docs]def create_password_hash(password: str) -> str:
h = bcrypt.hashpw(password.encode(), bcrypt.gensalt()).decode()
return h
[docs]def check_password_hash(password: str, password_hash: str) -> bool:
try:
is_bcrypt_ok = bcrypt.checkpw(password.encode(), password_hash.encode())
except ValueError:
is_bcrypt_ok = False
return is_bcrypt_ok
[docs]def check_password_hash_old(password: str, password_hash: str) -> bool:
return password_hash == hash_password_old(password)