Watcha op605 reviewed (#114)

* feat: improve name of Nextcloud errors

* feat: improve name of remove_from_group function

* feat: mprove name of create_all_permission_share_with_group function

* feat: improve name of function delete_share

* feat: improve name of Nextcloud errors

* feat: improve syntax of b64 functions

* feat: improve name of Nextcloud mapping functions

* feat: improve name of tests case in WatchaRoomNextcloudMappingTestCase

* feat: improve code syntax

* feat: improve name of function get_room_list_to_send_nextcloud_notification

* feat: improve name of update_existing_nextcloud_share_for_user function

* feat: use SynapseError instead of Exception

* feat: impove generate password function

* feat: improve syntax code

* feat: create a proper handler for Nextcloud integration

* feat: create proper Store for Nextcloud integration

* fix: change inviter display name for fix texts

* feat: adds JSON schema validation for Keycloak and Nextcloud client

* feat: add code modification after review (https://github.com/watcha-fr/synapse/pull/113)

* fix: update nextcloud_integration name in config

* feat: improve schema validation in KC and NC clients

* fix: update nextcloud_integration renaming

* feat: catch JSON schema errors
code_spécifique_watcha
KevICO 4 years ago committed by c-cal
parent f23b023caf
commit d2c8a0b771
Signed by: watcha
GPG Key ID: 87DD78E7F7A1581D
  1. 6
      synapse/api/errors.py
  2. 2
      synapse/config/_base.pyi
  3. 2
      synapse/config/homeserver.py
  4. 10
      synapse/config/watcha_nextcloud.py
  5. 238
      synapse/handlers/room.py
  6. 234
      synapse/handlers/watcha_nextcloud.py
  7. 39
      synapse/http/watcha_keycloak_client.py
  8. 104
      synapse/http/watcha_nextcloud_client.py
  9. 18
      synapse/rest/client/v1/room.py
  10. 2
      synapse/rest/client/v1/watcha.py
  11. 2
      synapse/server.py
  12. 12
      synapse/storage/databases/main/__init__.py
  13. 66
      synapse/storage/databases/main/room.py
  14. 4
      synapse/storage/databases/main/watcha_admin.py
  15. 63
      synapse/storage/databases/main/watcha_nextcloud.py
  16. 8
      synapse/util/watcha.py
  17. 117
      tests/handlers/test_watcha_nextcloud.py
  18. 2
      tests/rest/client/v1/test_watcha.py
  19. 93
      tests/rest/client/v1/test_watcha_nextcloud.py
  20. 20
      tests/storage/test_room.py

@ -79,12 +79,12 @@ class Codes:
# watcha+
NEXTCLOUD_CAN_NOT_ADD_USER_TO_GROUP = "W_NEXTCLOUD_CAN_NOT_ADD_USER_TO_GROUP"
NEXTCLOUD_CAN_NOT_CREATE_GROUP = "W_NEXTCLOUD_CANNOT_CREATE_GROUP"
NEXTCLOUD_CAN_NOT_CREATE_NEW_SHARE = "W_NEXTCLOUD_CAN_NOT_CREATE_NEW_SHARE"
NEXTCLOUD_CAN_NOT_DELETE_GROUP = "W_NEXTCLOUD_CAN_NOT_DELETE_GROUP"
NEXTCLOUD_CAN_NOT_DELETE_SHARE = "W_NEXTCLOUD_CAN_NOT_DELETE_SHARE"
NEXTCLOUD_CAN_NOT_GET_SHARES = "W_NEXTCLOUD_CANNOT_GET_SHARES"
NEXTCLOUD_CAN_NOT_GET_USER = "W_NEXTCLOUD_CAN_NOT_GET_USER"
NEXTCLOUD_CAN_NOT_REMOVE_USER_TO_GROUP = "W_NEXTCLOUD_CAN_NOT_REMOVE_USER_TO_GROUP"
NEXTCLOUD_CAN_NOT_REMOVE_USER_FROM_GROUP = "W_NEXTCLOUD_CAN_NOT_REMOVE_USER_FROM_GROUP"
NEXTCLOUD_CAN_NOT_SHARE = "W_NEXTCLOUD_CAN_NOT_SHARE"
NEXTCLOUD_CAN_NOT_UNSHARE = "W_NEXTCLOUD_CAN_NOT_UNSHARE"
# +watcha

@ -76,7 +76,7 @@ class RootConfig:
roomdirectory: room_directory.RoomDirectoryConfig
thirdpartyrules: third_party_event_rules.ThirdPartyRulesConfig
tracer: tracer.TracerConfig
nextcloudintegration: watcha_nextcloud_integration.NextcloudIntegrationConfig # watcha+ op544
nextcloud: watcha_nextcloud.NextcloudIntegrationConfig # watcha+ op544
config_classes: List = ...
def __init__(self) -> None: ...

@ -52,7 +52,7 @@ from .user_directory import UserDirectoryConfig
from .voip import VoipConfig
from .workers import WorkerConfig
from .watcha_nextcloud_integration import NextcloudIntegrationConfig # watcha+ op544
from .watcha_nextcloud import NextcloudIntegrationConfig # watcha+ op544
class HomeServerConfig(RootConfig):

@ -6,7 +6,7 @@ from ._base import Config, ConfigError
class NextcloudIntegrationConfig(Config):
section = "nextcloudintegration"
section = "nextcloud"
# echo -n watcha | md5sum | head -c 10
SERVICE_ACCOUNT_NAME = "c4d96a06b7_watcha_service_account"
@ -24,7 +24,7 @@ class NextcloudIntegrationConfig(Config):
def read_config(self, config, **kwargs):
self.nextcloud_enabled = False
nextcloud_config = config.get("nextcloud_integration")
nextcloud_config = config.get("nextcloud")
if not nextcloud_config or not nextcloud_config.get("enabled", False):
return
@ -36,7 +36,7 @@ class NextcloudIntegrationConfig(Config):
match = re.match("(https?://.+?)/realms/([^/]+)", issuer)
if match is None:
raise ConfigError(
"nextcloud_integration requires oidc_config.issuer to be of the form https://example/realms/xxx"
"nextcloud requires oidc_config.issuer to be of the form https://example/realms/xxx"
)
self.keycloak_url = match.group(1)
self.realm_name = match.group(2)
@ -46,7 +46,7 @@ class NextcloudIntegrationConfig(Config):
client_base_url = config.get("email", {}).get("client_base_url")
if client_base_url is None:
raise ConfigError(
"nextcloud_integration requires nextcloud_url or email.client_base_url to be set"
"nextcloud requires nextcloud_url or email.client_base_url to be set"
)
nextcloud_url = urljoin(client_base_url, "nextcloud")
self.nextcloud_url = nextcloud_url
@ -64,7 +64,7 @@ class NextcloudIntegrationConfig(Config):
return """\
# Configuration for the Nextcloud integration
#
nextcloud_integration:
nextcloud:
# Uncomment the below to enable the Nextcloud integration
#
#enabled: true

@ -61,25 +61,12 @@ from ._base import BaseHandler
if TYPE_CHECKING:
from synapse.server import HomeServer
# watcha+
from pathlib import Path
from requests import get, post, delete, auth, HTTPError
from requests.auth import HTTPBasicAuth
from synapse.http.watcha_keycloak_client import KeycloakClient
from synapse.http.watcha_nextcloud_client import NextcloudClient
from synapse.types import get_localpart_from_id, decode_localpart, map_username_to_mxid_localpart
# +watcha
logger = logging.getLogger(__name__)
id_server_scheme = "https://"
FIVE_MINUTES_IN_MS = 5 * 60 * 1000
# watcha+
# echo -n watcha | md5sum
NEXTCLOUD_GROUP_NAME_PREFIX = "c4d96a06b7_"
# +watcha
class RoomCreationHandler(BaseHandler):
@ -1442,228 +1429,3 @@ class RoomShutdownHandler:
"local_aliases": aliases_for_room,
"new_room_id": new_room_id,
}
# watcha+
class NextcloudHandler(BaseHandler):
def __init__(self, hs):
self.store = hs.get_datastore()
self.event_creation_handler = hs.get_event_creation_handler()
self.keycloak_client = KeycloakClient(hs)
self.nextcloud_client = NextcloudClient(hs)
async def create_keycloak_and_nextcloud_user(self, localpart, email, password_hash, synapse_role=None):
""" Create a user on Keycloak and Nextcloud server if it doesn't exist
Args :
localpart: the synapse localpart use as Keycloak username
email: email of the user
password_hash: the synapse password hash
synapse_role: the synapse role, it can be administrator, collaborator or partner.
"""
await self.keycloak_client.add_user(localpart, email, password_hash, synapse_role)
keycloak_user_representation = await self.keycloak_client.get_user(
localpart
)
await self.nextcloud_client.add_user(keycloak_user_representation["id"])
async def delete_room_nextcloud_mapping(self, room_id):
""" Delete a mapping between a room and an Nextcloud folder.
Args :
room_id: the id of the room.
"""
await self.nextcloud_client.delete_group(NEXTCLOUD_GROUP_NAME_PREFIX + room_id)
await self.store.deleted_room_mapping_with_nextcloud_directory(room_id)
async def update_room_nextcloud_mapping(
self, room_id, requester_id, nextcloud_directory_path
):
""" Update the mapping between a room and a Nextcloud folder.
Args :
room_id: the id of the room which must be linked with the Nextcloud folder.
requester_id: the user_id of the requester.
nextcloud_directory_path: the directory path of the Nextcloud folder to link with the room.
"""
group_name = NEXTCLOUD_GROUP_NAME_PREFIX + room_id
keycloak_user_representation = await self.keycloak_client.get_user(
decode_localpart(requester_id)
)
nextcloud_requester = keycloak_user_representation["id"]
await self.nextcloud_client.add_group(group_name)
await self.add_room_users_to_nextcloud_group(room_id)
old_share_id = await self.store.get_nextcloud_share_id_from_roomID(room_id)
if old_share_id:
await self.nextcloud_client.delete_share(nextcloud_requester, old_share_id)
new_share_id = await self.nextcloud_client.create_all_permission_share_with_group(
nextcloud_requester, nextcloud_directory_path, group_name
)
await self.store.map_room_with_nextcloud_directory(
room_id, nextcloud_directory_path, new_share_id
)
async def add_room_users_to_nextcloud_group(self, room_id):
""" Add all users of a room to a Nextcloud group which name like the room_id.
Args:
room_id: the id of the room which is the name of the Nextcloud group.
"""
group_name = NEXTCLOUD_GROUP_NAME_PREFIX + room_id
users_id = await self.store.get_users_in_room(room_id)
users_localpart = [get_localpart_from_id(user_id) for user_id in users_id]
keycloak_users_representation = (
await self.keycloak_client.get_users()
)
for keycloak_user in keycloak_users_representation:
localpart = map_username_to_mxid_localpart(keycloak_user["username"])
nextcloud_target = keycloak_user["id"]
if localpart in users_localpart:
try:
await self.nextcloud_client.get_user(nextcloud_target)
except Exception:
logger.warn(
"The user {} does not have a Nextcloud account.".format(
localpart
)
)
continue
try:
await self.nextcloud_client.add_user_to_group(
nextcloud_target, group_name
)
except Exception:
logger.warn(
"Unable to add the user {username} to the Nextcloud group {group_name}.".format(
username=localpart, group_name=group_name
)
)
continue
async def update_existing_nextcloud_share_for_user(
self, user_id, room_id, membership
):
group_name = NEXTCLOUD_GROUP_NAME_PREFIX + room_id
keycloak_user_representation = await self.keycloak_client.get_user(
decode_localpart(user_id)
)
nextcloud_username = keycloak_user_representation["id"]
if membership in ["invite", "join"]:
try:
await self.nextcloud_client.add_user_to_group(
nextcloud_username, group_name
)
except Exception:
logger.warn(
"Unable to add the user {username} to the Nextcloud group {group_name}.".format(
username=user_id, group_name=group_name
),
)
else:
try:
await self.nextcloud_client.remove_from_group(
nextcloud_username, group_name
)
except Exception:
logger.warn(
"Unable to remove the user {username} from the Nextcloud group {group_name}.".format(
username=user_id, group_name=group_name
),
)
async def get_room_list_to_send_nextcloud_notification(
self, directory, limit_of_notification_propagation
):
rooms = []
if not directory:
raise SynapseError(400, "The directory path is empty")
directories = [
str(directory)
for directory in Path(directory).parents
if limit_of_notification_propagation in str(directory)
and str(directory) != limit_of_notification_propagation
]
directories.append(directory)
for directory in directories:
room = await self.store.get_roomID_from_nextcloud_directory_path(directory)
if room:
rooms.append(room)
if not rooms:
raise SynapseError(
400, "No rooms are linked with this Nextcloud directory."
)
return rooms
async def send_nextcloud_notification_to_rooms(
self, rooms, file_name, file_url, file_operation
):
notification_sent = {
"file_name": file_name,
"file_operation": file_operation,
}
content = {
"body": file_operation,
"filename": file_name,
"msgtype": "m.file",
"url": "",
}
if file_operation in ("file_created", "file_restored", "file_moved"):
content["url"] = file_url
notified_rooms = []
for room in rooms:
users = await self.store.get_users_in_room(room)
if not users:
logger.warn(
"This room has no users. The Nextcloud notification cannot be posted.",
)
continue
requester = create_requester(users[0])
sender = requester.user.to_string()
event_dict = {
"type": EventTypes.Message,
"content": content,
"room_id": room,
"sender": sender,
}
await self.event_creation_handler.create_and_send_nonmember_event(
requester, event_dict
)
notified_rooms.append(
{"room_id": room, "sender": sender,}
)
notification_sent["notified_rooms"] = notified_rooms
return notification_sent
# +watcha

@ -0,0 +1,234 @@
import logging
from jsonschema.exceptions import ValidationError, SchemaError
from pathlib import Path
from ._base import BaseHandler
from synapse.api.constants import EventTypes
from synapse.api.errors import SynapseError
from synapse.http.watcha_keycloak_client import KeycloakClient
from synapse.http.watcha_nextcloud_client import NextcloudClient
from synapse.types import (
create_requester,
get_localpart_from_id,
map_username_to_mxid_localpart,
decode_localpart,
)
logger = logging.getLogger(__name__)
# echo -n watcha | md5sum | head -c 10
NEXTCLOUD_GROUP_NAME_PREFIX = "c4d96a06b7_"
class NextcloudHandler(BaseHandler):
def __init__(self, hs):
self.store = hs.get_datastore()
self.event_creation_handler = hs.get_event_creation_handler()
self.keycloak_client = KeycloakClient(hs)
self.nextcloud_client = NextcloudClient(hs)
async def create_keycloak_and_nextcloud_user(
self, localpart, email, password_hash, synapse_role=None
):
"""Create a user on Keycloak and Nextcloud server if it doesn't exist
Args :
localpart: the synapse localpart use as Keycloak username
email: email of the user
password_hash: the synapse password hash
synapse_role: the synapse role, it can be administrator, collaborator or partner.
"""
await self.keycloak_client.add_user(
localpart, email, password_hash, synapse_role
)
keycloak_user_representation = await self.keycloak_client.get_user(localpart)
await self.nextcloud_client.add_user(keycloak_user_representation["id"])
async def unbind(self, room_id):
"""Unbind a Nextcloud folder from a room.
Args :
room_id: the id of the room to bind.
"""
await self.nextcloud_client.delete_group(NEXTCLOUD_GROUP_NAME_PREFIX + room_id)
await self.store.unbind(room_id)
async def bind(self, user_id, room_id, path):
"""Bind a Nextcloud folder with a room.
Args :
user_id: the matrix user id of the requester.
room_id: the id of the room to bind.
path: the path of the Nextcloud folder to bind.
"""
group_name = NEXTCLOUD_GROUP_NAME_PREFIX + room_id
user = await self.keycloak_client.get_user(decode_localpart(user_id))
nextcloud_username = user["id"]
await self.nextcloud_client.add_group(group_name)
await self.add_room_users_to_nextcloud_group(room_id)
old_share_id = await self.store.get_nextcloud_share_id_from_room_id(room_id)
if old_share_id:
await self.nextcloud_client.unshare(nextcloud_username, old_share_id)
new_share_id = await self.nextcloud_client.share(
nextcloud_username, path, group_name
)
await self.store.bind(room_id, path, new_share_id)
async def add_room_users_to_nextcloud_group(self, room_id):
"""Add all users of a room to a Nextcloud.
Args:
room_id: the id of the room which the Nextcloud group name is infered from.
"""
group_name = NEXTCLOUD_GROUP_NAME_PREFIX + room_id
user_ids = await self.store.get_users_in_room(room_id)
localparts = [get_localpart_from_id(user_id) for user_id in user_ids]
users = await self.keycloak_client.get_users()
for user in users:
localpart = map_username_to_mxid_localpart(user["username"])
nextcloud_username = user["id"]
if localpart in localparts:
try:
await self.nextcloud_client.get_user(nextcloud_username)
except (SynapseError, ValidationError, SchemaError):
logger.warn(
"The user {} does not have a Nextcloud account.".format(
localpart
)
)
continue
try:
await self.nextcloud_client.add_user_to_group(
nextcloud_username, group_name
)
except (SynapseError, ValidationError, SchemaError):
logger.warn(
"Unable to add the user {} to the Nextcloud group {}.".format(
localpart, group_name
)
)
async def update_share(self, user_id, room_id, membership):
group_name = NEXTCLOUD_GROUP_NAME_PREFIX + room_id
user = await self.keycloak_client.get_user(decode_localpart(user_id))
nextcloud_username = user["id"]
if membership in ("invite", "join"):
try:
await self.nextcloud_client.add_user_to_group(
nextcloud_username, group_name
)
except (SynapseError, ValidationError, SchemaError):
logger.warn(
"Unable to add the user {} to the Nextcloud group {}.".format(
user_id, group_name
),
)
else:
try:
await self.nextcloud_client.remove_user_from_group(
nextcloud_username, group_name
)
except (SynapseError, ValidationError, SchemaError):
logger.warn(
"Unable to remove the user {} from the Nextcloud group {}.".format(
user_id, group_name
),
)
async def get_rooms_to_send_notification(
self, directory, limit_of_notification_propagation
):
rooms = []
if not directory:
raise SynapseError(400, "The directory path is empty")
directories = [
str(directory)
for directory in Path(directory).parents
if limit_of_notification_propagation in str(directory)
and str(directory) != limit_of_notification_propagation
]
directories.append(directory)
for directory in directories:
room = await self.store.get_room_id_from_path(directory)
if room:
rooms.append(room)
if not rooms:
raise SynapseError(
400, "No rooms are linked with this Nextcloud directory."
)
return rooms
async def send_nextcloud_notification_to_rooms(
self, rooms, file_name, file_url, file_operation
):
notification_sent = {
"file_name": file_name,
"file_operation": file_operation,
}
content = {
"body": file_operation,
"filename": file_name,
"msgtype": "m.file",
"url": "",
}
if file_operation in ("file_created", "file_restored", "file_moved"):
content["url"] = file_url
notified_rooms = []
for room in rooms:
users = await self.store.get_users_in_room(room)
if not users:
logger.warn(
"This room has no users. The Nextcloud notification cannot be posted.",
)
continue
requester = create_requester(users[0])
sender = requester.user.to_string()
event_dict = {
"type": EventTypes.Message,
"content": content,
"room_id": room,
"sender": sender,
}
await self.event_creation_handler.create_and_send_nonmember_event(
requester, event_dict
)
notified_rooms.append(
{
"room_id": room,
"sender": sender,
}
)
notification_sent["notified_rooms"] = notified_rooms
return notification_sent

@ -1,5 +1,5 @@
import logging
from json import JSONDecodeError
from jsonschema import validate
from typing import List
from synapse.api.errors import HttpResponseException
@ -7,6 +7,34 @@ from synapse.http.client import SimpleHttpClient
logger = logging.getLogger(__name__)
TOKEN_SCHEMA = {
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "Keycloak token schema",
"type": "object",
"properties": {
"access_token": {
"type": "string",
},
},
"required": ["access_token"]
}
USER_SCHEMA = {
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "Keycloak user schema",
"type": "array",
"properties": {
"id": {
"type": "string",
},
"username": {
"type": "string",
},
},
"required": ["id", "username"]
}
class KeycloakClient(SimpleHttpClient):
"""Interface for talking with Keycloak APIs"""
@ -71,6 +99,9 @@ class KeycloakClient(SimpleHttpClient):
headers=await self._get_header(),
args={"username": localpart},
)
validate(response, USER_SCHEMA)
return response[0]
async def get_users(self) -> List[dict]:
@ -84,6 +115,9 @@ class KeycloakClient(SimpleHttpClient):
self._get_endpoint("admin/realms/{}/users".format(self.realm_name)),
headers=await self._get_header(),
)
validate(response, USER_SCHEMA)
return response
async def _get_header(self):
@ -108,6 +142,9 @@ class KeycloakClient(SimpleHttpClient):
"password": self.service_account_password,
},
)
validate(response, TOKEN_SCHEMA)
return response["access_token"]
def _get_endpoint(self, path):

@ -1,5 +1,6 @@
import logging
from base64 import b64encode
from jsonschema import validate
from synapse.api.errors import Codes, SynapseError
from synapse.http.client import SimpleHttpClient
@ -7,6 +8,59 @@ from synapse.util.watcha import generate_password
logger = logging.getLogger(__name__)
META_SCHEMA = {
"type": "object",
"properties": {
"statuscode": {"type": "number"},
"status": {"type": "string"},
},
"required": ["statuscode", "status"],
}
STANDARD_SCHEMA = {
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "Standard schema of Owncloud API",
"definitions": {
"meta": META_SCHEMA,
},
"type": "object",
"properties": {
"ocs": {
"type": "object",
"properties": {
"meta": {"$ref": "#/definitions/meta"},
"data": {
"type": "array",
},
},
"required": ["meta", "data"],
},
},
"required": ["ocs"],
}
SHARE_SCHEMA = {
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "Share schema of Owncloud Share API",
"definitions": {"meta": META_SCHEMA},
"type": "object",
"properties": {
"ocs": {
"type": "object",
"properties": {
"meta": {"$ref": "#/definitions/meta"},
"data": {
"type": "object",
"properties": {"id": {"type": "string"}},
"required": ["id"],
},
},
"required": ["meta", "data"],
},
},
"required": ["ocs"],
}
class NextcloudClient(SimpleHttpClient):
"""Interface for talking with Nextcloud APIs
@ -56,7 +110,7 @@ class NextcloudClient(SimpleHttpClient):
102 - username already exists
103 - unknown error occurred whilst adding the user
"""
# A password is needed to create NC user, but it will not be used by KC login process.
# A password is needed to create NC user, but it will not be used by KC login process.
password = generate_password()
response = await self.post_json_get_json(
@ -68,7 +122,9 @@ class NextcloudClient(SimpleHttpClient):
meta = response["ocs"]["meta"]
if meta["statuscode"] == 102:
logger.info("User {} already exists on Nextcloud server.".format(keycloak_user_id))
logger.info(
"User {} already exists on Nextcloud server.".format(keycloak_user_id)
)
else:
self._raise_for_status(meta, Codes.NEXTCLOUD_CAN_NOT_CREATE_GROUP)
@ -91,6 +147,7 @@ class NextcloudClient(SimpleHttpClient):
headers=self._headers,
)
validate(response, STANDARD_SCHEMA)
meta = response["ocs"]["meta"]
if meta["statuscode"] == 102:
@ -111,12 +168,12 @@ class NextcloudClient(SimpleHttpClient):
"""
response = await self.delete_get_json(
uri="{}/ocs/v1.php/cloud/groups/{}".format(
self.nextcloud_url, group_name
),
uri="{}/ocs/v1.php/cloud/groups/{}".format(self.nextcloud_url, group_name),
headers=self._headers,
)
validate(response, STANDARD_SCHEMA)
self._raise_for_status(
response["ocs"]["meta"], Codes.NEXTCLOUD_CAN_NOT_DELETE_GROUP
)
@ -145,11 +202,13 @@ class NextcloudClient(SimpleHttpClient):
headers=self._headers,
)
validate(response, STANDARD_SCHEMA)
self._raise_for_status(
response["ocs"]["meta"], Codes.NEXTCLOUD_CAN_NOT_ADD_USER_TO_GROUP
)
async def remove_from_group(self, username, group_name):
async def remove_user_from_group(self, username, group_name):
"""Removes the specified user from the specified group.
Args:
@ -173,12 +232,14 @@ class NextcloudClient(SimpleHttpClient):
json_body={"groupid": group_name},
)
validate(response, STANDARD_SCHEMA)
self._raise_for_status(
response["ocs"]["meta"], Codes.NEXTCLOUD_CAN_NOT_REMOVE_USER_TO_GROUP
response["ocs"]["meta"], Codes.NEXTCLOUD_CAN_NOT_REMOVE_USER_FROM_GROUP
)
async def get_user(self, username):
""" Get informations of user with username given on parameter
"""Get informations of user with username given on parameter
Args:
username: the username of the user to add to the group.
@ -192,24 +253,25 @@ class NextcloudClient(SimpleHttpClient):
"""
response = await self.get_json(
uri="{}/ocs/v1.php/cloud/users/{}".format(
self.nextcloud_url, username
),
uri="{}/ocs/v1.php/cloud/users/{}".format(self.nextcloud_url, username),
headers=self._headers,
)
validate(response, STANDARD_SCHEMA)
self._raise_for_status(
response["ocs"]["meta"], Codes.NEXTCLOUD_CAN_NOT_GET_USER
)
return response["ocs"]["data"]
async def create_all_permission_share_with_group(self, requester, path, group_name):
async def share(self, requester, path, group_name):
"""Share an existing file or folder with all permissions for a group.
Args:
requester: the user who want to remove the share
args: request attributes to filter the search.
requester: the user who want to create the share
path: the path of folder to share
group_name: the name of group which will share the folder
Payload:
shareType: the type of the share. This can be one of:
@ -251,13 +313,13 @@ class NextcloudClient(SimpleHttpClient):
},
)
self._raise_for_status(
response["ocs"]["meta"], Codes.NEXTCLOUD_CAN_NOT_CREATE_NEW_SHARE
)
validate(response, SHARE_SCHEMA)
self._raise_for_status(response["ocs"]["meta"], Codes.NEXTCLOUD_CAN_NOT_SHARE)
return response["ocs"]["data"]["id"]
async def delete_share(self, requester, share_id):
async def unshare(self, requester, share_id):
"""Remove a given Nextcloud share
Args:
@ -277,6 +339,6 @@ class NextcloudClient(SimpleHttpClient):
json_body={"requester": requester},
)
self._raise_for_status(
response["ocs"]["meta"], Codes.NEXTCLOUD_CAN_NOT_DELETE_SHARE
)
validate(response, STANDARD_SCHEMA)
self._raise_for_status(response["ocs"]["meta"], Codes.NEXTCLOUD_CAN_NOT_UNSHARE)

