mirror of https://github.com/watcha-fr/synapse
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 errorscode_spécifique_watcha
parent
f23b023caf
commit
d2c8a0b771
@ -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 |
@ -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", |
||||
) |
Loading…
Reference in new issue