Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
odesenfans committed Jul 11, 2023
1 parent f0248b4 commit 4447577
Show file tree
Hide file tree
Showing 5 changed files with 146 additions and 77 deletions.
6 changes: 0 additions & 6 deletions AUTHORS.rst
Original file line number Diff line number Diff line change
@@ -1,6 +0,0 @@
============
Contributors
============

* Moshe Malawach <[email protected]>
* Hugo Herter <[email protected]>
22 changes: 12 additions & 10 deletions src/aleph/db/models/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,15 @@ class BasePermissionDb(Base):
granted_by: str = Column(String, nullable=False, index=True)
address: str = Column(String, nullable=False)
type: str = Column(String, nullable=False)
channel: Optional[Channel] = Column(String, nullable=True)
valid_from: Optional[dt.datetime] = Column(TIMESTAMP(timezone=True), nullable=False)
valid_until: Optional[dt.datetime] = Column(TIMESTAMP(timezone=True), nullable=True)
channel: Optional[Channel] = Column(String, nullable=True)
expires: Optional[dt.datetime] = Column(TIMESTAMP(timezone=True), nullable=True)

__mapper_args__: Dict[str, Any] = {
"polymorphic_on": type,
}
children: List["BasePermissionDb"] = relationship(
delegations: List["BasePermissionDb"] = relationship(
"BasePermissionDb",
secondary=delegations_table,
primaryjoin=id == delegations_table.c.parent,
Expand All @@ -64,12 +65,12 @@ def is_equivalent_to(self, other: "BasePermissionDb") -> bool:
Returns whether the permission `other` is equal to this one, ignoring validity ranges.
"""
return (
self.type == other.type
and self.owner == other.owner
and self.granted_by == other.granted_by
and self.address == other.address
and self.channel == other.channel
and self.valid_until == other.valid_until
self.type == other.type
and self.owner == other.owner
and self.granted_by == other.granted_by
and self.address == other.address
and self.channel == other.channel
and self.valid_until == other.valid_until
)

def is_subset(self, other: "BasePermissionDb") -> bool:
Expand All @@ -88,8 +89,9 @@ def __hash__(self):
self.type,
self.address,
self.granted_by,
self.valid_from,
self.valid_until,
# TODO: should we exclude these from __hash__ or use HashWrapper?
# self.valid_from,
# self.valid_until,
self.channel,
)
)
Expand Down
176 changes: 117 additions & 59 deletions src/aleph/handlers/content/permission.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,51 +8,44 @@
- handle garbage collection of unused hashes
"""

import asyncio
import logging
from typing import List, Optional, Set, Tuple

import aioipfs
from aioipfs import NotPinnedError
from aioipfs.api import RepoAPI
from aleph_message.models import ItemType, StoreContent, ItemHash

from aleph.config import get_config
from aleph.db.accessors.files import (
delete_file as delete_file_db,
insert_message_file_pin,
get_file_tag,
upsert_file_tag,
delete_file_pin,
refresh_file_tag,
is_pinned_file,
get_message_file_pin,
upsert_file,
)
from copy import deepcopy
from typing import List, Set, Tuple

from aleph.db.accessors.permissions import (
has_delegation_permission,
get_permissions,
expire_permissions,
)
from aleph.db.models import MessageDb, BasePermissionDb
from aleph.exceptions import AlephStorageException, UnknownHashError
from aleph.db.models import (
MessageDb,
BasePermissionDb,
DelegationPermissionDb,
PostPermissionDb,
ExecutablePermissionDb,
AggregatePermissionDb,
StorePermissionDb,
)
from aleph.handlers.content.content_handler import ContentHandler
from aleph.schemas.permissions import PermissionContent, DelegationPermission
from aleph.schemas.permissions import (
PermissionContent,
DelegationPermission,
Permission,
PostPermission,
AggregatePermission,
StorePermission,
ExecutablePermission,
CrudPermission,
)
from aleph.storage import StorageService
from aleph.toolkit.timestamp import timestamp_to_datetime
from aleph.types.db_session import DbSession
from aleph.types.files import FileTag, FileType
from aleph.types.message_status import (
PermissionDenied,
FileUnavailable,
InvalidMessageFormat,
StoreRefNotFound,
StoreCannotUpdateStoreWithRef,
CannotForgetForgetMessage,
CannotForgetPermissionMessage,
PermissionCannotDelegateDelegation,
)
from aleph.utils import item_type_from_hash

LOGGER = logging.getLogger(__name__)

Expand All @@ -66,27 +59,81 @@ def _get_permission_content(message: MessageDb) -> PermissionContent:
return content


def _get_permission_diff(
current_permissions: Set[BasePermissionDb], new_permissions: Set[BasePermissionDb]
) -> Tuple[Set[BasePermissionDb], Set[BasePermissionDb]]:
PERMISSION_TYPE_MAP = {
AggregatePermission: AggregatePermissionDb,
DelegationPermission: DelegationPermissionDb,
ExecutablePermission: ExecutablePermissionDb,
PostPermission: PostPermissionDb,
StorePermission: StorePermissionDb,
}

permissions_to_keep = set()
permissions_with_new_validity_range = dict()
permissions_to_add = set()
permissions_to_expire = set()

# Remove identical permissions
for new_permission in new_permissions:
for current_permission in current_permissions:
if new_permission.is_equivalent_to(current_permission):
if new_permission.valid_until == current_permission.valid_until:
permissions_to_keep.add(current_permission)
else:
permissions_with_new_validity_range[
current_permission.id
] = new_permission.valid_until
def map_permission_to_db(
permission: Permission,
content: PermissionContent,
address: str,
message: MessageDb,
) -> BasePermissionDb:
db_model_args = {
"owner": content.address,
"granted_by": message.sender,
"address": address,
"channel": message.channel,
"valid_from": timestamp_to_datetime(content.time),
"valid_until": None,
"expires": None,
}

if isinstance(permission, CrudPermission):
db_model_args["create"] = permission.create
db_model_args["update"] = permission.update
db_model_args["delete"] = permission.delete
db_model_args["refs"] = permission.refs
db_model_args["addresses"] = permission.addresses

if isinstance(permission, PostPermission):
db_model_args["post_types"] = permission.post_types

return PERMISSION_TYPE_MAP[type(permission)](**db_model_args)


PermissionSet = Set[BasePermissionDb]

return permissions_to_expire, permissions_to_add

def get_permission_diff(
current_permissions: PermissionSet, new_permissions: PermissionSet
) -> Tuple[PermissionSet, PermissionSet, PermissionSet]:

# Remove identical permissions
permissions_to_add = new_permissions - current_permissions
permissions_to_keep = current_permissions & new_permissions
permissions_to_expire = current_permissions - new_permissions

return permissions_to_keep, permissions_to_add, permissions_to_expire


def make_new_delegated_permission(
delegated_permission: BasePermissionDb, replaced_by: BasePermissionDb
) -> BasePermissionDb:
new_delegated_permission = deepcopy(replaced_by)
new_delegated_permission.address = delegated_permission.address
new_delegated_permission.granted_by = delegated_permission.granted_by
return new_delegated_permission


def update_delegated_permissions(
permissions_to_expire: PermissionSet, new_permissions: PermissionSet
) -> None:
for current_permission in permissions_to_expire:
for new_permission in new_permissions:
if new_permission.is_subset(current_permission):
updated_delegations = [
make_new_delegated_permission(
delegated_permission=delegation, replaced_by=new_permission
)
for delegation in current_permission.delegations
]
new_permission.delegations += updated_delegations


class PermissionMessageHandler(ContentHandler):
Expand Down Expand Up @@ -150,19 +197,34 @@ async def process_permission_message(self, session: DbSession, message: MessageD
datetime=message_datetime,
)

new_permissions_set = set(
map_permission_to_db(
permission=permission,
content=content,
address=address,
message=message,
)
for permission in permissions
)

# Isolate new permissions (diff DB vs message).
expired_permissions, new_permissions = _get_permission_diff(
current_permissions, permissions
(
permissions_to_keep,
permissions_to_add,
permissions_to_expire,
) = get_permission_diff(
current_permissions=set(current_permissions),
new_permissions=new_permissions_set,
)

# Nothing to do, move on to the next address.
if not (expired_permissions or new_permissions):
if not (permissions_to_expire or permissions_to_add):
continue

# If message deletes delegation permission, expire all delegations.
if any(
isinstance(permission, DelegationPermission)
for permission in expired_permissions
for permission in permissions_to_expire
):
...
# expire_permissions(session=session, address=)
Expand All @@ -179,14 +241,10 @@ async def process_permission_message(self, session: DbSession, message: MessageD
on_behalf_of=on_behalf_of,
datetime=message_datetime,
):
if any(
isinstance(permission, DelegationPermission)
for permission in permissions
):
# Do nothing? We need to check if the permissions granted to the address are still
# a superset of all the permissions that are delegated to the address, or expire them
# if that's not the case.
...
update_delegated_permissions(
permissions_to_expire=permissions_to_expire,
new_permissions=permissions_to_add,
)

expire_permissions(
session=session,
Expand Down
2 changes: 0 additions & 2 deletions src/aleph/schemas/permissions.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import datetime as dt
from typing import Optional, List, Literal, Dict, Annotated, Union

from aleph_message.models import BaseContent
Expand All @@ -10,7 +9,6 @@
class BasePermission(BaseModel):
# Discriminator field for the different permission types.
type: str
valid_until: Optional[dt.datetime] = None
channel: Optional[Channel] = None


Expand Down
17 changes: 17 additions & 0 deletions src/aleph/toolkit/hash_wrapper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from typing import TypeVar, Callable

T = TypeVar("T")


class HashWrapper:
"""
A wrapper class to use set operations on objects with an alternate implementation
of __hash__ without modifying the original class.
"""

def __init__(self, obj: T, hash_func: Callable[[T], int]):
self.obj = obj
self.hash_func = hash_func

def __hash__(self):
return self.hash_func(self.obj)

0 comments on commit 4447577

Please sign in to comment.