async def finish_reencryption_maintenance( self, organization_id: OrganizationID, author: DeviceID, realm_id: RealmID, encryption_revision: int, ) -> None: realm = self._get_realm(organization_id, realm_id) if realm.roles.get(author.user_id) != RealmRole.OWNER: raise RealmAccessError() if not realm.status.in_maintenance: raise RealmNotInMaintenanceError( f"Realm `{realm_id}` not under maintenance") if encryption_revision != realm.status.encryption_revision: raise RealmEncryptionRevisionError("Invalid encryption revision") if not self._vlob_component._maintenance_reencryption_is_finished_hook( organization_id, realm_id, encryption_revision): raise RealmMaintenanceError("Reencryption operations are not over") realm.status = RealmStatus( maintenance_type=None, maintenance_started_on=None, maintenance_started_by=None, encryption_revision=encryption_revision, ) await self._send_event( BackendEvent.REALM_MAINTENANCE_FINISHED, organization_id=organization_id, author=author, realm_id=realm_id, encryption_revision=encryption_revision, )
async def query_get_stats( conn, organization_id: OrganizationID, author: DeviceID, realm_id: UUID ) -> RealmStats: ret = await conn.fetchrow( *_q_has_realm_access( organization_id=organization_id, realm_id=realm_id, user_id=author.user_id ) ) if not ret: raise RealmNotFoundError(f"Realm `{realm_id}` doesn't exist") if not ret["has_access"]: raise RealmAccessError() blocks_size = await conn.fetch( *_q_get_blocks_size_from_realm(organization_id=organization_id, realm_id=realm_id) ) vlobs_size = await conn.fetch( *_q_get_vlob_size_from_realm(organization_id=organization_id, realm_id=realm_id) ) RealmStats.blocks_size = 0 RealmStats.vlobs_size = 0 if "sum" in blocks_size[0] and blocks_size[0]["sum"] is not None: RealmStats.blocks_size = blocks_size[0]["sum"] if "sum" in vlobs_size[0] and vlobs_size[0]["sum"] is not None: RealmStats.vlobs_size = vlobs_size[0]["sum"] return RealmStats
async def get_status( self, organization_id: OrganizationID, author: DeviceID, realm_id: UUID ) -> RealmStatus: realm = self._get_realm(organization_id, realm_id) if author.user_id not in realm.roles: raise RealmAccessError() return realm.status
async def update_roles( self, organization_id: OrganizationID, new_role: RealmGrantedRole, recipient_message: Optional[bytes] = None, ) -> None: assert new_role.granted_by.user_id != new_role.user_id try: user = self._user_component._get_user(organization_id, new_role.user_id) except UserNotFoundError: raise RealmNotFoundError(f"User `{new_role.user_id}` doesn't exist") if user.profile == UserProfile.OUTSIDER and new_role.role in ( RealmRole.MANAGER, RealmRole.OWNER, ): raise RealmIncompatibleProfileError( "User with OUTSIDER profile cannot be MANAGER or OWNER" ) realm = self._get_realm(organization_id, new_role.realm_id) if realm.status.in_maintenance: raise RealmInMaintenanceError("Data realm is currently under maintenance") owner_only = (RealmRole.OWNER,) owner_or_manager = (RealmRole.OWNER, RealmRole.MANAGER) existing_user_role = realm.roles.get(new_role.user_id) if existing_user_role in owner_or_manager or new_role.role in owner_or_manager: needed_roles = owner_only else: needed_roles = owner_or_manager author_role = realm.roles.get(new_role.granted_by.user_id) if author_role not in needed_roles: raise RealmAccessError() if existing_user_role == new_role.role: raise RealmRoleAlreadyGranted() realm.granted_roles.append(new_role) await self._send_event( "realm.roles_updated", organization_id=organization_id, author=new_role.granted_by, realm_id=new_role.realm_id, user=new_role.user_id, role=new_role.role, ) if recipient_message is not None: await self._message_component.send( organization_id, new_role.granted_by, new_role.user_id, new_role.granted_on, recipient_message, )
async def get_role_certificates(self, organization_id: OrganizationID, author: DeviceID, realm_id: RealmID) -> List[bytes]: realm = self._get_realm(organization_id, realm_id) if author.user_id not in realm.roles: raise RealmAccessError() return [x.certificate for x in realm.granted_roles]
async def query_get_role_certificates( conn, organization_id: OrganizationID, author: DeviceID, realm_id: UUID, since: pendulum.Pendulum, ) -> List[bytes]: ret = await conn.fetch(_q_get_role_certificates, organization_id, realm_id) if not ret: # Existing group must have at least one owner user raise RealmNotFoundError(f"Realm `{realm_id}` doesn't exist") out = [] author_current_role = None for user_id, role, certif, certified_on in ret: if not since or certified_on > since: out.append(certif) if user_id == author.user_id: author_current_role = role if author_current_role is None: raise RealmAccessError() return out
async def query_start_reencryption_maintenance( conn, organization_id: OrganizationID, author: DeviceID, realm_id: RealmID, encryption_revision: int, per_participant_message: Dict[UserID, bytes], timestamp: pendulum.DateTime, ) -> None: # Retrieve realm and make sure it is not under maintenance status = await get_realm_status(conn, organization_id, realm_id) if status.in_maintenance: raise RealmInMaintenanceError( f"Realm `{realm_id}` alrealy in maintenance") if encryption_revision != status.encryption_revision + 1: raise RealmEncryptionRevisionError("Invalid encryption revision") roles = await _get_realm_role_for_not_revoked(conn, organization_id, realm_id) if roles.get(author.user_id) != RealmRole.OWNER: raise RealmAccessError() if per_participant_message.keys() ^ roles.keys(): raise RealmParticipantsMismatchError( "Realm participants and message recipients mismatch") await conn.execute(*_q_query_start_reencryption_maintenance_update_realm( organization_id=organization_id.str, realm_id=realm_id.uuid, maintenance_started_by=author.str, maintenance_started_on=timestamp, maintenance_type="REENCRYPTION", encryption_revision=encryption_revision, )) await conn.execute( * _q_query_start_reencryption_maintenance_update_vlob_encryption_revision( organization_id=organization_id.str, realm_id=realm_id.uuid, encryption_revision=encryption_revision, )) await send_signal( conn, BackendEvent.REALM_MAINTENANCE_STARTED, organization_id=organization_id, author=author, realm_id=realm_id, encryption_revision=encryption_revision, ) for recipient, body in per_participant_message.items(): await send_message(conn, organization_id, author, recipient, timestamp, body)
async def start_reencryption_maintenance( self, organization_id: OrganizationID, author: DeviceID, realm_id: RealmID, encryption_revision: int, per_participant_message: Dict[UserID, bytes], timestamp: pendulum.DateTime, ) -> None: realm = self._get_realm(organization_id, realm_id) if realm.roles.get(author.user_id) != RealmRole.OWNER: raise RealmAccessError() if realm.status.in_maintenance: raise RealmInMaintenanceError( f"Realm `{realm_id}` alrealy in maintenance") if encryption_revision != realm.status.encryption_revision + 1: raise RealmEncryptionRevisionError("Invalid encryption revision") now = pendulum.now() not_revoked_roles = set() for user_id in realm.roles.keys(): user = await self._user_component.get_user(organization_id, user_id) if not user.revoked_on or user.revoked_on > now: not_revoked_roles.add(user_id) if per_participant_message.keys() ^ not_revoked_roles: raise RealmParticipantsMismatchError( "Realm participants and message recipients mismatch") realm.status = RealmStatus( maintenance_type=MaintenanceType.REENCRYPTION, maintenance_started_on=timestamp, maintenance_started_by=author, encryption_revision=encryption_revision, ) self._vlob_component._maintenance_reencryption_start_hook( organization_id, realm_id, encryption_revision) # Should first send maintenance event, then message to each participant await self._send_event( BackendEvent.REALM_MAINTENANCE_STARTED, organization_id=organization_id, author=author, realm_id=realm_id, encryption_revision=encryption_revision, ) for recipient, msg in per_participant_message.items(): await self._message_component.send(organization_id, author, recipient, timestamp, msg)
async def get_role_certificates( self, organization_id: OrganizationID, author: DeviceID, realm_id: UUID, since: pendulum.Pendulum, ) -> List[bytes]: realm = self._get_realm(organization_id, realm_id) if author.user_id not in realm.roles: raise RealmAccessError() if since: return [x.certificate for x in realm.granted_roles if x.granted_on > since] else: return [x.certificate for x in realm.granted_roles]
async def get_stats(self, organization_id: OrganizationID, author: DeviceID, realm_id: RealmID) -> RealmStats: realm = self._get_realm(organization_id, realm_id) if author.user_id not in realm.roles: raise RealmAccessError() blocks_size = 0 vlobs_size = 0 for value in self._block_component._blockmetas.values(): if value.realm_id == realm_id: blocks_size += value.size for value in self._vlob_component._vlobs.values(): if value.realm_id == realm_id: vlobs_size += sum(len(blob) for (blob, _, _) in value.data) return RealmStats(blocks_size=blocks_size, vlobs_size=vlobs_size)
async def query_finish_reencryption_maintenance( conn, organization_id: OrganizationID, author: DeviceID, realm_id: RealmID, encryption_revision: int, ) -> None: # Retrieve realm and make sure it is not under maintenance status = await get_realm_status(conn, organization_id, realm_id) roles = await _get_realm_role_for_not_revoked(conn, organization_id, realm_id, [author.user_id]) if roles.get(author.user_id) != RealmRole.OWNER: raise RealmAccessError() if not status.in_maintenance: raise RealmNotInMaintenanceError( f"Realm `{realm_id}` not under maintenance") if encryption_revision != status.encryption_revision: raise RealmEncryptionRevisionError("Invalid encryption revision") # Test reencryption operations are over rep = await conn.fetch(*_query_finish_reencryption_maintenance_get_info( organization_id=organization_id.str, realm_id=realm_id, encryption_revision=encryption_revision, )) try: previous, current = rep except ValueError: raise RealmMaintenanceError("Reencryption operations are not over") assert previous["encryption_revision"] == encryption_revision - 1 assert current["encryption_revision"] == encryption_revision assert previous["count"] >= current["count"] if previous["count"] != current["count"]: raise RealmMaintenanceError("Reencryption operations are not over") await conn.execute(*_query_finish_reencryption_maintenance_update_realm( organization_id=organization_id.str, realm_id=realm_id)) await send_signal( conn, BackendEvent.REALM_MAINTENANCE_FINISHED, organization_id=organization_id, author=author, realm_id=realm_id, encryption_revision=encryption_revision, )
async def query_get_status(conn, organization_id: OrganizationID, author: DeviceID, realm_id: UUID) -> RealmStatus: ret = await conn.fetchrow(_q_get_realm_status, organization_id, realm_id, author.user_id) if not ret: raise RealmNotFoundError(f"Realm `{realm_id}` doesn't exist") if not ret["has_access"]: raise RealmAccessError() return RealmStatus( maintenance_type=STR_TO_REALM_MAINTENANCE_TYPE.get( ret["maintenance_type"]), maintenance_started_on=ret["maintenance_started_on"], maintenance_started_by=ret["maintenance_started_by"], encryption_revision=ret["encryption_revision"], )
async def query_get_stats(conn, organization_id: OrganizationID, author: DeviceID, realm_id: RealmID) -> RealmStats: ret = await conn.fetchrow( *_q_has_realm_access(organization_id=organization_id.str, realm_id=realm_id.uuid, user_id=author.user_id.str)) if not ret: raise RealmNotFoundError(f"Realm `{realm_id}` doesn't exist") if not ret["has_access"]: raise RealmAccessError() blocks_size_rep = await conn.fetchrow(*_q_get_blocks_size_from_realm( organization_id=organization_id.str, realm_id=realm_id.uuid)) vlobs_size_rep = await conn.fetchrow(*_q_get_vlob_size_from_realm( organization_id=organization_id.str, realm_id=realm_id.uuid)) blocks_size = blocks_size_rep["sum"] or 0 vlobs_size = vlobs_size_rep["sum"] or 0 return RealmStats(blocks_size=blocks_size, vlobs_size=vlobs_size)
async def query_get_status(conn, organization_id: OrganizationID, author: DeviceID, realm_id: RealmID) -> RealmStatus: ret = await conn.fetchrow( *_q_get_realm_status(organization_id=organization_id.str, realm_id=realm_id.uuid, user_id=author.user_id.str)) if not ret: raise RealmNotFoundError(f"Realm `{realm_id}` doesn't exist") if not ret["has_access"]: raise RealmAccessError() return RealmStatus( maintenance_type=MaintenanceType(ret["maintenance_type"]) if ret["maintenance_type"] else None, maintenance_started_on=ret["maintenance_started_on"], maintenance_started_by=DeviceID(ret["maintenance_started_by"]) if ret["maintenance_started_by"] else None, encryption_revision=ret["encryption_revision"], )
async def query_get_role_certificates(conn, organization_id: OrganizationID, author: DeviceID, realm_id: RealmID) -> List[bytes]: ret = await conn.fetch(*_q_get_role_certificates( organization_id=organization_id.str, realm_id=realm_id.uuid)) if not ret: # Existing group must have at least one owner user raise RealmNotFoundError(f"Realm `{realm_id}` doesn't exist") out = [] author_current_role = None for user_id, role, certif, _ in ret: user_id = UserID(user_id) out.append(certif) if user_id == author.user_id: author_current_role = role if author_current_role is None: raise RealmAccessError() return out
async def query_start_reencryption_maintenance( conn, organization_id: OrganizationID, author: DeviceID, realm_id: UUID, encryption_revision: int, per_participant_message: Dict[UserID, bytes], timestamp: pendulum.Pendulum, ) -> None: # Retrieve realm and make sure it is not under maintenance rep = await get_realm_status(conn, organization_id, realm_id) if rep["maintenance_type"]: raise RealmInMaintenanceError( f"Realm `{realm_id}` alrealy in maintenance") if encryption_revision != rep["encryption_revision"] + 1: raise RealmEncryptionRevisionError("Invalid encryption revision") roles = await get_realm_role_for_not_revoked(conn, organization_id, realm_id) if per_participant_message.keys() ^ roles.keys(): raise RealmParticipantsMismatchError( "Realm participants and message recipients mismatch") if roles.get(author.user_id) != RealmRole.OWNER: raise RealmAccessError() query = """ UPDATE realm SET encryption_revision=$6, maintenance_started_by=({}), maintenance_started_on=$4, maintenance_type=$5 WHERE _id = ({}) """.format( q_device_internal_id(organization_id=Parameter("$1"), device_id=Parameter("$3")), q_realm_internal_id(organization_id=Parameter("$1"), realm_id=Parameter("$2")), ) await conn.execute(query, organization_id, realm_id, author, timestamp, "REENCRYPTION", encryption_revision) query = """ INSERT INTO vlob_encryption_revision( realm, encryption_revision ) SELECT ({}), $3 """.format( q_realm_internal_id(organization_id=Parameter("$1"), realm_id=Parameter("$2"))) await conn.execute(query, organization_id, realm_id, encryption_revision) await send_signal( conn, "realm.maintenance_started", organization_id=organization_id, author=author, realm_id=realm_id, encryption_revision=encryption_revision, ) for recipient, body in per_participant_message.items(): await send_message(conn, organization_id, author, recipient, timestamp, body)
async def update_roles( self, organization_id: OrganizationID, new_role: RealmGrantedRole, recipient_message: Optional[bytes] = None, ) -> None: assert new_role.granted_by is not None assert new_role.granted_by.user_id != new_role.user_id # The only way for an OUTSIDER to be OWNER is to create his own realm # (given he needs to have one to store it user manifest). try: user = self._user_component._get_user(organization_id, new_role.user_id) except UserNotFoundError: raise RealmNotFoundError( f"User `{new_role.user_id}` doesn't exist") if user.profile == UserProfile.OUTSIDER and new_role.role in ( RealmRole.MANAGER, RealmRole.OWNER, ): raise RealmIncompatibleProfileError( "User with OUTSIDER profile cannot be MANAGER or OWNER") realm = self._get_realm(organization_id, new_role.realm_id) if realm.status.in_maintenance: raise RealmInMaintenanceError( "Data realm is currently under maintenance") owner_only = (RealmRole.OWNER, ) owner_or_manager = (RealmRole.OWNER, RealmRole.MANAGER) existing_user_role = realm.roles.get(new_role.user_id) needed_roles: Tuple[RealmRole, ...] if existing_user_role in owner_or_manager or new_role.role in owner_or_manager: needed_roles = owner_only else: needed_roles = owner_or_manager author_role = realm.roles.get(new_role.granted_by.user_id) if author_role not in needed_roles: raise RealmAccessError() if existing_user_role == new_role.role: raise RealmRoleAlreadyGranted() # Timestamps for the role certificates of a given user should be striclty increasing last_role = realm.get_last_role(new_role.user_id) if last_role is not None and last_role.granted_on >= new_role.granted_on: raise RealmRoleRequireGreaterTimestampError(last_role.granted_on) # Perfrom extra checks when removing write rights if new_role.role in (RealmRole.READER, None): # The change of role needs to occur strictly after the last upload for this user realm_last_vlob_update = self._vlob_component._get_last_vlob_update( organization_id, new_role.realm_id, new_role.user_id) if realm_last_vlob_update is not None and realm_last_vlob_update >= new_role.granted_on: raise RealmRoleRequireGreaterTimestampError( realm_last_vlob_update) # Perform extra checks when removing management rights if new_role.role in (RealmRole.CONTRIBUTOR, RealmRole.READER, None): # The change of role needs to occur strictly after the last change of role performed by this user realm_last_role_change = realm.last_role_change_per_user.get( new_role.user_id) if realm_last_role_change is not None and realm_last_role_change >= new_role.granted_on: raise RealmRoleRequireGreaterTimestampError( realm_last_role_change) # Update role and record last change timestamp for this user realm.granted_roles.append(new_role) author_user_id = new_role.granted_by.user_id current_value = realm.last_role_change_per_user.get(author_user_id) realm.last_role_change_per_user[author_user_id] = ( new_role.granted_on if current_value is None else max( current_value, new_role.granted_on)) await self._send_event( BackendEvent.REALM_ROLES_UPDATED, organization_id=organization_id, author=new_role.granted_by, realm_id=new_role.realm_id, user=new_role.user_id, role=new_role.role, ) if recipient_message is not None: await self._message_component.send( organization_id, new_role.granted_by, new_role.user_id, new_role.granted_on, recipient_message, )
async def query_update_roles( conn, organization_id: OrganizationID, new_role: RealmGrantedRole, recipient_message: Optional[bytes], ) -> None: assert new_role.granted_by is not None if new_role.granted_by.user_id == new_role.user_id: raise RealmAccessError("Cannot modify our own role") # Make sure user profile is compatible rep = await conn.fetchrow(*_q_get_user_profile( organization_id=organization_id, user_id=new_role.user_id)) if not rep: raise RealmNotFoundError(f"User `{new_role.user_id}` doesn't exist") if rep["profile"] == UserProfile.OUTSIDER.value and new_role.role in ( RealmRole.MANAGER, RealmRole.OWNER, ): raise RealmIncompatibleProfileError( "User with OUTSIDER profile cannot be MANAGER or OWNER") # Retrieve realm and make sure it is not under maintenance rep = await conn.fetchrow(*_q_get_realm_status( organization_id=organization_id, realm_id=new_role.realm_id)) if not rep: raise RealmNotFoundError(f"Realm `{new_role.realm_id}` doesn't exist") if rep["maintenance_type"]: raise RealmInMaintenanceError( "Data realm is currently under maintenance") # Check access rights and user existance ((author_id, author_role), (user_id, existing_user_role)) = await conn.fetch(*_q_get_roles( organization_id=organization_id, realm_id=new_role.realm_id, users_ids=(new_role.granted_by.user_id, new_role.user_id), )) assert author_id assert user_id author_role = STR_TO_REALM_ROLE.get(author_role) existing_user_role = STR_TO_REALM_ROLE.get(existing_user_role) owner_only = (RealmRole.OWNER, ) owner_or_manager = (RealmRole.OWNER, RealmRole.MANAGER) needed_roles: Tuple[RealmRole, ...] if existing_user_role in owner_or_manager or new_role.role in owner_or_manager: needed_roles = owner_only else: needed_roles = owner_or_manager if author_role not in needed_roles: raise RealmAccessError() if existing_user_role == new_role.role: raise RealmRoleAlreadyGranted() await conn.execute(*_q_insert_realm_user_role( organization_id=organization_id, realm_id=new_role.realm_id, user_id=new_role.user_id, role=new_role.role.value if new_role.role else None, certificate=new_role.certificate, granted_by=new_role.granted_by, granted_on=new_role.granted_on, )) await send_signal( conn, BackendEvent.REALM_ROLES_UPDATED, organization_id=organization_id, author=new_role.granted_by, realm_id=new_role.realm_id, user=new_role.user_id, role_str=new_role.role.value if new_role.role else None, ) if recipient_message: await send_message( conn, organization_id, new_role.granted_by, new_role.user_id, new_role.granted_on, recipient_message, )
async def query_update_roles( conn, organization_id: OrganizationID, new_role: RealmGrantedRole, recipient_message: Optional[bytes], ) -> None: if new_role.granted_by.user_id == new_role.user_id: raise RealmAccessError("Cannot modify our own role") # Retrieve realm and make sure it is not under maintenance rep = await conn.fetchrow(_q_get_realm_status, organization_id, new_role.realm_id) if not rep: raise RealmNotFoundError(f"Realm `{new_role.realm_id}` doesn't exist") if rep["maintenance_type"]: raise RealmInMaintenanceError("Data realm is currently under maintenance") # Check access rights and user existance ((author_id, author_role), (user_id, existing_user_role)) = await conn.fetch( _q_get_roles, organization_id, new_role.realm_id, (new_role.granted_by.user_id, new_role.user_id), ) assert author_id if not user_id: raise RealmNotFoundError(f"User `{new_role.user_id}` doesn't exist") author_role = STR_TO_REALM_ROLE.get(author_role) existing_user_role = STR_TO_REALM_ROLE.get(existing_user_role) owner_only = (RealmRole.OWNER,) owner_or_manager = (RealmRole.OWNER, RealmRole.MANAGER) if existing_user_role in owner_or_manager or new_role.role in owner_or_manager: needed_roles = owner_only else: needed_roles = owner_or_manager if author_role not in needed_roles: raise RealmAccessError() if existing_user_role == new_role.role: raise RealmRoleAlreadyGranted() await conn.execute( _q_insert_realm_user_role, organization_id, new_role.realm_id, new_role.user_id, new_role.role.value if new_role.role else None, new_role.certificate, new_role.granted_by, new_role.granted_on, ) await send_signal( conn, "realm.roles_updated", organization_id=organization_id, author=new_role.granted_by, realm_id=new_role.realm_id, user=new_role.user_id, role_str=new_role.role.value if new_role.role else None, ) if recipient_message: await send_message( conn, organization_id, new_role.granted_by, new_role.user_id, new_role.granted_on, recipient_message, )
async def query_update_roles( conn, organization_id: OrganizationID, new_role: RealmGrantedRole, recipient_message: Optional[bytes], ) -> None: assert new_role.granted_by is not None if new_role.granted_by.user_id == new_role.user_id: raise RealmAccessError("Cannot modify our own role") # Make sure user profile is compatible rep = await conn.fetchrow(*_q_get_user_profile( organization_id=organization_id.str, user_id=new_role.user_id.str)) if not rep: raise RealmNotFoundError(f"User `{new_role.user_id}` doesn't exist") if rep["profile"] == UserProfile.OUTSIDER.value and new_role.role in ( RealmRole.MANAGER, RealmRole.OWNER, ): raise RealmIncompatibleProfileError( "User with OUTSIDER profile cannot be MANAGER or OWNER") # Retrieve realm and make sure it is not under maintenance rep = await conn.fetchrow(*_q_get_realm_status( organization_id=organization_id.str, realm_id=new_role.realm_id.uuid)) if not rep: raise RealmNotFoundError(f"Realm `{new_role.realm_id}` doesn't exist") if rep["maintenance_type"]: raise RealmInMaintenanceError( "Data realm is currently under maintenance") # Check access rights and user existance ((author_id, author_role), (user_id, existing_user_role)) = await conn.fetch(*_q_get_roles( organization_id=organization_id.str, realm_id=new_role.realm_id.uuid, users_ids=(new_role.granted_by.user_id.str, new_role.user_id.str), )) assert author_id assert user_id if author_role is not None: author_role, _ = author_role if author_role is not None: author_role = RealmRole(author_role) last_role_granted_on = None if existing_user_role is not None: existing_user_role, last_role_granted_on = existing_user_role if existing_user_role is not None: existing_user_role = RealmRole(existing_user_role) owner_only = (RealmRole.OWNER, ) owner_or_manager = (RealmRole.OWNER, RealmRole.MANAGER) needed_roles: Tuple[RealmRole, ...] if existing_user_role in owner_or_manager or new_role.role in owner_or_manager: needed_roles = owner_only else: needed_roles = owner_or_manager if author_role not in needed_roles: raise RealmAccessError() if existing_user_role == new_role.role: raise RealmRoleAlreadyGranted() # Timestamps for the role certificates of a given user should be striclty increasing if last_role_granted_on is not None and last_role_granted_on >= new_role.granted_on: raise RealmRoleRequireGreaterTimestampError(last_role_granted_on) # Perfrom extra checks when removing write rights if new_role.role in (RealmRole.READER, None): # The change of role needs to occur strictly after the last upload for this user rep = await conn.fetchrow(*_q_get_last_vlob_update( organization_id=organization_id.str, realm_id=new_role.realm_id.uuid, user_id=new_role.user_id.str, )) realm_last_vlob_update = None if not rep else rep[0] if realm_last_vlob_update is not None and realm_last_vlob_update >= new_role.granted_on: raise RealmRoleRequireGreaterTimestampError(realm_last_vlob_update) # Perform extra checks when removing management rights if new_role.role in (RealmRole.CONTRIBUTOR, RealmRole.READER, None): # The change of role needs to occur strictly after the last change of role performed by this user rep = await conn.fetchrow(*_q_get_last_role_change( organization_id=organization_id.str, realm_id=new_role.realm_id.uuid, user_id=new_role.user_id.str, )) realm_last_role_change = None if not rep else rep[0] if realm_last_role_change is not None and realm_last_role_change >= new_role.granted_on: raise RealmRoleRequireGreaterTimestampError(realm_last_role_change) await conn.execute(*_q_insert_realm_user_role( organization_id=organization_id.str, realm_id=new_role.realm_id.uuid, user_id=new_role.user_id.str, role=new_role.role.value if new_role.role else None, certificate=new_role.certificate, granted_by=new_role.granted_by.str, granted_on=new_role.granted_on, )) await conn.execute(*_q_set_last_role_change( organization_id=organization_id.str, realm_id=new_role.realm_id.uuid, user_id=new_role.granted_by.user_id.str, granted_on=new_role.granted_on, )) await send_signal( conn, BackendEvent.REALM_ROLES_UPDATED, organization_id=organization_id, author=new_role.granted_by, realm_id=new_role.realm_id, user=new_role.user_id, role=new_role.role, ) if recipient_message: await send_message( conn, organization_id, new_role.granted_by, new_role.user_id, new_role.granted_on, recipient_message, )
async def query_finish_reencryption_maintenance( conn, organization_id: OrganizationID, author: DeviceID, realm_id: UUID, encryption_revision: int, ) -> None: # Retrieve realm and make sure it is not under maintenance rep = await get_realm_status(conn, organization_id, realm_id) roles = await get_realm_role_for_not_revoked(conn, organization_id, realm_id, [author.user_id]) if roles.get(author.user_id) != RealmRole.OWNER: raise RealmAccessError() if not rep["maintenance_type"]: raise RealmNotInMaintenanceError( f"Realm `{realm_id}` not under maintenance") if encryption_revision != rep["encryption_revision"]: raise RealmEncryptionRevisionError("Invalid encryption revision") # Test reencryption operations are over query = """ WITH cte_encryption_revisions AS ( SELECT _id, encryption_revision FROM vlob_encryption_revision WHERE realm = ({}) AND (encryption_revision = $3 OR encryption_revision = $3 - 1) ) SELECT encryption_revision, COUNT(*) as count FROM vlob_atom INNER JOIN cte_encryption_revisions ON cte_encryption_revisions._id = vlob_atom.vlob_encryption_revision GROUP BY encryption_revision ORDER BY encryption_revision """.format( q_realm_internal_id(organization_id=Parameter("$1"), realm_id=Parameter("$2"))) rep = await conn.fetch(query, organization_id, realm_id, encryption_revision) try: previous, current = rep except ValueError: raise RealmMaintenanceError("Reencryption operations are not over") assert previous["encryption_revision"] == encryption_revision - 1 assert current["encryption_revision"] == encryption_revision assert previous["count"] >= current["count"] if previous["count"] != current["count"]: raise RealmMaintenanceError("Reencryption operations are not over") query = """ UPDATE realm SET maintenance_started_by=NULL, maintenance_started_on=NULL, maintenance_type=NULL WHERE _id = ({}) """.format( q_realm_internal_id(organization_id=Parameter("$1"), realm_id=Parameter("$2"))) await conn.execute(query, organization_id, realm_id) await send_signal( conn, "realm.maintenance_finished", organization_id=organization_id, author=author, realm_id=realm_id, encryption_revision=encryption_revision, )