@ -208,7 +208,7 @@ class RoomStateEventRestServlet(TransactionRestServlet):
content=content,
)
# watcha+
mapped_directory = await self.store.get_nextcloud_directory_path_from_roomID(
mapped_directory = await self.store.get_path_from_room_id(
room_id
)
if mapped_directory and membership in [
@ -222,7 +222,7 @@ class RoomStateEventRestServlet(TransactionRestServlet):
if membership in ["join", "leave"]
else state_key
)
await self.nextcloud_handler.update_existing_nextcloud_share_for_user(
await self.nextcloud_handler.update_share(
user, room_id, membership
)
# +watcha
@ -287,7 +287,7 @@ class RoomStateEventRestServlet(TransactionRestServlet):
requester_id = requester.user.to_string()
if not nextcloud_url:
await self.nextcloud_handler.delete_room_nextcloud_mapping(
await self.nextcloud_handler.unbind(
room_id
)
else:
@ -303,8 +303,8 @@ class RoomStateEventRestServlet(TransactionRestServlet):
nextcloud_directory_path = url_query["dir"][0]
await self.nextcloud_handler.update_room_nextcloud_mapping(
room_id, requester_id, nextcloud_directory_path
await self.nextcloud_handler.bind(
requester_id, room_id, nextcloud_directory_path
)
# +watcha
(
@ -443,11 +443,11 @@ class JoinRoomAliasServlet(TransactionRestServlet):
)
# watcha+
mapped_directory = await self.store.get_nextcloud_directory_path_from_roomID(
mapped_directory = await self.store.get_path_from_room_id(
room_id
)
if mapped_directory:
await self.nextcloud_handler.update_existing_nextcloud_share_for_user(
await self.nextcloud_handler.update_share(
requester.user.to_string(), room_id, "join"
)
# +watcha
@ -982,7 +982,7 @@ class RoomMembershipRestServlet(TransactionRestServlet):
pass
# watcha+
mapped_directory = await self.store.get_nextcloud_directory_path_from_roomID(
mapped_directory = await self.store.get_path_from_room_id(
room_id
)
if mapped_directory and membership_action in [
@ -996,7 +996,7 @@ class RoomMembershipRestServlet(TransactionRestServlet):
if membership_action in ["join", "leave"]
else content["user_id"]
)
await self.nextcloud_handler.update_existing_nextcloud_share_for_user(
await self.nextcloud_handler.update_share(
user, room_id, membership_action
)
# +watcha

@ -118,7 +118,7 @@ class WatchaSendNextcloudActivityToWatchaRoomServlet(RestServlet):
continue
try:
rooms = await self.nextcloud_handler.get_room_list_to_send_nextcloud_notification(
rooms = await self.nextcloud_handler.get_rooms_to_send_notification(
notification["directory"],
notification["limit_of_notification_propagation"],
)

@ -125,7 +125,7 @@ from synapse.util.stringutils import random_string
# watcha+
from synapse.handlers.watcha_administration import AdministrationHandler
from synapse.handlers.watcha_invite_partner import InvitePartnerHandler
from synapse.handlers.room import NextcloudHandler
from synapse.handlers.watcha_nextcloud import NextcloudHandler
# +watcha

@ -73,18 +73,15 @@ from .user_directory import UserDirectoryStore
from .user_erasure_store import UserErasureStore
# watcha+
from .watcha_admin import WatchaAdminStore
from .watcha_admin import AdministrationStore
from .watcha_invite_external import ExternalInvitationStore
from .watcha_nextcloud import NextcloudStore
# +watcha
logger = logging.getLogger(__name__)
class DataStore(
# watcha+
ExternalInvitationStore,
WatchaAdminStore,
# +watcha
EventsBackgroundUpdatesStore,
RoomMemberStore,
RoomStore,
@ -127,6 +124,11 @@ class DataStore(
UIAuthStore,
CacheInvalidationWorkerStore,
ServerMetricsStore,
# watcha+
AdministrationStore,
ExternalInvitationStore,
NextcloudStore,
# +watcha
):
def __init__(self, database: DatabasePool, db_conn, hs):
self.hs = hs

@ -1599,69 +1599,3 @@ class RoomStore(RoomBackgroundUpdateStore, RoomWorkerStore, SearchStore):
self.is_room_blocked,
(room_id,),
)
# watcha+
async def get_nextcloud_directory_path_from_roomID(self, room_id):
""" Get Nextcloud directory path which is mapped with room_id.
"""
return await self.db_pool.simple_select_one_onecol(
table="room_nextcloud_mapping",
keyvalues={"room_id": room_id},
retcol="directory_path",
allow_none=True,
desc="get_nextcloud_directory_path_from_roomID",
)
async def get_roomID_from_nextcloud_directory_path(self, directory_path):
""" Get the room_id mapped with Nextcloud directory path.
"""
return await self.db_pool.simple_select_one_onecol(
table="room_nextcloud_mapping",
keyvalues={"directory_path": directory_path},
retcol="room_id",
allow_none=True,
desc="get_roomID_from_nextcloud_directory_path",
)
async def get_nextcloud_share_id_from_roomID(self, room_id):
""" Get Nextcloud share id of the room id.
"""
return await self.db_pool.simple_select_one_onecol(
table="room_nextcloud_mapping",
keyvalues={"room_id": room_id},
retcol="share_id",
allow_none=True,
desc="get_nextcloud_share_id_from_roomID",
)
async def map_room_with_nextcloud_directory(
self, room_id, directory_path, share_id
):
""" Set mapping between Watcha room and Nextcloud directory.
"""
await self.db_pool.simple_upsert(
table="room_nextcloud_mapping",
keyvalues={"room_id": room_id},
values={
"room_id": room_id,
"directory_path": directory_path,
"share_id": share_id,
},
desc="map_room_with_nextcloud_directory",
)
async def deleted_room_mapping_with_nextcloud_directory(self, room_id):
""" Delete mapping between Watcha room and Nextcloud directory for room_id.
"""
await self.db_pool.simple_delete(
table="room_nextcloud_mapping",
keyvalues={"room_id": room_id},
desc="deleted_room_mapping_with_nextcloud_directory",
)
# +watcha

@ -23,9 +23,9 @@ def _caller_name():
return "<unknown function>"
class WatchaAdminStore(SQLBaseStore):
class AdministrationStore(SQLBaseStore):
def __init__(self, database: DatabasePool, db_conn, hs):
super(WatchaAdminStore, self).__init__(database, db_conn, hs)
super().__init__(database, db_conn, hs)
self.clock = hs.get_clock()

@ -0,0 +1,63 @@
from synapse.storage._base import SQLBaseStore
from synapse.storage.database import DatabasePool
class NextcloudStore(SQLBaseStore):
def __init__(self, database: DatabasePool, db_conn, hs):
super().__init__(database, db_conn, hs)
async def get_path_from_room_id(self, room_id):
"""Get the Nextcloud folder path which is bound with room_id."""
return await self.db_pool.simple_select_one_onecol(
table="room_nextcloud_mapping",
keyvalues={"room_id": room_id},
retcol="directory_path",
allow_none=True,
desc="get_path_from_room_id",
)
async def get_room_id_from_path(self, path):
"""Get the room_id bound with Nextcloud folder path."""
return await self.db_pool.simple_select_one_onecol(
table="room_nextcloud_mapping",
keyvalues={"directory_path": path},
retcol="room_id",
allow_none=True,
desc="get_room_id_from_path",
)
async def get_nextcloud_share_id_from_room_id(self, room_id):
"""Get Nextcloud share id of the room id."""
return await self.db_pool.simple_select_one_onecol(
table="room_nextcloud_mapping",
keyvalues={"room_id": room_id},
retcol="share_id",
allow_none=True,
desc="get_nextcloud_share_id_from_room_id",
)
async def bind(self, room_id, path, share_id):
"""Bind a room with a Nextcloud folder."""
await self.db_pool.simple_upsert(
table="room_nextcloud_mapping",
keyvalues={"room_id": room_id},
values={
"room_id": room_id,
"directory_path": path,
"share_id": share_id,
},
desc="bind",
)
async def unbind(self, room_id):
"""Delete mapping between Watcha room and Nextcloud directory for room_id."""
await self.db_pool.simple_delete(
table="room_nextcloud_mapping",
keyvalues={"room_id": room_id},
desc="unbind",
)

@ -9,7 +9,7 @@ from email.mime.text import MIMEText
from jinja2 import Environment, FileSystemLoader
from os.path import join, dirname, abspath
from pathlib import Path
from secrets import token_bytes
from secrets import token_hex
from smtplib import SMTP
from synapse.api.errors import SynapseError
@ -22,13 +22,13 @@ TEMPLATE_DIR = join(dirname(abspath(__file__)), "watcha_templates")
def generate_password():
""" Generate a base 64 encoded password with 16 bytes of randomness
""" Generate a password with 16 bytes of randomness
Returns:
The encoded password.
"""
return b64encode(token_bytes(16)).decode()
return token_hex(16)
def compute_registration_token(user, email=None, password=None):
@ -43,7 +43,7 @@ def compute_registration_token(user, email=None, password=None):
json = '{{"user":"{user}", "email":"{email}", "pw":"{password}"}}'.format(
user=user, email=email, password=password
)
return b64encode(json.encode("utf-8")).decode("ascii")
return b64encode(json.encode()).decode()
# additional email we send to, when not sending to a mail gun

@ -10,6 +10,7 @@ from tests.unittest import HomeserverTestCase
NEXTCLOUD_GROUP_NAME_PREFIX = "c4d96a06b7_"
def simple_async_mock(return_value=None, raises=None):
# AsyncMock is not available in python3.5, this mimics part of its behaviour
async def cb(*args, **kwargs):
@ -63,94 +64,80 @@ class NextcloudHandlerTestCase(HomeserverTestCase):
self.nextcloud_client.delete_group = simple_async_mock()
self.nextcloud_client.get_user = simple_async_mock()
self.nextcloud_client.add_user_to_group = simple_async_mock()
self.nextcloud_client.remove_from_group = simple_async_mock()
self.nextcloud_client.delete_share = simple_async_mock()
self.nextcloud_client.create_all_permission_share_with_group = simple_async_mock(
return_value=1
)
self.nextcloud_client.remove_user_from_group = simple_async_mock()
self.nextcloud_client.unshare = simple_async_mock()
self.nextcloud_client.share = simple_async_mock(return_value=1)
def test_set_new_room_nextcloud_mapping(self):
def test_set_a_new_bind(self):
self.get_success(
self.nextcloud_handler.update_room_nextcloud_mapping(
self.room_id, self.creator, "/directory"
)
self.nextcloud_handler.bind(self.creator, self.room_id, "/directory")
)
mapped_directory = self.get_success(
self.store.get_nextcloud_directory_path_from_roomID(self.room_id)
self.store.get_path_from_room_id(self.room_id)
)
share_id = self.get_success(
self.store.get_nextcloud_share_id_from_room_id(self.room_id)
)
# Verify that mocked functions are called once
self.keycloak_client.get_user.assert_called_once()
self.nextcloud_client.add_group.assert_called_once()
self.keycloak_client.get_users.assert_called_once()
self.nextcloud_client.create_all_permission_share_with_group.assert_called_once()
self.nextcloud_client.share.assert_called_once()
# Verify that mocked functions are called twice
self.assertEquals(self.nextcloud_client.get_user.call_count, 2)
self.assertEquals(self.nextcloud_client.add_user_to_group.call_count, 2)
# Verify that mocked functions are not called
self.nextcloud_client.delete_share.assert_not_called()
self.nextcloud_client.unshare.assert_not_called()
self.assertEqual(mapped_directory, "/directory")
def test_update_existing_room_nextcloud_mapping(self):
self.get_success(
self.store.map_room_with_nextcloud_directory(
self.room_id, "/directory", 2
)
)
def test_update_an_existing_bind(self):
self.get_success(self.store.bind(self.room_id, "/directory", 2))
old_mapped_directory = self.get_success(
self.store.get_nextcloud_directory_path_from_roomID(self.room_id)
self.store.get_path_from_room_id(self.room_id)
)
old_share_id = self.get_success(
self.store.get_nextcloud_share_id_from_roomID(self.room_id)
self.store.get_nextcloud_share_id_from_room_id(self.room_id)
)
self.assertEqual(old_mapped_directory, "/directory")
self.assertEqual(old_share_id, 2)
self.get_success(
self.nextcloud_handler.update_room_nextcloud_mapping(
self.room_id, self.creator, "/directory2"
)
self.nextcloud_handler.bind(self.creator, self.room_id, "/directory2")
)
mapped_directory = self.get_success(
self.store.get_nextcloud_directory_path_from_roomID(self.room_id)
self.store.get_path_from_room_id(self.room_id)
)
new_share_id = self.get_success(
self.store.get_nextcloud_share_id_from_roomID(self.room_id)
self.store.get_nextcloud_share_id_from_room_id(self.room_id)
)
# Verify that mocked functions has called :
self.nextcloud_client.delete_share.assert_called()
self.nextcloud_client.unshare.assert_called()
self.assertEqual(mapped_directory, "/directory2")
self.assertEqual(new_share_id, 1)
def test_delete_existing_room_nextcloud_mapping(self):
self.get_success(
self.store.map_room_with_nextcloud_directory(
self.room_id, "/directory", 2
)
)
self.get_success(
self.nextcloud_handler.delete_room_nextcloud_mapping(
self.room_id
)
)
def test_delete_an_existing_bind(self):
self.get_success(self.store.bind(self.room_id, "/directory", 2))
self.get_success(self.nextcloud_handler.unbind(self.room_id))
mapped_directory = self.get_success(
self.store.get_nextcloud_directory_path_from_roomID(self.room_id)
self.store.get_path_from_room_id(self.room_id)
)
share_id = self.get_success(
self.store.get_nextcloud_share_id_from_roomID(self.room_id)
self.store.get_nextcloud_share_id_from_room_id(self.room_id)
)
self.nextcloud_client.delete_group.assert_called()
@ -158,9 +145,11 @@ class NextcloudHandlerTestCase(HomeserverTestCase):
self.assertIsNone(share_id)
def test_add_user_to_nextcloud_group_without_nextcloud_account(self):
self.nextcloud_client.get_user = simple_async_mock(raises=SynapseError)
self.nextcloud_client.get_user = simple_async_mock(
raises=SynapseError(code=400, msg="")
)
with self.assertLogs("synapse.handlers.room", level="WARN") as cm:
with self.assertLogs("synapse.handlers.watcha_nextcloud", level="WARN") as cm:
self.get_success(
self.nextcloud_handler.add_room_users_to_nextcloud_group(self.room_id)
)
@ -181,9 +170,11 @@ class NextcloudHandlerTestCase(HomeserverTestCase):
def test_add_user_to_nextcloud_group_with_exception(self):
group_name = NEXTCLOUD_GROUP_NAME_PREFIX + self.room_id
self.nextcloud_client.add_user_to_group = simple_async_mock(raises=SynapseError)
self.nextcloud_client.add_user_to_group = simple_async_mock(
raises=SynapseError(code=400, msg="")
)
with self.assertLogs("synapse.handlers.room", level="WARN") as cm:
with self.assertLogs("synapse.handlers.watcha_nextcloud", level="WARN") as cm:
self.get_success(
self.nextcloud_handler.add_room_users_to_nextcloud_group(self.room_id)
)
@ -204,84 +195,84 @@ class NextcloudHandlerTestCase(HomeserverTestCase):
def test_update_existing_nextcloud_share_on_invite_membership(self):
self.get_success(
self.nextcloud_handler.update_existing_nextcloud_share_for_user(
self.nextcloud_handler.update_share(
"@second_inviter:test", self.room_id, "invite"
)
)
self.keycloak_client.get_user.assert_called_once()
self.nextcloud_client.add_user_to_group.assert_called_once()
self.nextcloud_client.remove_from_group.assert_not_called()
self.nextcloud_client.remove_user_from_group.assert_not_called()
def test_update_existing_nextcloud_share_on_join_membership(self):
self.get_success(
self.nextcloud_handler.update_existing_nextcloud_share_for_user(
self.nextcloud_handler.update_share(
"@second_inviter:test", self.room_id, "join"
)
)
self.keycloak_client.get_user.assert_called_once()
self.nextcloud_client.add_user_to_group.assert_called_once()
self.nextcloud_client.remove_from_group.assert_not_called()
self.nextcloud_client.remove_user_from_group.assert_not_called()
def test_update_existing_nextcloud_share_on_leave_membership(self):
self.get_success(
self.nextcloud_handler.update_existing_nextcloud_share_for_user(
self.nextcloud_handler.update_share(
"@second_inviter:test", self.room_id, "leave"
)
)
self.keycloak_client.get_user.assert_called_once()
self.nextcloud_client.remove_from_group.assert_called_once()
self.nextcloud_client.remove_user_from_group.assert_called_once()
self.nextcloud_client.add_user_to_group.assert_not_called()
def test_update_existing_nextcloud_share_on_kick_membership(self):
self.get_success(
self.nextcloud_handler.update_existing_nextcloud_share_for_user(
self.nextcloud_handler.update_share(
"@second_inviter:test", self.room_id, "kick"
)
)
self.keycloak_client.get_user.assert_called_once()
self.nextcloud_client.remove_from_group.assert_called_once()
self.nextcloud_client.remove_user_from_group.assert_called_once()
self.nextcloud_client.add_user_to_group.assert_not_called()
def test_update_existing_nextcloud_share_on_invite_membership_with_exception(self):
group_name = NEXTCLOUD_GROUP_NAME_PREFIX + self.room_id
self.nextcloud_client.add_user_to_group = simple_async_mock(raises=SynapseError)
self.nextcloud_client.add_user_to_group = simple_async_mock(
raises=SynapseError(code=400, msg="")
)
second_inviter = "@second_inviter:test"
with self.assertLogs("synapse.handlers.room", level="WARN") as cm:
with self.assertLogs("synapse.handlers.watcha_nextcloud", level="WARN") as cm:
self.get_success(
self.nextcloud_handler.update_existing_nextcloud_share_for_user(
self.nextcloud_handler.update_share(
second_inviter, self.room_id, "invite"
)
)
self.assertIn(
"Unable to add the user {} to the Nextcloud group {}.".format(
second_inviter, group_name
second_inviter, NEXTCLOUD_GROUP_NAME_PREFIX + self.room_id
),
cm.output[0],
)
def test_update_existing_nextcloud_share_on_leave_membership_with_exception(self):
group_name = NEXTCLOUD_GROUP_NAME_PREFIX + self.room_id
self.nextcloud_client.remove_from_group = simple_async_mock(raises=SynapseError)
self.nextcloud_client.remove_user_from_group = simple_async_mock(
raises=SynapseError(code=400, msg="")
)
second_inviter = "@second_inviter:test"
with self.assertLogs("synapse.handlers.room", level="WARN") as cm:
with self.assertLogs("synapse.handlers.watcha_nextcloud", level="WARN") as cm:
self.get_success(
self.nextcloud_handler.update_existing_nextcloud_share_for_user(
self.nextcloud_handler.update_share(
second_inviter, self.room_id, "leave"
)
)
self.assertIn(
"Unable to remove the user {} from the Nextcloud group {}.".format(
second_inviter, group_name
second_inviter, NEXTCLOUD_GROUP_NAME_PREFIX + self.room_id
),
cm.output[0],
)

@ -445,7 +445,7 @@ class WatchaSendNextcloudActivityToWatchaRoomServletTestCase(
room_id = self._create_room()
mapping_value["room_id"] = room_id
await self.store.map_room_with_nextcloud_directory(
await self.store.bind(
room_id, mapping_value["path"], mapping_value["share_id"]
)

@ -16,34 +16,54 @@ def simple_async_mock(return_value=None, raises=None):
return Mock(side_effect=cb)
class WatchaRoomNextcloudMappingEventTestCase(unittest.HomeserverTestCase):
class NextcloudRestTestCase(unittest.HomeserverTestCase):
"""Tests that Nextcloud sharing is updated with membership event when the room is mapped with a Nextcloud directory"""
servlets = [
synapse.rest.admin.register_servlets_for_client_rest_resource,
login.register_servlets,
room.register_servlets,
login.register_servlets,
]
def prepare(self, reactor, clock, hs):
self.room_owner = self.register_user("room_owner", "test")
self.room_owner_tok = self.login("room_owner", "test")
self.store = hs.get_datastore()
self.creator = self.register_user("creator", "pass")
self.creator_tok = self.login("creator", "pass")
self.room_id = self.helper.create_room_as(
self.room_owner, tok=self.room_owner_tok
self.inviter = self.register_user("inviter", "pass")
self.inviter_tok = self.login("inviter", "pass")
self.room_id = self.helper.create_room_as(self.creator, tok=self.creator_tok)
# map a room with a Nextcloud directory :
self.get_success(
self.store.bind(self.room_id, "/directory", 1)
)
# mock some functions of WatchaRoomNextcloudMappingHandler
self.nextcloud_handler = hs.get_nextcloud_handler()
self.nextcloud_handler.update_room_nextcloud_mapping = simple_async_mock()
self.nextcloud_handler.delete_room_nextcloud_mapping = simple_async_mock()
self.keycloak_client = self.nextcloud_handler.keycloak_client
self.nextcloud_client = self.nextcloud_handler.nextcloud_client
self.nextcloud_handler.bind = simple_async_mock()
self.nextcloud_handler.unbind = simple_async_mock()
self.nextcloud_directory_url = (
"https://test/nextcloud/apps/files/?dir=/directory"
)
self.keycloak_client.get_user = simple_async_mock(
return_value={"id": "1234", "username": "creator"},
)
self.nextcloud_client.add_user_to_group = simple_async_mock()
self.nextcloud_client.remove_user_from_group = simple_async_mock()
def send_room_nextcloud_mapping_event(self, request_content):
request, channel = self.make_request(
"PUT",
"/rooms/{}/state/im.vector.web.settings".format(self.room_id),
content=json.dumps(request_content),
access_token=self.room_owner_tok,
access_token=self.creator_tok,
)
self.render(request)
@ -54,16 +74,18 @@ class WatchaRoomNextcloudMappingEventTestCase(unittest.HomeserverTestCase):
{"nextcloudShare": self.nextcloud_directory_url}
)
self.assertTrue(self.nextcloud_handler.update_room_nextcloud_mapping.called)
self.assertTrue(self.nextcloud_handler.bind.called)
self.assertEquals(200, channel.code)
def test_delete_existing_room_nextcloud_mapping(self):
self.send_room_nextcloud_mapping_event(
{"nextcloudShare": self.nextcloud_directory_url}
)
channel = self.send_room_nextcloud_mapping_event({"nextcloudShare": ""})
self.assertTrue(self.nextcloud_handler.bind.called)
self.assertTrue(self.nextcloud_handler.delete_room_nextcloud_mapping.called)
channel = self.send_room_nextcloud_mapping_event({"nextcloudShare": ""})
self.assertTrue(self.nextcloud_handler.unbind.called)
self.assertEquals(200, channel.code)
def test_update_existing_room_nextcloud_mapping(self):
@ -74,7 +96,7 @@ class WatchaRoomNextcloudMappingEventTestCase(unittest.HomeserverTestCase):
{"nextcloudShare": "https://test/nextcloud/apps/files/?dir=/directory2"}
)
self.assertTrue(self.nextcloud_handler.update_room_nextcloud_mapping.called)
self.assertTrue(self.nextcloud_handler.bind.called)
self.assertEquals(200, channel.code)
def test_create_new_room_nextcloud_mapping_without_nextcloudShare_attribute(self):
@ -82,7 +104,7 @@ class WatchaRoomNextcloudMappingEventTestCase(unittest.HomeserverTestCase):
{"nextcloud": self.nextcloud_directory_url}
)
self.assertFalse(self.nextcloud_handler.update_room_nextcloud_mapping.called)
self.assertFalse(self.nextcloud_handler.bind.called)
self.assertRaises(SynapseError)
self.assertEquals(400, channel.code)
self.assertEquals(
@ -95,7 +117,7 @@ class WatchaRoomNextcloudMappingEventTestCase(unittest.HomeserverTestCase):
{"nextcloudShare": "https://test/nextcloud/apps/files/?file=brandbook.pdf"}
)
self.assertFalse(self.nextcloud_handler.update_room_nextcloud_mapping.called)
self.assertFalse(self.nextcloud_handler.bind.called)
self.assertRaises(SynapseError)
self.assertEquals(400, channel.code)
self.assertEquals(
@ -103,43 +125,6 @@ class WatchaRoomNextcloudMappingEventTestCase(unittest.HomeserverTestCase):
json.loads(channel.result["body"])["error"],
)
class WatchaMembershipNextcloudSharingTestCase(unittest.HomeserverTestCase):
"""Tests that Nextcloud sharing is updated with membership event when the room is mapped with a Nextcloud directory"""
servlets = [
synapse.rest.admin.register_servlets_for_client_rest_resource,
room.register_servlets,
login.register_servlets,
]
def prepare(self, reactor, clock, hs):
self.store = hs.get_datastore()
self.creator = self.register_user("creator", "pass")
self.creator_tok = self.login("creator", "pass")
self.inviter = self.register_user("inviter", "pass")
self.inviter_tok = self.login("inviter", "pass")
self.room_id = self.helper.create_room_as(self.creator, tok=self.creator_tok)
# map a room with a Nextcloud directory :
self.get_success(
self.store.map_room_with_nextcloud_directory(self.room_id, "/directory", 1)
)
# mock some functions of WatchaRoomNextcloudMappingHandler
self.nextcloud_handler = hs.get_nextcloud_handler()
self.keycloak_client = self.nextcloud_handler.keycloak_client
self.nextcloud_client = self.nextcloud_handler.nextcloud_client
self.keycloak_client.get_user = simple_async_mock(
return_value={"id": "1234", "username": "creator"},
)
self.nextcloud_client.add_user_to_group = simple_async_mock()
self.nextcloud_client.remove_from_group = simple_async_mock()
def test_update_nextcloud_share_on_invite_and_join_event(self):
self.helper.invite(
self.room_id, self.creator, self.inviter, tok=self.creator_tok
@ -168,7 +153,7 @@ class WatchaMembershipNextcloudSharingTestCase(unittest.HomeserverTestCase):
self.render(request)
self.nextcloud_client.add_user_to_group.assert_called_once()
self.nextcloud_client.remove_from_group.assert_called_once()
self.nextcloud_client.remove_user_from_group.assert_called_once()
self.assertEqual(200, channel.code)
def test_update_nextcloud_share_on_kick_event(self):
@ -186,7 +171,7 @@ class WatchaMembershipNextcloudSharingTestCase(unittest.HomeserverTestCase):
self.render(request)
self.assertEquals(self.nextcloud_client.add_user_to_group.call_count, 2)
self.nextcloud_client.remove_from_group.assert_called_once()
self.nextcloud_client.remove_user_from_group.assert_called_once()
self.assertEqual(200, channel.code)
def test_update_nextcloud_share_with_an_unmapped_room(self):

@ -177,7 +177,7 @@ class WatchaRoomEventsStoreTestCase(unittest.HomeserverTestCase):
# Set mapping between a room and a nextcloud directory :
yield defer.ensureDeferred(
self.store.map_room_with_nextcloud_directory(
self.store.bind(
self.room_id, self.directory_path, self.share_id
)
)
@ -185,13 +185,13 @@ class WatchaRoomEventsStoreTestCase(unittest.HomeserverTestCase):
@defer.inlineCallbacks
def test_get_room_mapping_with_nextcloud_directory(self):
mapped_directory = yield defer.ensureDeferred(
self.store.get_nextcloud_directory_path_from_roomID(self.room_id)
self.store.get_path_from_room_id(self.room_id)
)
mapped_room = yield defer.ensureDeferred(
self.store.get_roomID_from_nextcloud_directory_path(self.directory_path)
self.store.get_room_id_from_path(self.directory_path)
)
share_id = yield defer.ensureDeferred(
self.store.get_nextcloud_share_id_from_roomID(self.room_id)
self.store.get_nextcloud_share_id_from_room_id(self.room_id)
)
self.assertEquals(mapped_directory, self.directory_path)
@ -201,16 +201,16 @@ class WatchaRoomEventsStoreTestCase(unittest.HomeserverTestCase):
@defer.inlineCallbacks
def test_delete_room_nextcloud_mapping(self):
yield defer.ensureDeferred(
self.store.deleted_room_mapping_with_nextcloud_directory(self.room_id)
self.store.unbind(self.room_id)
)
mapped_directory = yield defer.ensureDeferred(
self.store.get_nextcloud_directory_path_from_roomID(self.room_id)
self.store.get_path_from_room_id(self.room_id)
)
self.assertIsNone(mapped_directory)
share_id = yield defer.ensureDeferred(
self.store.get_nextcloud_share_id_from_roomID(self.room_id)
self.store.get_nextcloud_share_id_from_room_id(self.room_id)
)
self.assertIsNone(share_id)
@ -221,18 +221,18 @@ class WatchaRoomEventsStoreTestCase(unittest.HomeserverTestCase):
new_share_id = 2
yield defer.ensureDeferred(
self.store.map_room_with_nextcloud_directory(
self.store.bind(
self.room_id, new_directory_path, new_share_id
)
)
mapped_directory = yield defer.ensureDeferred(
self.store.get_nextcloud_directory_path_from_roomID(self.room_id)
self.store.get_path_from_room_id(self.room_id)
)
self.assertEquals(mapped_directory, new_directory_path)
share_id = yield defer.ensureDeferred(
self.store.get_nextcloud_share_id_from_roomID(self.room_id)
self.store.get_nextcloud_share_id_from_room_id(self.room_id)
)
self.assertEquals(share_id, new_share_id)

Loading…
Cancel
Save