From e0ce44cfc5290cc00aa7b2630ad7300347f2d2ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Wojtasik?= Date: Wed, 16 Feb 2022 07:42:23 +0100 Subject: [PATCH 01/10] Add MUC Light GraphQL schema --- priv/graphql/schemas/admin/admin_schema.gql | 4 ++ priv/graphql/schemas/admin/muc_light.gql | 48 +++++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 priv/graphql/schemas/admin/muc_light.gql diff --git a/priv/graphql/schemas/admin/admin_schema.gql b/priv/graphql/schemas/admin/admin_schema.gql index 4f663fda6e..8cffc62f95 100644 --- a/priv/graphql/schemas/admin/admin_schema.gql +++ b/priv/graphql/schemas/admin/admin_schema.gql @@ -18,6 +18,8 @@ type AdminQuery{ session: SessionAdminQuery "Stanza management" stanza: StanzaAdminQuery + "MUC Light room management" + muc_light: MUCLightAdminQuery } """ @@ -33,4 +35,6 @@ type AdminMutation @protected{ session: SessionAdminMutation "Stanza management" stanza: StanzaAdminMutation + "MUC Light room management" + muc_light: MUCLightAdminMutation } diff --git a/priv/graphql/schemas/admin/muc_light.gql b/priv/graphql/schemas/admin/muc_light.gql new file mode 100644 index 0000000000..85dc6bd668 --- /dev/null +++ b/priv/graphql/schemas/admin/muc_light.gql @@ -0,0 +1,48 @@ +""" +Allow admin to manage Multi-User Chat Light rooms. +""" +type MUCLightAdminMutation @protected{ + "Create a MUC light room under the given XMPP hostname" + createRoom(domain: String!, name: String!, owner: JID!, subject: String!, id: String): Room + "Change configuration of a MUC Light room" + changeRoomConfiguration(id: String!, domain: String!, name: String!, owner: JID!, subject: String!): Room + "Invite a user to a MUC Light room" + inviteUser(domain: String!, name: String!, sender: JID!, recipient: JID!): String + "Remove a MUC Light room" + deleteRoom(id: String!, domain: String!): String + "Kick a user from a MUC Light room" + kickUser(domain: String!, id: String!, user: JID!): String + "Send a message to a MUC Light room" + sendMessageToRoom(domain: String!, name: String!, from: JID!, body: String!): String +} + +""" +Allow admin to get information about Multi-User Chat Light rooms. +""" +type MUCLightAdminQuery @protected{ + "Get the MUC Light room archived messages" + getRoomMessages(domain: String!, id: String!, pageSize: Int!, before: DateTime): StanzasPayload + "Get configuration of the MUC Light room" + getRoomConfig(domain: String!, id: String!): Room + "Get users list of given MUC Light room" + listRoomUsers(domain: String!, id: String!): [RoomUser!] + "Get the list of MUC Light rooms that the user participates in" + listUserRooms(user: JID!): [JID!] +} + +type RoomPayload{ + message: String! + room: Room +} + +type Room{ + jid: JID + name: String + subject: String + participants: [RoomUser!] +} + +type RoomUser{ + jid: JID + affiliance: String +} From cdca4be236991b0eaf99cf37e52b701c27e5b890 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Wojtasik?= Date: Wed, 16 Feb 2022 07:45:44 +0100 Subject: [PATCH 02/10] Extract common commands to API module --- .../mongoose_client_api_rooms_config.erl | 9 +- src/muc_light/mod_muc_light.erl | 4 +- src/muc_light/mod_muc_light_api.erl | 385 ++++++++++++++++++ src/muc_light/mod_muc_light_commands.erl | 231 ++--------- 4 files changed, 436 insertions(+), 193 deletions(-) create mode 100644 src/muc_light/mod_muc_light_api.erl diff --git a/src/mongoose_client_api/mongoose_client_api_rooms_config.erl b/src/mongoose_client_api/mongoose_client_api_rooms_config.erl index 18d13559ff..a9b4845ce8 100644 --- a/src/mongoose_client_api/mongoose_client_api_rooms_config.erl +++ b/src/mongoose_client_api/mongoose_client_api_rooms_config.erl @@ -54,11 +54,11 @@ from_json(Req, State) -> handle_request(Method, JSONData, Req, State) -> case handle_request_by_method(Method, JSONData, Req, State) of - ok -> + {ok, _} -> {true, Req, State}; - {error, internal, not_allowed} -> + {not_allowed, _} -> mongoose_client_api:forbidden_request(Req, State); - {error, internal, _} -> + {_, _} -> {false, Req, State} end. @@ -67,4 +67,5 @@ handle_request_by_method(<<"PUT">>, Req, State) -> mongoose_client_api_rooms:assert_room_id_set(Req, State), #{user := User, jid := #jid{lserver = Server}, room_id := RoomID} = State, - mod_muc_light_commands:change_room_config(Server, RoomID, Name, User, Subject). + UserJID = jid:from_binary(User), + mod_muc_light_api:change_room_config(Server, RoomID, Name, UserJID, Subject). diff --git a/src/muc_light/mod_muc_light.erl b/src/muc_light/mod_muc_light.erl index c828de2e43..e2042cbf9b 100644 --- a/src/muc_light/mod_muc_light.erl +++ b/src/muc_light/mod_muc_light.erl @@ -142,12 +142,12 @@ try_to_create_room(CreatorJid, RoomJID, #create{raw_config = RawConfig} = Creati Error end. --spec change_room_config(UserJid :: jid:jid(), RoomID :: jid:resource(), +-spec change_room_config(UserJid :: jid:jid(), RoomID :: jid:user(), MUCLightDomain :: jid:server(), ConfigReq :: config_req_props(), Acc :: mongoose_acc:t()) -> {ok, jid:jid(), config_req_props()} - | {error, validation_error() | bad_request | not_allowed}. + | {error, validation_error() | bad_request | not_allowed | not_exists | item_not_found}. change_room_config(UserJid, RoomID, MUCLightDomain, ConfigReq, Acc1) -> RoomJID = jid:make(RoomID, MUCLightDomain, <<>>), {Acc2, AffUsersRes} = get_room_affiliations_from_acc(Acc1, RoomJID), diff --git a/src/muc_light/mod_muc_light_api.erl b/src/muc_light/mod_muc_light_api.erl new file mode 100644 index 0000000000..264204d56d --- /dev/null +++ b/src/muc_light/mod_muc_light_api.erl @@ -0,0 +1,385 @@ +%% @doc Provide an interface for frontends (like graphql or ctl) to manage MUC Light rooms. +-module(mod_muc_light_api). + +-export([create_room/5, + invite_to_room/4, + change_room_config/5, + change_affiliation/5, + remove_user_from_room/4, + send_message/4, + delete_room/3, + delete_room/2, + get_room_messages/4, + get_user_rooms/1, + get_room_info/2, + get_room_aff/2 + ]). + +-include("mod_muc_light.hrl"). +-include("mongoose.hrl"). +-include("jlib.hrl"). +-include("mongoose_rsm.hrl"). + +-type create_room_result() :: {ok, room()} | {exist | + max_occupants_reached | + validation_error, iolist()}. + +-type change_room_config_result() :: {ok, room()} | {wrong_user | + not_allowed | + not_exists | + validation_error, iolist()}. + +-type get_room_messages_result() :: {ok, []} | {domain_not_found | internal, iolist()}. + +-type invite_to_room_result() :: {ok | user_without_room | not_found, iolist()}. + +-type get_room_info_result() :: {ok, map()} | {domain_not_found | not_exists, iolist()}. + +-type get_room_aff_result() :: {ok, [aff_user()]} | {domain_not_found | not_exists, iolist()}. + +-type room() :: #{jid := jid:jid(), + name := binary(), + subject := binary(), + aff_users := aff_users() + }. + +-export_type([room/0, create_room_result/0]). + +-define(ROOM_NOT_EXIST_RESULT, {not_exists, "Room does not exist"}). +-define(VALIDATION_ERROR_RESULT(Key, Reason), + {validation_error, io_lib:format("Validation failed for key: ~p with reason ~p", + [Key, Reason])}). +-define(USER_WITHOUT_ROOM_RESULT, {user_without_room, "Given user does not occupy any room"}). + +-spec create_room(jid:lserver(), binary(), binary(), jid:jid(), binary()) -> create_room_result(). +create_room(Domain, RoomId, RoomTitle, CreatorJID, Subject) -> + case get_muc_hosts(Domain) of + {ok, _HostType, MUCLightDomain} -> + MUCServiceJID = jid:make_bare(RoomId, MUCLightDomain), + Config = make_room_config(RoomTitle, Subject), + case mod_muc_light:try_to_create_room(CreatorJID, MUCServiceJID, Config) of + {ok, RoomJID, #create{aff_users = AffUsers}} -> + {ok, make_room(RoomJID, RoomTitle, Subject, AffUsers)}; + {error, exists} -> + {exist, "Room already exists"}; + {error, max_occupants_reached} -> + {max_occupants_reached, "Max occupants number reached"}; + {error, {Key, Reason}} -> + ?VALIDATION_ERROR_RESULT(Key, Reason) + end; + Error -> + Error + end. + +-spec invite_to_room(jid:lserver(), binary(), jid:jid(), jid:jid()) -> invite_to_room_result(). +invite_to_room(Domain, RoomName, SenderJID, RecipientJID) -> + case get_muc_hosts(Domain) of + {ok, HostType, MUCServer} -> + RecipientBin = jid:to_binary(jid:to_bare(RecipientJID)), + case muc_light_room_name_to_jid_and_aff(HostType, SenderJID, RoomName, MUCServer) of + {ok, R, _Aff} -> + S = jid:to_bare(SenderJID), + Changes = query(?NS_MUC_LIGHT_AFFILIATIONS, + [affiliate(RecipientBin, <<"member">>)]), + ejabberd_router:route(S, R, iq(jid:to_binary(S), jid:to_binary(R), + <<"set">>, [Changes])), + {ok, "User invited successfully"}; + {error, given_user_does_not_occupy_any_room} -> + ?USER_WITHOUT_ROOM_RESULT; + {error, not_exists} -> + ?ROOM_NOT_EXIST_RESULT + end; + Error -> + Error + end. + +-spec change_room_config(jid:lserver(), binary(), binary(), jid:jid(), binary()) -> + change_room_config_result(). +change_room_config(Domain, RoomID, RoomName, UserJID, Subject) -> + case get_muc_hosts(Domain) of + {ok, HostType, MUCLightDomain} -> + LServer = jid:nameprep(Domain), + UserUS = jid:to_bare(UserJID), + ConfigReq = #config{ raw_config = + [{<<"roomname">>, RoomName}, {<<"subject">>, Subject}]}, + Acc = mongoose_acc:new(#{location => ?LOCATION, lserver => LServer, + host_type => HostType}), + case mod_muc_light:change_room_config(UserUS, RoomID, MUCLightDomain, ConfigReq, Acc) of + {ok, RoomJID, _} -> + {ok, make_room(RoomJID, RoomName, Subject, [])}; + {error, item_not_found} -> + {wrong_user, "The given user is not room participant"}; + {error, not_allowed} -> + {not_allowed, "The given user has not permission to change config"}; + {error, not_exists} -> + ?ROOM_NOT_EXIST_RESULT; + {error, {error, {Key, Reason}}} -> + ?VALIDATION_ERROR_RESULT(Key, Reason) + end; + Error -> + Error + end. + +-spec change_affiliation(jid:lserver(), binary(), jid:jid(), jid:jid(), binary()) -> + ok | {domain_not_found, iolist()}. +change_affiliation(Domain, RoomID, SenderJID, RecipientJID, Affiliation) -> + case get_muc_hosts(Domain) of + {ok, _HostType, MUCLightDomain} -> + RecipientBare = jid:to_bare(RecipientJID), + R = jid:make_bare(RoomID, MUCLightDomain), + S = jid:to_bare(SenderJID), + Changes = query(?NS_MUC_LIGHT_AFFILIATIONS, + [affiliate(jid:to_binary(RecipientBare), Affiliation)]), + ejabberd_router:route(S, R, iq(jid:to_binary(S), jid:to_binary(R), + <<"set">>, [Changes])), + ok; + Error -> + Error + end. + +-spec remove_user_from_room(jid:lserver(), binary(), jid:jid(), jid:jid()) -> + {ok | domain_not_found, iolist()}. +remove_user_from_room(Domain, RoomID, SenderJID, RecipientJID) -> + case change_affiliation(Domain, RoomID, SenderJID, RecipientJID, <<"none">>) of + ok -> + {ok, io_lib:format("User ~s kicked successfully", [jid:to_binary(RecipientJID)])}; + Error -> + Error + end. + +-spec send_message(jid:lserver(), binary(), jid:jid(), binary()) -> + {ok | domain_not_found | room_not_found | user_without_room, iolist()}. +send_message(Domain, RoomName, SenderJID, Message) -> + case get_muc_hosts(Domain) of + {ok, HostType, MUCServer} -> + Body = #xmlel{name = <<"body">>, + children = [ #xmlcdata{ content = Message } ] + }, + Stanza = #xmlel{name = <<"message">>, + attrs = [{<<"type">>, <<"groupchat">>}], + children = [ Body ] + }, + SenderBare = jid:to_bare(SenderJID), + SenderUS = jid:to_lus(SenderBare), + case mod_muc_light_db_backend:get_user_rooms(HostType, SenderUS, MUCServer) of + [] -> + ?USER_WITHOUT_ROOM_RESULT; + RoomJIDs when is_list(RoomJIDs) -> + FindFun = find_room_and_user_aff_by_room_name(HostType, RoomName, SenderUS), + case lists:foldl(FindFun, none, RoomJIDs) of + {ok, {RU, MUCServer}, _Aff} -> + R = jid:make_bare(RU, MUCServer), + ejabberd_router:route(SenderBare, R, Stanza), + {ok, "Message send successfully"}; + none -> + {room_not_found, "Room does not found"} + end + end; + Error -> + Error + end. + +-spec delete_room(jid:lserver(), binary(), jid:jid()) -> + { ok | domain_not_found | not_exists | user_without_room, iolist()}. +delete_room(Domain, RoomName, OwnerJID) -> + OwnerBare = jid:to_bare(OwnerJID), + case get_muc_hosts(Domain) of + {ok, HostType, MUCServer} -> + Res = case muc_light_room_name_to_jid_and_aff(HostType, OwnerBare, + RoomName, MUCServer) of + {ok, RoomJID, owner} -> + mod_muc_light:delete_room(jid:to_lus(RoomJID)); + {ok, _, _} -> + {error, not_allowed}; + {error, _} = Err -> + Err + end, + format_delete_error_message(Res); + Error -> + Error + end. + +-spec delete_room(jid:lserver(), binary()) -> { ok | domain_not_found | not_exists, iolist()}. +delete_room(Domain, RoomID) -> + case get_muc_hosts(Domain) of + {ok, _HostType, MUCLightDomain} -> + Res = mod_muc_light:delete_room({RoomID, MUCLightDomain}), + format_delete_error_message(Res); + Error -> + Error + end. + +-spec get_room_messages(jid:lserver(), binary(), integer() | undefined, + mod_mam:unix_timestamp() | undefined) -> get_room_messages_result(). +get_room_messages(Domain, RoomID, PageSize, Before) -> + case get_muc_hosts(Domain) of + {ok, HostType, MUCLightDomain} -> + RoomJID = jid:make_bare(RoomID, MUCLightDomain), + Now = os:system_time(microsecond), + ArchiveID = mod_mam_muc:archive_id_int(HostType, RoomJID), + End = maybe_before(Before, Now), + RSM = #rsm_in{direction = before, id = undefined}, + R = mod_mam_muc:lookup_messages(HostType, + #{archive_id => ArchiveID, + owner_jid => RoomJID, + rsm => RSM, + borders => undefined, + start_ts => undefined, + end_ts => End, + now => Now, + with_jid => undefined, + search_text => undefined, + page_size => PageSize, + limit_passed => true, + max_result_limit => 50, + is_simple => true}), + case R of + {ok, {_, _, Messages}} -> + {ok, Messages}; + {error, Term} -> + {internal, io_lib:format("Internal error occured ~p", [Term])} + end; + Error -> + Error + end. + +-spec get_room_info(jid:lserver(), binary()) -> get_room_info_result(). +get_room_info(Domain, RoomID) -> + case get_muc_hosts(Domain) of + {ok, HostType, MUCServer} -> + case mod_muc_light_db_backend:get_info(HostType, {RoomID, MUCServer}) of + {ok, [{roomname, Name}, {subject, Subject}], AffUsers, _Version} -> + {ok, make_room(jid:make_bare(RoomID, MUCServer), Name, Subject, AffUsers)}; + {error, not_exists} -> + ?ROOM_NOT_EXIST_RESULT + end; + Error -> + Error + end. + +-spec get_room_aff(jid:lserver(), binary()) -> get_room_aff_result(). +get_room_aff(Domain, RoomID) -> + case get_room_info(Domain, RoomID) of + {ok, #{aff_users := AffUsers}} -> + {ok, AffUsers}; + Error -> + Error + end. + +-spec get_user_rooms(jid:jid()) -> {ok, [RoomUS :: jid:simple_bare_jid()]} | + {domain_not_found, iolist()}. +get_user_rooms(#jid{lserver = LServer} = UserJID) -> + case get_muc_hosts(LServer) of + {ok, HostType, MUCServer} -> + UserUS = jid:to_lus(UserJID), + {ok, mod_muc_light_db_backend:get_user_rooms(HostType, UserUS, MUCServer)}; + Error -> + Error + end. + + %% Internal + +-spec get_muc_hosts(jid:lserver()) -> {ok, mongooseim:host_type(), jid:lserver()} | + {domain_not_found, iolist()}. +get_muc_hosts(LServer) -> + case mongoose_domain_api:get_domain_host_type(LServer) of + {ok, HostType} -> + {ok, HostType, mod_muc_light_utils:server_host_to_muc_host(HostType, LServer)}; + {error, not_found} -> + {domain_not_found, io_lib:format("Domain ~s does not exist", [LServer])} + end. + +make_room(JID, Name, Subject, AffUsers) -> + #{jid => JID, name => Name, subject => Subject, aff_users => AffUsers}. + +format_delete_error_message(ok) -> + {ok, "Room deleted successfully!"}; +format_delete_error_message({error, not_allowed}) -> + {not_allowed, "You cannot delete this room"}; +format_delete_error_message({error, not_exists}) -> + {not_exists, "Cannot remove not existing room"}; +format_delete_error_message({error, given_user_does_not_occupy_any_room}) -> + ?USER_WITHOUT_ROOM_RESULT. + +iq(To, From, Type, Children) -> + UUID = uuid:uuid_to_string(uuid:get_v4(), binary_standard), + #xmlel{name = <<"iq">>, + attrs = [{<<"from">>, From}, + {<<"to">>, To}, + {<<"type">>, Type}, + {<<"id">>, UUID}], + children = Children + }. + +query(NS, Children) when is_binary(NS), is_list(Children) -> + #xmlel{name = <<"query">>, + attrs = [{<<"xmlns">>, NS}], + children = Children + }. + +affiliate(JID, Kind) when is_binary(JID), is_binary(Kind) -> + #xmlel{name = <<"user">>, + attrs = [{<<"affiliation">>, Kind}], + children = [ #xmlcdata{ content = JID } ] + }. + +-spec make_room_config(binary(), binary()) -> create_req_props(). +make_room_config(Name, Subject) -> + #create{raw_config = [{<<"roomname">>, Name}, + {<<"subject">>, Subject}] + }. + +-spec muc_light_room_name_to_jid_and_aff(HostType :: mongooseim:host_type(), + UserJID :: jid:jid(), + RoomName :: binary(), + Domain :: jid:lserver()) -> + {ok, jid:jid(), aff()} | {error, given_user_does_not_occupy_any_room} | {error, not_exists}. +muc_light_room_name_to_jid_and_aff(HostType, UserJID, RoomName, MUCServer) -> + UserUS = jid:to_lus(UserJID), + case mod_muc_light_db_backend:get_user_rooms(HostType, UserUS, MUCServer) of + [] -> + {error, given_user_does_not_occupy_any_room}; + RoomUSs when is_list(RoomUSs) -> + FindFun = find_room_and_user_aff_by_room_name(HostType, RoomName, UserUS), + case lists:foldl(FindFun, none, RoomUSs) of + {ok, {RU, MUCServer}, UserAff} -> + {ok, jid:make_bare(RU, MUCServer), UserAff}; + none -> + {error, not_exists} + end + end. + +-spec get_room_name_and_user_aff(mongooseim:host_type(), RoomUS :: jid:simple_bare_jid(), + UserUS :: jid:simple_bare_jid()) -> + {ok, RoomName :: binary(), UserAff :: aff()} | {error, not_exists}. +get_room_name_and_user_aff(HostType, RoomUS, UserUS) -> + case mod_muc_light_db_backend:get_info(HostType, RoomUS) of + {ok, Cfg, Affs, _} -> + {roomname, RoomName} = lists:keyfind(roomname, 1, Cfg), + {_, UserAff} = lists:keyfind(UserUS, 1, Affs), + {ok, RoomName, UserAff}; + Error -> + Error + end. + +-type find_room_acc() :: {ok, RoomUS :: jid:simple_bare_jid(), UserAff :: aff()} | none. + +-spec find_room_and_user_aff_by_room_name(mongooseim:host_type(), RoomName :: binary(), + UserUS :: jid:simple_bare_jid()) -> + fun((RoomUS :: jid:simple_bare_jid(), find_room_acc()) -> find_room_acc()). +find_room_and_user_aff_by_room_name(HostType, RoomName, UserUS) -> + fun (RoomUS, none) -> + case get_room_name_and_user_aff(HostType, RoomUS, UserUS) of + {ok, RoomName, UserAff} -> + {ok, RoomUS, UserAff}; + _ -> + none + end; + (_, Acc) when Acc =/= none -> + Acc + end. + +maybe_before(undefined, Now) -> + Now; +maybe_before(Timestamp, _) -> + Timestamp. diff --git a/src/muc_light/mod_muc_light_commands.erl b/src/muc_light/mod_muc_light_commands.erl index b7f831f8ef..d759ed5b41 100644 --- a/src/muc_light/mod_muc_light_commands.erl +++ b/src/muc_light/mod_muc_light_commands.erl @@ -32,12 +32,7 @@ -export([delete_room/3]). -export([change_room_config/5]). --ignore_xref([delete_room/3, invite_to_room/4, send_message/4]). - --include("mod_muc_light.hrl"). --include("mongoose.hrl"). --include("jlib.hrl"). - +-ignore_xref([delete_room/3, invite_to_room/4, send_message/4, change_room_config/5]). %%-------------------------------------------------------------------- %% `gen_mod' callbacks @@ -165,194 +160,56 @@ commands() -> %%-------------------------------------------------------------------- create_unique_room(Domain, RoomName, Creator, Subject) -> - create_room(Domain, <<>>, RoomName, Creator, Subject). + CreatorJID = jid:from_binary(Creator), + case mod_muc_light_api:create_room(Domain, <<>>, RoomName, CreatorJID, Subject) of + {ok, #{jid := JID}} -> jid:to_binary(JID); + Error -> format_err_result(Error) + end. create_identifiable_room(Domain, Identifier, RoomName, Creator, Subject) -> - create_room(Domain, Identifier, RoomName, Creator, Subject). - -invite_to_room(Domain, RoomName, Sender, Recipient0) -> - Recipient1 = jid:binary_to_bare(Recipient0), - case muc_light_room_name_to_jid_and_aff(jid:from_binary(Sender), RoomName, Domain) of - {ok, R, _Aff} -> - S = jid:binary_to_bare(Sender), - Changes = query(?NS_MUC_LIGHT_AFFILIATIONS, - [affiliate(jid:to_binary(Recipient1), <<"member">>)]), - ejabberd_router:route(S, R, iq(jid:to_binary(S), jid:to_binary(R), - <<"set">>, [Changes])); - {error, given_user_does_not_occupy_any_room} -> - {error, forbidden, "given user does not occupy any room"}; - {error, not_found} -> - {error, not_found, "room does not exist"} + CreatorJID = jid:from_binary(Creator), + case mod_muc_light_api:create_room(Domain, Identifier, RoomName, CreatorJID, Subject) of + {ok, #{jid := JID}} -> jid:to_binary(JID); + Error -> format_err_result(Error) end. -change_affiliation(Domain, RoomID, Sender, Recipient0, Affiliation) -> - Recipient1 = jid:binary_to_bare(Recipient0), - LServer = jid:nameprep(Domain), - HostType = mod_muc_light_utils:server_host_to_host_type(LServer), - MUCLightDomain = mod_muc_light_utils:server_host_to_muc_host(HostType, LServer), - R = jid:make(RoomID, MUCLightDomain, <<>>), - S = jid:binary_to_bare(Sender), - Changes = query(?NS_MUC_LIGHT_AFFILIATIONS, - [affiliate(jid:to_binary(Recipient1), Affiliation)]), - ejabberd_router:route(S, R, iq(jid:to_binary(S), jid:to_binary(R), - <<"set">>, [Changes])). +invite_to_room(Domain, RoomName, Sender, Recipient) -> + SenderJID = jid:from_binary(Sender), + RecipientJID = jid:from_binary(Recipient), + Result = mod_muc_light_api:invite_to_room(Domain, RoomName, SenderJID, RecipientJID), + format_result_no_msg(Result). + +change_affiliation(Domain, RoomID, Sender, Recipient, Affiliation) -> + % FIXME use mod_muc_light_api instead of this in the client api + SenderJID = jid:from_binary(Sender), + RecipientJID = jid:from_binary(Recipient), + mod_muc_light_api:change_affiliation(Domain, RoomID, SenderJID, RecipientJID, Affiliation). change_room_config(Domain, RoomID, RoomName, User, Subject) -> - LServer = jid:nameprep(Domain), - HostType = mod_muc_light_utils:server_host_to_host_type(LServer), - MUCLightDomain = mod_muc_light_utils:server_host_to_muc_host(HostType, LServer), - UserUS = jid:binary_to_bare(User), - ConfigReq = #config{ raw_config = - [{<<"roomname">>, RoomName}, {<<"subject">>, Subject}]}, - Acc = mongoose_acc:new(#{location => ?LOCATION, lserver => LServer, host_type => HostType}), - case mod_muc_light:change_room_config(UserUS, RoomID, MUCLightDomain, ConfigReq, Acc) of - {ok, _RoomJID, _} -> - ok; - {error, Reason} -> - {error, internal, Reason} - end. + UserJID = jid:from_binary(User), + Result = mod_muc_light_api:change_room_config(Domain, RoomID, RoomName, UserJID, Subject), + format_result_no_msg(Result). send_message(Domain, RoomName, Sender, Message) -> - Body = #xmlel{name = <<"body">>, - children = [ #xmlcdata{ content = Message } ] - }, - Stanza = #xmlel{name = <<"message">>, - attrs = [{<<"type">>, <<"groupchat">>}], - children = [ Body ] - }, - S = jid:binary_to_bare(Sender), - case get_user_rooms(jid:to_lus(S), Domain) of - [] -> - {error, denied, "given user does not occupy any room"}; - RoomJIDs when is_list(RoomJIDs) -> - FindFun = find_room_and_user_aff_by_room_name(RoomName, jid:to_lus(S)), - {ok, {RU, RS}, _Aff} = lists:foldl(FindFun, none, RoomJIDs), - true = is_subdomain(RS, Domain), - R = jid:make(RU, RS, <<>>), - ejabberd_router:route(S, R, Stanza) - end. + SenderJID = jid:from_binary(Sender), + Result = mod_muc_light_api:send_message(Domain, RoomName, SenderJID, Message), + format_result_no_msg(Result). --spec delete_room(DomainName :: binary(), RoomName :: binary(), - Owner :: binary()) -> - ok | {error, atom(), term()}. +-spec delete_room(jid:lserver(), binary(), jid:literal_jid()) -> ok | {error, atom(), term()}. delete_room(DomainName, RoomName, Owner) -> - OwnerJID = jid:binary_to_bare(Owner), - case muc_light_room_name_to_jid_and_aff(OwnerJID, RoomName, DomainName) of - {ok, RoomJID, owner} -> mod_muc_light:delete_room(jid:to_lus(RoomJID)); - {ok, _, _} -> {error, denied, "you can not delete this room"}; - {error, given_user_does_not_occupy_any_room} -> {error, denied, "given user does not occupy this room"}; - {error, not_found} -> {error, not_found, "room does not exist"} - end. - -%%-------------------------------------------------------------------- -%% Ancillary -%%-------------------------------------------------------------------- - -create_room(Domain, RoomId, RoomTitle, Creator, Subject) -> - LServer = jid:nameprep(Domain), - HostType = mod_muc_light_utils:server_host_to_host_type(LServer), - MUCLightDomain = mod_muc_light_utils:server_host_to_muc_host(HostType, LServer), - CreatorJid = jid:from_binary(Creator), - MUCServiceJID = jid:make(RoomId, MUCLightDomain, <<>>), - Config = make_room_config(RoomTitle, Subject), - case mod_muc_light:try_to_create_room(CreatorJid, MUCServiceJID, Config) of - {ok, RoomJid, _} -> - jid:to_binary(RoomJid); - {error, exists} -> - {error, denied, "Room already exists"}; - {error, Reason} -> - {error, internal, Reason} - end. - -make_room_config(Name, Subject) -> - #create{raw_config = [{<<"roomname">>, Name}, - {<<"subject">>, Subject}] - }. - --spec muc_light_room_name_to_jid_and_aff(UserJID :: jid:jid(), - RoomName :: binary(), - Domain :: jid:lserver()) -> - {ok, jid:jid(), aff()} | {error, given_user_does_not_occupy_any_room} | {error, not_found}. -muc_light_room_name_to_jid_and_aff(UserJID, RoomName, Domain) -> - UserUS = jid:to_lus(UserJID), - case get_user_rooms(UserUS, Domain) of - [] -> - {error, given_user_does_not_occupy_any_room}; - RoomUSs when is_list(RoomUSs) -> - FindFun = find_room_and_user_aff_by_room_name(RoomName, UserUS), - case lists:foldl(FindFun, none, RoomUSs) of - {ok, {RU, RS}, UserAff} -> - true = is_subdomain(RS, Domain), - {ok, jid:make(RU, RS, <<>>), UserAff}; - none -> - {error, not_found} - end - end. - --spec get_user_rooms(UserUS :: jid:simple_bare_jid(), Domain :: jid:lserver()) -> - [jid:simple_bare_jid()]. -get_user_rooms({_, UserS} = UserUS, Domain) -> - HostType = mod_muc_light_utils:server_host_to_host_type(UserS), - mod_muc_light_db_backend:get_user_rooms(HostType, UserUS, Domain). - --spec get_room_name_and_user_aff(RoomUS :: jid:simple_bare_jid(), - UserUS :: jid:simple_bare_jid()) -> - {ok, RoomName :: binary(), UserAff :: aff()} | {error, not_exists}. -get_room_name_and_user_aff(RoomUS, {_, UserS} = UserUS) -> - HostType = mod_muc_light_utils:server_host_to_host_type(UserS), - case mod_muc_light_db_backend:get_info(HostType, RoomUS) of - {ok, Cfg, Affs, _} -> - {roomname, RoomName} = lists:keyfind(roomname, 1, Cfg), - {_, UserAff} = lists:keyfind(UserUS, 1, Affs), - {ok, RoomName, UserAff}; - Error -> - Error - end. - --type find_room_acc() :: {ok, RoomUS :: jid:simple_bare_jid(), UserAff :: aff()} | none. - --spec find_room_and_user_aff_by_room_name(RoomName :: binary(), - UserUS :: jid:simple_bare_jid()) -> - fun((RoomUS :: jid:simple_bare_jid(), find_room_acc()) -> find_room_acc()). -find_room_and_user_aff_by_room_name(RoomName, UserUS) -> - fun (RoomUS, none) -> - case get_room_name_and_user_aff(RoomUS, UserUS) of - {ok, RoomName, UserAff} -> - {ok, RoomUS, UserAff}; - _ -> - none - end; - (_, Acc) when Acc =/= none -> - Acc - end. - -is_subdomain(Child, Parent) -> - %% Example input Child = <<"muclight.localhost">> and Parent = - %% <<"localhost">> - case binary:match(Child, Parent) of - nomatch -> false; - {_, _} -> true - end. - -iq(To, From, Type, Children) -> - UUID = uuid:uuid_to_string(uuid:get_v4(), binary_standard), - #xmlel{name = <<"iq">>, - attrs = [{<<"from">>, From}, - {<<"to">>, To}, - {<<"type">>, Type}, - {<<"id">>, UUID}], - children = Children - }. - -query(NS, Children) when is_binary(NS), is_list(Children) -> - #xmlel{name = <<"query">>, - attrs = [{<<"xmlns">>, NS}], - children = Children - }. - -affiliate(JID, Kind) when is_binary(JID), is_binary(Kind) -> - #xmlel{name = <<"user">>, - attrs = [{<<"affiliation">>, Kind}], - children = [ #xmlcdata{ content = JID } ] - }. - + OwnerJID = jid:from_binary(Owner), + Result = mod_muc_light_api:delete_room(DomainName, RoomName, OwnerJID), + format_result_no_msg(Result). + +format_result_no_msg({ok, _}) -> ok; +format_result_no_msg(Res) -> format_err_result(Res). + +format_err_result({ResStatus, Msg}) when not_exists =:= ResStatus; + room_not_found =:= ResStatus; + domain_not_found =:= ResStatus -> + {error, not_found, Msg}; +format_err_result({ResStatus, Msg}) when exist =:= ResStatus; + not_allowed =:= ResStatus; + user_without_room =:= ResStatus -> + {error, denied, Msg}; +format_err_result({_, Reason}) -> {error, internal, Reason}. From 3bec1eb5b45dd35ae6e0fb7b5ec2c2f380374ad1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Wojtasik?= Date: Wed, 16 Feb 2022 07:54:17 +0100 Subject: [PATCH 03/10] Implement resolvers for MUC Light admin queries --- .../admin/mongoose_graphql_admin_mutation.erl | 2 + .../admin/mongoose_graphql_admin_query.erl | 2 + ...goose_graphql_muc_light_admin_mutation.erl | 68 +++++++++++++++++++ ...mongoose_graphql_muc_light_admin_query.erl | 63 +++++++++++++++++ .../mongoose_graphql_stanza_admin_query.erl | 10 +-- src/graphql/mongoose_graphql.erl | 2 + .../mongoose_graphql_muc_light_helper.erl | 12 ++++ .../mongoose_graphql_stanza_helper.erl | 11 +++ 8 files changed, 161 insertions(+), 9 deletions(-) create mode 100644 src/graphql/admin/mongoose_graphql_muc_light_admin_mutation.erl create mode 100644 src/graphql/admin/mongoose_graphql_muc_light_admin_query.erl create mode 100644 src/graphql/mongoose_graphql_muc_light_helper.erl create mode 100644 src/graphql/mongoose_graphql_stanza_helper.erl diff --git a/src/graphql/admin/mongoose_graphql_admin_mutation.erl b/src/graphql/admin/mongoose_graphql_admin_mutation.erl index ec07208772..fcb6c974d8 100644 --- a/src/graphql/admin/mongoose_graphql_admin_mutation.erl +++ b/src/graphql/admin/mongoose_graphql_admin_mutation.erl @@ -10,6 +10,8 @@ execute(_Ctx, _Obj, <<"domains">>, _Args) -> {ok, admin}; execute(_Ctx, _Obj, <<"account">>, _Args) -> {ok, account}; +execute(_Ctx, _Obj, <<"muc_light">>, _Args) -> + {ok, muc_light}; execute(_Ctx, _Obj, <<"session">>, _Opts) -> {ok, session}; execute(_Ctx, _Obj, <<"stanza">>, _) -> diff --git a/src/graphql/admin/mongoose_graphql_admin_query.erl b/src/graphql/admin/mongoose_graphql_admin_query.erl index bb06dc4310..ee71ebbeab 100644 --- a/src/graphql/admin/mongoose_graphql_admin_query.erl +++ b/src/graphql/admin/mongoose_graphql_admin_query.erl @@ -10,6 +10,8 @@ execute(_Ctx, _Obj, <<"domains">>, _Args) -> {ok, admin}; execute(_Ctx, _Obj, <<"account">>, _Args) -> {ok, account}; +execute(_Ctx, _Obj, <<"muc_light">>, _Args) -> + {ok, muc_light}; execute(_Ctx, _Obj, <<"session">>, _Opts) -> {ok, session}; execute(_Ctx, _Obj, <<"stanza">>, _Opts) -> diff --git a/src/graphql/admin/mongoose_graphql_muc_light_admin_mutation.erl b/src/graphql/admin/mongoose_graphql_muc_light_admin_mutation.erl new file mode 100644 index 0000000000..7cc1b373e4 --- /dev/null +++ b/src/graphql/admin/mongoose_graphql_muc_light_admin_mutation.erl @@ -0,0 +1,68 @@ +-module(mongoose_graphql_muc_light_admin_mutation). + +-export([execute/4]). + +-ignore_xref([execute/4]). + +-include("../mongoose_graphql_types.hrl"). + +-import(mongoose_graphql_helper, [make_error/2, format_result/2]). +-import(mongoose_graphql_muc_light_helper, [make_room/1, make_ok_user/1]). + +execute(_Ctx, _Obj, <<"createRoom">>, Args) -> + create_room(Args); +execute(_Ctx, _Obj, <<"changeRoomConfiguration">>, Args) -> + change_room_config(Args); +execute(_Ctx, _Obj, <<"inviteUser">>, Args) -> + invite_user(Args); +execute(_Ctx, _Obj, <<"deleteRoom">>, Args) -> + delete_room(Args); +execute(_Ctx, _Obj, <<"kickUser">>, Args) -> + kick_user(Args); +execute(_Ctx, _Obj, <<"sendMessageToRoom">>, Args) -> + send_msg_to_room(Args). + +-spec create_room(map()) -> {ok, map()} | {error, resolver_error()}. +create_room(#{<<"id">> := null} = Args) -> + create_room(Args#{<<"id">> => <<>>}); +create_room(#{<<"id">> := RoomID, <<"domain">> := Domain, <<"name">> := RoomName, + <<"owner">> := CreatorJID, <<"subject">> := Subject}) -> + case mod_muc_light_api:create_room(Domain, RoomID, RoomName, CreatorJID, Subject) of + {ok, Room} -> + {ok, make_room(Room)}; + Err -> + make_error(Err, #{domain => Domain, roomID => RoomID, creator => CreatorJID}) + end. + +-spec change_room_config(map()) -> {ok, map()} | {error, resolver_error()}. +change_room_config(#{<<"id">> := RoomID, <<"domain">> := Domain, <<"name">> := RoomName, + <<"owner">> := OwnerJID, <<"subject">> := Subject}) -> + case mod_muc_light_api:change_room_config(Domain, RoomID, RoomName, OwnerJID, Subject) of + {ok, Room} -> + {ok, make_room(Room)}; + Err -> + make_error(Err, #{domain => Domain, id => RoomID, owner => OwnerJID}) + end. + +-spec delete_room(map()) -> {ok, binary()} | {error, resolver_error()}. +delete_room(#{<<"domain">> := Domain, <<"id">> := RoomID}) -> + Result = mod_muc_light_api:delete_room(Domain, RoomID), + format_result(Result, #{domain => Domain, id => RoomID}). + +-spec invite_user(map()) -> {ok, binary()} | {error, resolver_error()}. +invite_user(#{<<"domain">> := Domain, <<"name">> := Name, <<"sender">> := SenderJID, + <<"recipient">> := RecipientJID}) -> + Result = mod_muc_light_api:invite_to_room(Domain, Name, SenderJID, RecipientJID), + format_result(Result, #{domain => Domain, name => Name, + sender => sender, recipient => RecipientJID}). + +-spec kick_user(map()) -> {ok, binary()} | {error, resolver_error()}. +kick_user(#{<<"domain">> := Domain, <<"id">> := RoomID, <<"user">> := UserJID}) -> + Result = mod_muc_light_api:remove_user_from_room(Domain, RoomID, UserJID, UserJID), + format_result(Result, #{user => UserJID}). + +-spec send_msg_to_room(map()) -> {ok, binary()} | {error, resolver_error()}. +send_msg_to_room(#{<<"domain">> := Domain, <<"name">> := RoomName, <<"from">> := FromJID, + <<"body">> := Message}) -> + Result = mod_muc_light_api:send_message(Domain, RoomName, FromJID, Message), + format_result(Result, #{domain => Domain, name => RoomName, from => FromJID}). diff --git a/src/graphql/admin/mongoose_graphql_muc_light_admin_query.erl b/src/graphql/admin/mongoose_graphql_muc_light_admin_query.erl new file mode 100644 index 0000000000..e20385ab80 --- /dev/null +++ b/src/graphql/admin/mongoose_graphql_muc_light_admin_query.erl @@ -0,0 +1,63 @@ +-module(mongoose_graphql_muc_light_admin_query). + +-export([execute/4]). + +-ignore_xref([execute/4]). + +-include("../mongoose_graphql_types.hrl"). + +-import(mongoose_graphql_helper, [make_error/2, format_result/2]). +-import(mongoose_graphql_muc_light_helper, [make_room/1, make_ok_user/1]). + +execute(_Ctx, _Obj, <<"listUserRooms">>, Args) -> + list_user_rooms(Args); +execute(_Ctx, _Obj, <<"listRoomUsers">>, Args) -> + list_room_users(Args); +execute(_Ctx, _Obj, <<"getRoomConfig">>, Args) -> + get_room_config(Args); +execute(_Ctx, _Obj, <<"getRoomMessages">>, Args) -> + get_room_messages(Args). + +-spec list_user_rooms(map()) -> {ok, [binary()]} | {error, resolver_error()}. +list_user_rooms(#{<<"user">> := UserJID}) -> + case mod_muc_light_api:get_user_rooms(UserJID) of + {ok, Rooms} -> + {ok, [{ok, R} || R <- Rooms]}; + Err -> + make_error(Err, #{user => UserJID}) + end. + +-spec list_room_users(map()) -> {ok, [map()]} | {error, resolver_error()}. +list_room_users(#{<<"domain">> := Domain, <<"id">> := RoomID}) -> + case mod_muc_light_api:get_room_aff(Domain, RoomID) of + {ok, Affs} -> + {ok, [make_ok_user(A) || A <- Affs]}; + Err -> + make_error(Err, #{domain => Domain, id => RoomID}) + end. + +-spec get_room_config(map()) -> {ok, map()} | {error, resolver_error()}. +get_room_config(#{<<"domain">> := Domain, <<"id">> := RoomID}) -> + case mod_muc_light_api:get_room_info(Domain, RoomID) of + {ok, Room} -> + {ok, make_room(Room)}; + Err -> + make_error(Err, #{domain => Domain, id => RoomID}) + end. + +-spec get_room_messages(map()) -> {ok, map()} | {error, resolver_error()}. +get_room_messages(#{<<"domain">> := Domain, <<"id">> := RoomID, + <<"pageSize">> := PageSize, <<"before">> := Before}) -> + Before2 = null_to_undefined(Before), + case mod_muc_light_api:get_room_messages(Domain, RoomID, PageSize, Before2) of + {ok, Rows} -> + Maps = lists:map(fun mongoose_graphql_stanza_helper:row_to_map/1, Rows), + {ok, #{<<"stanzas">> => Maps, <<"limit">> => null}}; + Err -> + make_error(Err, #{domain => Domain, id => RoomID}) + end. + +%% Helpers + +null_to_undefined(null) -> undefined; +null_to_undefined(V) -> V. diff --git a/src/graphql/admin/mongoose_graphql_stanza_admin_query.erl b/src/graphql/admin/mongoose_graphql_stanza_admin_query.erl index c4892180cb..cc58f0a2e8 100644 --- a/src/graphql/admin/mongoose_graphql_stanza_admin_query.erl +++ b/src/graphql/admin/mongoose_graphql_stanza_admin_query.erl @@ -29,16 +29,8 @@ get_last_messages2(Caller, Limit, With, Before) -> Before2 = null_as_undefined(Before), %% Before is in microseconds Limit2 = min(500, Limit), Rows = mongoose_stanza_api:lookup_recent_messages(Caller, With2, Before2, Limit2), - Maps = lists:map(fun row_to_map/1, Rows), + Maps = lists:map(fun mongoose_graphql_stanza_helper:row_to_map/1, Rows), {ok, #{<<"stanzas">> => Maps, <<"limit">> => Limit2}}. null_as_undefined(null) -> undefined; null_as_undefined(Value) -> Value. - --spec row_to_map(mod_mam:message_row()) -> {ok, map()}. -row_to_map(#{id := Id, jid := From, packet := Msg}) -> - {Microseconds, _} = mod_mam_utils:decode_compact_uuid(Id), - StanzaID = mod_mam_utils:mess_id_to_external_binary(Id), - Map = #{<<"sender">> => From, <<"timestamp">> => Microseconds, - <<"stanza_id">> => StanzaID, <<"stanza">> => Msg}, - {ok, Map}. diff --git a/src/graphql/mongoose_graphql.erl b/src/graphql/mongoose_graphql.erl index 38dc017cc0..16887069d4 100644 --- a/src/graphql/mongoose_graphql.erl +++ b/src/graphql/mongoose_graphql.erl @@ -127,6 +127,8 @@ admin_mapping_rules() -> 'StanzaAdminQuery' => mongoose_graphql_stanza_admin_query, 'AccountAdminQuery' => mongoose_graphql_account_admin_query, 'AccountAdminMutation' => mongoose_graphql_account_admin_mutation, + 'MUCLightAdminMutation' => mongoose_graphql_muc_light_admin_mutation, + 'MUCLightAdminQuery' => mongoose_graphql_muc_light_admin_query, 'Domain' => mongoose_graphql_domain, default => mongoose_graphql_default}, interfaces => #{default => mongoose_graphql_default}, diff --git a/src/graphql/mongoose_graphql_muc_light_helper.erl b/src/graphql/mongoose_graphql_muc_light_helper.erl new file mode 100644 index 0000000000..abe97da494 --- /dev/null +++ b/src/graphql/mongoose_graphql_muc_light_helper.erl @@ -0,0 +1,12 @@ +-module(mongoose_graphql_muc_light_helper). + +-export([make_room/1, make_ok_user/1]). + +-spec make_room(mod_muc_light_api:room()) -> map(). +make_room(#{jid := JID, name := Name, subject := Subject, aff_users := Users}) -> + Participants = lists:map(fun make_ok_user/1, Users), + #{<<"jid">> => JID, <<"name">> => Name, <<"subject">> => Subject, + <<"participants">> => Participants}. + +make_ok_user({JID, Aff}) -> + {ok, #{<<"jid">> => JID, <<"affiliance">> => atom_to_binary(Aff)}}. diff --git a/src/graphql/mongoose_graphql_stanza_helper.erl b/src/graphql/mongoose_graphql_stanza_helper.erl new file mode 100644 index 0000000000..438dedcd05 --- /dev/null +++ b/src/graphql/mongoose_graphql_stanza_helper.erl @@ -0,0 +1,11 @@ +-module(mongoose_graphql_stanza_helper). + +-export([row_to_map/1]). + +-spec row_to_map(mod_mam:message_row()) -> {ok, map()}. +row_to_map(#{id := Id, jid := From, packet := Msg}) -> + {Microseconds, _} = mod_mam_utils:decode_compact_uuid(Id), + StanzaID = mod_mam_utils:mess_id_to_external_binary(Id), + Map = #{<<"sender">> => From, <<"timestamp">> => Microseconds, + <<"stanza_id">> => StanzaID, <<"stanza">> => Msg}, + {ok, Map}. From 2a53e4870416f9a78a664f3ddc0208f65535d967 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Wojtasik?= Date: Wed, 16 Feb 2022 08:07:01 +0100 Subject: [PATCH 04/10] Add tests for MUC Light GraphQL admin API --- big_tests/default.spec | 1 + big_tests/dynamic_domains.spec | 4 +- big_tests/tests/graphql_muc_light_SUITE.erl | 484 +++++++++++++++++++ big_tests/tests/muc_light_http_api_SUITE.erl | 6 +- 4 files changed, 490 insertions(+), 5 deletions(-) create mode 100644 big_tests/tests/graphql_muc_light_SUITE.erl diff --git a/big_tests/default.spec b/big_tests/default.spec index a6468dea9d..45ff1a5e53 100644 --- a/big_tests/default.spec +++ b/big_tests/default.spec @@ -28,6 +28,7 @@ {suites, "tests", graphql_SUITE}. {suites, "tests", graphql_account_SUITE}. {suites, "tests", graphql_domain_SUITE}. +{suites, "tests", graphql_muc_light_SUITE}. {suites, "tests", graphql_session_SUITE}. {suites, "tests", graphql_stanza_SUITE}. {suites, "tests", inbox_SUITE}. diff --git a/big_tests/dynamic_domains.spec b/big_tests/dynamic_domains.spec index 0a5c830623..cb948f4bfe 100644 --- a/big_tests/dynamic_domains.spec +++ b/big_tests/dynamic_domains.spec @@ -41,11 +41,11 @@ {suites, "tests", graphql_SUITE}. {suites, "tests", graphql_account_SUITE}. +{suites, "tests", graphql_domain_SUITE}. +{suites, "tests", graphql_muc_light_SUITE}. {suites, "tests", graphql_session_SUITE}. {suites, "tests", graphql_stanza_SUITE}. -{suites, "tests", graphql_domain_SUITE}. - {suites, "tests", inbox_SUITE}. {suites, "tests", inbox_extensions_SUITE}. diff --git a/big_tests/tests/graphql_muc_light_SUITE.erl b/big_tests/tests/graphql_muc_light_SUITE.erl new file mode 100644 index 0000000000..83d9ed1670 --- /dev/null +++ b/big_tests/tests/graphql_muc_light_SUITE.erl @@ -0,0 +1,484 @@ +-module(graphql_muc_light_SUITE). + +-compile([export_all, nowarn_export_all]). + +-import(distributed_helper, [mim/0, require_rpc_nodes/1, rpc/4]). +-import(graphql_helper, [execute/3, execute_auth/2, get_listener_port/1, + get_listener_config/1, get_ok_value/2, get_err_msg/1]). + +-include_lib("common_test/include/ct.hrl"). +-include_lib("jid/include/jid.hrl"). +-include_lib("eunit/include/eunit.hrl"). +-include_lib("exml/include/exml.hrl"). + +-define(UNKNOWN_DOMAIN, <<"not-existing-domain">>). +-define(UNKNOWN, <<"not-existing">>). + +suite() -> + require_rpc_nodes([mim]) ++ escalus:suite(). + +all() -> + [{group, user_muc_light}, + {group, admin_muc_light}]. + +groups() -> + [{user_muc_light, [], user_muc_light_handler()}, + {admin_muc_light, [], admin_muc_light_handler()}]. + +user_muc_light_handler() -> + [mock]. + +admin_muc_light_handler() -> + [admin_create_room, + admin_create_identified_room, + admin_change_room_config, + admin_change_room_config_errors, + admin_invite_user, + admin_invite_user_errors, + admin_delete_room, + admin_kick_user, + admin_send_message_to_room, + admin_send_message_to_room_errors, + admin_get_room_messages, + admin_list_user_rooms, + admin_list_room_users, + admin_get_room_config + ]. + +init_per_suite(Config) -> + Config1 = init_modules(Config), + [{muc_light_host, muc_light_helper:muc_host()} + | escalus:init_per_suite(Config1)]. + +end_per_suite(Config) -> + escalus_fresh:clean(), + dynamic_modules:restore_modules(Config), + escalus:end_per_suite(Config). + +init_modules(Config) -> + HostType = domain_helper:host_type(), + Config1 = dynamic_modules:save_modules(HostType, Config), + Config2 = rest_helper:maybe_enable_mam(mam_helper:backend(), HostType, Config1), + dynamic_modules:ensure_modules(HostType, required_modules(suite)), + Config2. + +required_modules(SuiteOrTC) -> + [{mod_muc_light, common_muc_light_opts() ++ muc_light_opts(SuiteOrTC)}]. + +muc_light_opts(config_can_be_changed_by_all) -> + [{all_can_configure, true}]; +muc_light_opts(suite) -> + []. + +common_muc_light_opts() -> + MucPattern = distributed_helper:subhost_pattern(muc_light_helper:muc_host_pattern()), + [{host, MucPattern}, + {rooms_in_rosters, true}]. + +init_per_group(admin_muc_light, Config) -> + graphql_helper:init_admin_handler(Config); +init_per_group(_GN, Config) -> + Config. + +end_per_group(_GN, Config) -> + Config. + +init_per_testcase(TC, Config) -> + rest_helper:maybe_skip_mam_test_cases(TC, [admin_get_room_messages], Config). + +end_per_testcase(TC, Config) -> + escalus:end_per_testcase(TC, Config). + +mock(_Config) -> + ok. + +admin_create_room(Config) -> + escalus:fresh_story_with_config(Config, [{alice, 1}], fun admin_create_room_story/2). + +admin_create_room_story(Config, Alice) -> + AliceBin = escalus_client:short_jid(Alice), + AliceBinLower = escalus_utils:jid_to_lower(AliceBin), + Domain = escalus_client:server(Alice), + MucServer = ?config(muc_light_host, Config), + Name = <<"first room">>, + Subject = <<"testing">>, + Res = execute_auth(admin_create_room_body(Domain, Name, AliceBin, Subject, null), Config), + Path = [data, muc_light, createRoom], + #{<<"jid">> := JID, <<"name">> := Name, <<"subject">> := Subject, + <<"participants">> := Participants} = get_ok_value(Path, Res), + ?assertMatch(#jid{server = MucServer}, jid:from_binary(JID)), + ?assertEqual([#{<<"jid">> => AliceBinLower, <<"affiliance">> => <<"owner">>}], Participants), + % Try with a non-existing domain + Res2 = execute_auth(admin_create_room_body(?UNKNOWN_DOMAIN, Name, AliceBin, Subject, null), Config), + ?assertNotEqual(nomatch, binary:match(get_err_msg(Res2), <<"does not exist">>)). + +admin_create_identified_room(Config) -> + escalus:fresh_story_with_config(Config, [{alice, 1}], fun admin_create_identified_room_story/2). + +admin_create_identified_room_story(Config, Alice) -> + AliceBin = escalus_client:short_jid(Alice), + Domain = escalus_client:server(Alice), + MucServer = ?config(muc_light_host, Config), + Name = <<"first room">>, + Subject = <<"testing">>, + Id = <<"my_room">>, + Res = execute_auth(admin_create_room_body(Domain, Name, AliceBin, Subject, Id), Config), + Path = [data, muc_light, createRoom], + #{<<"jid">> := JID, <<"name">> := Name, <<"subject">> := Subject} = get_ok_value(Path, Res), + ?assertMatch(#jid{user = Id, server = MucServer}, jid:from_binary(JID)), + % Create a room with an existing ID + Res2 = execute_auth(admin_create_room_body(Domain, <<"snd room">>, AliceBin, Subject, Id), Config), + ?assertNotEqual(nomatch, binary:match(get_err_msg(Res2), <<"already exists">>)), + % Try with a non-existing domain + Res3 = execute_auth(admin_create_room_body(?UNKNOWN_DOMAIN, <<>>, AliceBin, Subject, Id), Config), + ?assertNotEqual(nomatch, binary:match(get_err_msg(Res3), <<"does not exist">>)). + +admin_change_room_config(Config) -> + escalus:fresh_story_with_config(Config, [{alice, 1}], fun admin_change_room_config_story/2). + +admin_change_room_config_story(Config, Alice) -> + AliceBin = escalus_client:short_jid(Alice), + Domain = escalus_client:server(Alice), + Name = <<"first room">>, + Subject = <<"testing">>, + Id = atom_to_binary(?FUNCTION_NAME), + % Create a new room + execute_auth(admin_create_room_body(Domain, Name, AliceBin, Subject, Id), Config), + % Try to change the room configuration + Name2 = <<"changed room">>, + Subject2 = <<"not testing">>, + Res = execute_auth(admin_change_room_configuration_body(Id, Domain, AliceBin, Name2, Subject2), Config), + Path = [data, muc_light, changeRoomConfiguration], + ?assertMatch(#{<<"name">> := Name2, <<"subject">> := Subject2}, get_ok_value(Path, Res)). + +admin_change_room_config_errors(Config) -> + escalus:fresh_story_with_config(Config, [{alice, 1}, {bob, 1}], fun admin_change_room_config_errors_story/3). + +admin_change_room_config_errors_story(Config, Alice, Bob) -> + AliceBin = escalus_client:short_jid(Alice), + BobBin = escalus_client:short_jid(Bob), + Domain = escalus_client:server(Alice), + RoomName = <<"first room">>, + {ok, #{jid := #jid{luser = RoomID}}} = create_room(<<>>, Domain, RoomName, <<>>, AliceBin), + {ok, _} = invite_user(Domain, RoomName, AliceBin, BobBin), + % Try to change the config with a non-existing domain + Res = execute_auth(admin_change_room_configuration_body(RoomID, ?UNKNOWN_DOMAIN, AliceBin, RoomName, <<>>), Config), + ?assertNotEqual(nomatch, binary:match(get_err_msg(Res), <<"does not exist">>)), + % Try to change the config of the non-existing room + Res2 = execute_auth(admin_change_room_configuration_body(<<"unknown">>, Domain, AliceBin, RoomName, <<>>), Config), + ?assertNotEqual(nomatch, binary:match(get_err_msg(Res2), <<"does not exist">>)), + % Try to change the config by the non-existing user + Res3 = execute_auth(admin_change_room_configuration_body(RoomID, Domain, <<"wrong-user@wrong-domain">>, RoomName, <<>>), Config), + ?assertNotEqual(nomatch, binary:match(get_err_msg(Res3), <<"not room participant">>)), + % Try to change a config by the user without permission + Res4 = execute_auth(admin_change_room_configuration_body(RoomID, Domain, BobBin, RoomName, <<>>), Config), + ?assertNotEqual(nomatch, binary:match(get_err_msg(Res4), <<"not permission to change">>)). + +admin_invite_user(Config) -> + escalus:fresh_story_with_config(Config, [{alice, 1}, {bob, 1}], fun admin_invite_user_story/3). + +admin_invite_user_story(Config, Alice, Bob) -> + AliceBin = escalus_client:short_jid(Alice), + BobBin = escalus_client:short_jid(Bob), + Domain = escalus_client:server(Alice), + Name = <<"first room">>, + Name2 = <<"second room">>, + {ok, #{jid := RoomJID}} = create_room(<<>>, Domain, Name, <<>>, AliceBin), + {ok, _} = create_room(<<>>, Domain, Name2, <<>>, AliceBin), + + Res = execute_auth(admin_invite_user_body(Domain, Name, AliceBin, BobBin), Config), + ?assertNotEqual(nomatch, binary:match(get_ok_value([data, muc_light, inviteUser], Res), + <<"successfully">>)), + BobName = escalus_utils:jid_to_lower(escalus_client:username(Bob)), + AliceName = escalus_utils:jid_to_lower(escalus_client:username(Alice)), + ExpectedAff = lists:sort([{{AliceName, Domain}, owner}, + {{BobName, Domain}, member}]), + ?assertMatch(ExpectedAff, lists:sort(get_room_aff(RoomJID))). + +admin_invite_user_errors(Config) -> + escalus:fresh_story_with_config(Config, [{alice, 1}, {bob, 1}], fun admin_invite_user_errors_story/3). + +admin_invite_user_errors_story(Config, Alice, Bob) -> + AliceBin = escalus_client:short_jid(Alice), + BobBin = escalus_client:short_jid(Bob), + Domain = escalus_client:server(Alice), + Name = <<"first room">>, + {ok, #{jid := _RoomJID}} = create_room(<<>>, Domain, Name, <<>>, AliceBin), + % Try to invite a user to not existing room + Res = execute_auth(admin_invite_user_body(Domain, <<>>, AliceBin, BobBin), Config), + ?assertNotEqual(nomatch, binary:match(get_err_msg(Res), <<"does not exist">>)), + % User without rooms tries to invite a user + Res2 = execute_auth(admin_invite_user_body(Domain, <<>>, BobBin, AliceBin), Config), + ?assertNotEqual(nomatch, binary:match(get_err_msg(Res2), <<"does not occupy any room">>)), + % Try with a non-existing domain + Res3 = execute_auth(admin_invite_user_body(?UNKNOWN_DOMAIN, Name, AliceBin, BobBin), Config), + ?assertNotEqual(nomatch, binary:match(get_err_msg(Res3), <<"does not exist">>)). + +admin_delete_room(Config) -> + escalus:fresh_story_with_config(Config, [{alice, 1}], fun admin_delete_room_story/2). + +admin_delete_room_story(Config, Alice) -> + AliceBin = escalus_client:short_jid(Alice), + Domain = escalus_client:server(Alice), + Name = <<"first room">>, + RoomID = <<"delete_room_id">>, + {ok, #{jid := RoomJID}} = create_room(RoomID, Domain, Name, <<>>, AliceBin), + Res = execute_auth(admin_delete_room_body(Domain, RoomID), Config), + ?assertNotEqual(nomatch, binary:match(get_ok_value([data, muc_light, deleteRoom], Res), + <<"successfully">>)), + ?assertEqual({error, not_exists}, get_room_info(jid:from_binary(RoomJID))), + % Try with a non-existing domain + Res2 = execute_auth(admin_delete_room_body(?UNKNOWN_DOMAIN, RoomID), Config), + ?assertNotEqual(nomatch, binary:match(get_err_msg(Res2), <<"does not exist">>)). + +admin_kick_user(Config) -> + escalus:fresh_story_with_config(Config, [{alice, 1}, {bob, 1}], fun admin_kick_user_story/3). + +admin_kick_user_story(Config, Alice, Bob) -> + AliceBin = escalus_client:short_jid(Alice), + BobBin = escalus_client:short_jid(Bob), + Domain = escalus_client:server(Alice), + RoomName = <<"first room">>, + RoomID = <<"kick_user_test_room">>, + {ok, #{jid := RoomJID}} = create_room(RoomID, Domain, RoomName, <<>>, AliceBin), + {ok, _} = invite_user(Domain, RoomName, AliceBin, BobBin), + ?assertEqual(2, length(get_room_aff(RoomJID))), + Res = execute_auth(admin_kick_user_body(Domain, RoomID, BobBin), Config), + ?assertNotEqual(nomatch, binary:match(get_ok_value([data, muc_light, kickUser], Res), + <<"successfully">>)), + ?assertEqual(1, length(get_room_aff(RoomJID))), + % Try with a non-existing domain + Res2 = execute_auth(admin_kick_user_body(?UNKNOWN_DOMAIN, RoomID, BobBin), Config), + ?assertNotEqual(nomatch, binary:match(get_err_msg(Res2), <<"does not exist">>)). + +admin_send_message_to_room(Config) -> + escalus:fresh_story_with_config(Config, [{alice, 1}, {bob, 1}], + fun admin_send_message_to_room_story/3). + +admin_send_message_to_room_story(Config, Alice, Bob) -> + AliceBin = escalus_client:short_jid(Alice), + BobBin = escalus_client:short_jid(Bob), + Domain = escalus_client:server(Alice), + RoomName = <<"first room">>, + RoomID = <<"send_message_test_room">>, + MsgBody = <<"Hello there!">>, + {ok, #{jid := _RoomJID}} = create_room(RoomID, Domain, RoomName, <<>>, AliceBin), + {ok, _} = invite_user(Domain, RoomName, AliceBin, BobBin), + Res = execute_auth(admin_send_message_to_room_body(Domain, RoomName, AliceBin, MsgBody), Config), + ?assertNotEqual(nomatch, binary:match(get_ok_value([data, muc_light, sendMessageToRoom], Res), + <<"successfully">>)). + +admin_send_message_to_room_errors(Config) -> + escalus:fresh_story_with_config(Config, [{alice, 1}, {bob, 1}], + fun admin_send_message_to_room_errors_story/3). + +admin_send_message_to_room_errors_story(Config, Alice, Bob) -> + AliceBin = escalus_client:short_jid(Alice), + BobBin = escalus_client:short_jid(Bob), + Domain = escalus_client:server(Alice), + ARoomName = <<"alice room">>, + BRoomName = <<"bob room">>, + MsgBody = <<"Hello there!">>, + {ok, #{jid := _RoomJID}} = create_room(<<>>, Domain, ARoomName, <<>>, AliceBin), + % Try with a non-existing domain + Res2 = execute_auth(admin_send_message_to_room_body(?UNKNOWN_DOMAIN, ARoomName, AliceBin, MsgBody), Config), + ?assertNotEqual(nomatch, binary:match(get_err_msg(Res2), <<"does not exist">>)), + % Try with a user without rooms + Res3 = execute_auth(admin_send_message_to_room_body(Domain, ARoomName, BobBin, MsgBody), Config), + ?assertNotEqual(nomatch, binary:match(get_err_msg(Res3), <<"does not occupy any room">>)), + % Try with a room not occupied by this user + {ok, #{jid := _RoomJID2}} = create_room(<<>>, Domain, BRoomName, <<>>, BobBin), + Res4 = execute_auth(admin_send_message_to_room_body(Domain, ARoomName, BobBin, MsgBody), Config), + ?assertNotEqual(nomatch, binary:match(get_err_msg(Res4), <<"does not found">>)). + +admin_get_room_messages(Config) -> + escalus:fresh_story_with_config(Config, [{alice, 1}], fun admin_get_room_messages_story/2). + +admin_get_room_messages_story(Config, Alice) -> + Path = [data, muc_light, getRoomMessages, stanzas], + AliceBin = escalus_client:short_jid(Alice), + Domain = escalus_client:server(Alice), + RoomName = <<"first room">>, + RoomName2 = <<"second room">>, + RoomID = <<"get_messages_test_room">>, + {ok, #{jid := _RoomJID}} = create_room(RoomID, Domain, RoomName, <<>>, AliceBin), + {ok, _} = create_room(<<>>, Domain, RoomName2, <<>>, AliceBin), + Message = <<"Hello friends">>, + send_message_to_room(Domain, RoomName, jid:from_binary(AliceBin), Message), + mam_helper:maybe_wait_for_archive(Config), + % Get messages so far + Res = execute_auth(admin_get_room_messages_body(Domain, RoomID, 50, null), Config), + [#{<<"stanza">> := StanzaXML}] = get_ok_value(Path, Res), + ?assertMatch({ok, #xmlel{name = <<"message">>}}, exml:parse(StanzaXML)), + % Get messages before the given date and time + Before = <<"2022-02-17T04:54:13+00:00">>, + Res2 = execute_auth(admin_get_room_messages_body(Domain, RoomID, 50, Before), Config), + ?assertMatch([], get_ok_value(Path, Res2)), + % Try with a non-existing domain + Res3 = execute_auth(admin_get_room_messages_body(?UNKNOWN_DOMAIN, RoomID, 50, null), Config), + ?assertNotEqual(nomatch, binary:match(get_err_msg(Res3), <<"does not exist">>)). + +admin_list_user_rooms(Config) -> + escalus:fresh_story_with_config(Config, [{alice, 1}], fun admin_list_user_rooms_story/2). + +admin_list_user_rooms_story(Config, Alice) -> + Path = [data, muc_light, listUserRooms], + AliceBin = escalus_client:short_jid(Alice), + Domain = escalus_client:server(Alice), + RoomName = <<"first room">>, + RoomName2 = <<"second room">>, + {ok, #{jid := RoomJID}} = create_room(<<>>, Domain, RoomName, <<>>, AliceBin), + {ok, #{jid := RoomJID2}} = create_room(<<>>, Domain, RoomName2, <<>>, AliceBin), + Res = execute_auth(admin_list_user_rooms_body(AliceBin), Config), + ?assertEqual(lists:sort([jid:to_binary(RoomJID), jid:to_binary(RoomJID2)]), + lists:sort(get_ok_value(Path, Res))), + % Try with a non-existing user + Res2 = execute_auth(admin_list_user_rooms_body(<<"not-exist@", Domain/binary>>), Config), + ?assertEqual([], lists:sort(get_ok_value(Path, Res2))), + % Try with a non-existing domain + Res3 = execute_auth(admin_list_user_rooms_body(<<"not-exist@not-exist">>), Config), + ?assertNotEqual(nomatch, binary:match(get_err_msg(Res3), <<"does not exist">>)). + +admin_list_room_users(Config) -> + escalus:fresh_story_with_config(Config, [{alice, 1}], fun admin_list_room_users_story/2). + +admin_list_room_users_story(Config, Alice) -> + AliceBin = escalus_client:short_jid(Alice), + AliceLower = escalus_utils:jid_to_lower(AliceBin), + Domain = escalus_client:server(Alice), + RoomName = <<"first room">>, + {ok, #{jid := #jid{luser = RoomID}}} = create_room(<<>>, Domain, RoomName, <<>>, AliceBin), + Res = execute_auth(admin_list_room_users_body(Domain, RoomID), Config), + ?assertEqual([#{<<"jid">> => AliceLower, <<"affiliance">> => <<"owner">>}], + get_ok_value([data, muc_light, listRoomUsers], Res)), + % Try with a non-existing domain + Res2 = execute_auth(admin_list_room_users_body(?UNKNOWN_DOMAIN, RoomID), Config), + ?assertNotEqual(nomatch, binary:match(get_err_msg(Res2), <<"does not exist">>)). + +admin_get_room_config(Config) -> + escalus:fresh_story_with_config(Config, [{alice, 1}], fun admin_get_room_config_story/2). + +admin_get_room_config_story(Config, Alice) -> + AliceBin = escalus_client:short_jid(Alice), + AliceLower = escalus_utils:jid_to_lower(AliceBin), + Domain = escalus_client:server(Alice), + RoomName = <<"first room">>, + RoomSubject = <<"Room about nothing">>, + {ok, #{jid := #jid{luser = RoomID} = RoomJID}} = create_room(<<>>, Domain, RoomName, + RoomSubject, AliceBin), + RoomJIDBin = jid:to_binary(RoomJID), + Res = execute_auth(admin_get_room_config_body(Domain, RoomID), Config), + ?assertEqual(#{<<"jid">> => RoomJIDBin, <<"subject">> => RoomSubject, <<"name">> => RoomName, + <<"participants">> => [#{<<"jid">> => AliceLower, <<"affiliance">> => <<"owner">>}]}, + get_ok_value([data, muc_light, getRoomConfig], Res)), + % Try with a non-existing domain + Res2 = execute_auth(admin_get_room_config_body(?UNKNOWN_DOMAIN, RoomID), Config), + ?assertNotEqual(nomatch, binary:match(get_err_msg(Res2), <<"does not exist">>)), + % Try with a non-existing room + Res3 = execute_auth(admin_get_room_config_body(Domain, ?UNKNOWN), Config), + ?assertNotEqual(nomatch, binary:match(get_err_msg(Res3), <<"does not exist">>)). + +%% Helpers + +send_message_to_room(Domain, RoomName, SenderJID, Message) -> + rpc(mim(), mod_muc_light_api, send_message, [Domain, RoomName, SenderJID, Message]). + +get_room_messages(ID, Domain) -> + {ok, Messages} = rpc(mim(), mod_muc_light_api, get_room_messages, [Domain, ID]), + Messages. + +create_room(Id, Domain, Name, Subject, CreatorBin) -> + CreatorJID = jid:from_binary(CreatorBin), + rpc(mim(), mod_muc_light_api, create_room, [Domain, Id, Name, CreatorJID, Subject]). + +invite_user(Domain, RoomName, SenderBin, RecipientBin) -> + SenderJID = jid:from_binary(SenderBin), + RecipientJID = jid:from_binary(RecipientBin), + rpc(mim(), mod_muc_light_api, invite_to_room, [Domain, RoomName, SenderJID, RecipientJID]). + +get_room_info(JID) -> + HostType = domain_helper:host_type(), + RoomUS = jid:to_lus(JID), + rpc(mim(), mod_muc_light_db_backend, get_info, [HostType, RoomUS]). + +get_room_aff(JID) -> + {ok, _, Aff, _} = get_room_info(JID), + Aff. + +%% Request bodies + +admin_create_room_body(Domain, Name, Owner, Subject, Id) -> + Query = <<"mutation M1($domain: String!, $name: String!, $owner: JID!, $subject: String!, $id: String) + { muc_light { createRoom(domain: $domain, name: $name, owner: $owner, subject: $subject, id: $id) + { jid name subject participants {jid affiliance} } } }">>, + OpName = <<"M1">>, + Vars = #{<<"domain">> => Domain, <<"name">> => Name, <<"owner">> => Owner, <<"subject">> => Subject, <<"id">> => Id}, + #{query => Query, operationName => OpName, variables => Vars}. + +admin_change_room_configuration_body(Id, Domain, Owner, Name, Subject) -> + Query = <<"mutation M1($id: String!, $domain: String!, $name: String!, $owner: JID!, $subject: String!) + { muc_light { changeRoomConfiguration(id: $id, domain: $domain, name: $name, owner: $owner, subject: $subject) + { jid name subject participants {jid affiliance} } } }">>, + OpName = <<"M1">>, + Vars = #{<<"id">> => Id, <<"domain">> => Domain, <<"name">> => Name, <<"owner">> => Owner, <<"subject">> => Subject}, + #{query => Query, operationName => OpName, variables => Vars}. + +admin_invite_user_body(Domain, Name, Sender, Recipient) -> + Query = <<"mutation M1($domain: String!, $name: String!, $sender: JID!, $recipient: JID!) + { muc_light { inviteUser(domain: $domain, name: $name, sender: $sender, recipient: $recipient) } }">>, + OpName = <<"M1">>, + Vars = #{<<"domain">> => Domain, <<"name">> => Name, <<"sender">> => Sender, <<"recipient">> => Recipient}, + #{query => Query, operationName => OpName, variables => Vars}. + +admin_delete_room_body(Domain, RoomID) -> + Query = <<"mutation M1($domain: String!, $id: String!) + { muc_light { deleteRoom(domain: $domain, id: $id)} }">>, + OpName = <<"M1">>, + Vars = #{<<"domain">> => Domain, <<"id">> => RoomID}, + #{query => Query, operationName => OpName, variables => Vars}. + +admin_kick_user_body(Domain, RoomID, User) -> + Query = <<"mutation M1($domain: String!, $id: String!, $user: JID!) + { muc_light { kickUser(domain: $domain, id: $id, user: $user)} }">>, + OpName = <<"M1">>, + Vars = #{<<"domain">> => Domain, <<"id">> => RoomID, <<"user">> => User}, + #{query => Query, operationName => OpName, variables => Vars}. + +admin_send_message_to_room_body(Domain, RoomName, From, Body) -> + Query = <<"mutation M1($domain: String!, $name: String!, $from: JID!, $body: String!) + { muc_light { sendMessageToRoom(domain: $domain, name: $name, from: $from, body: $body)} }">>, + OpName = <<"M1">>, + Vars = #{<<"domain">> => Domain, <<"name">> => RoomName, <<"from">> => From, <<"body">> => Body}, + #{query => Query, operationName => OpName, variables => Vars}. + +admin_get_room_messages_body(Domain, RoomID, PageSize, Before) -> + Query = <<"query Q1($domain: String!, $id: String!, $pageSize: Int!, $before: DateTime) + { muc_light { getRoomMessages(domain: $domain, id: $id, pageSize: $pageSize, before: $before) + { stanzas { stanza } } } }">>, + OpName = <<"Q1">>, + Vars = #{<<"domain">> => Domain, <<"id">> => RoomID, + <<"pageSize">> => PageSize, <<"before">> => Before}, + #{query => Query, operationName => OpName, variables => Vars}. + +admin_list_user_rooms_body(User) -> + Query = <<"query Q1($user: JID!) + { muc_light { listUserRooms(user: $user) } }">>, + OpName = <<"Q1">>, + Vars = #{<<"user">> => User}, + #{query => Query, operationName => OpName, variables => Vars}. + +admin_list_room_users_body(Domain, RoomID) -> + Query = <<"query Q1($domain: String!, $id: String!) + { muc_light { listRoomUsers(domain: $domain, id: $id) + { jid affiliance } } }">>, + OpName = <<"Q1">>, + Vars = #{<<"domain">> => Domain, <<"id">> => RoomID}, + #{query => Query, operationName => OpName, variables => Vars}. + +admin_get_room_config_body(Domain, RoomID) -> + Query = <<"query Q1($domain: String!, $id: String!) + { muc_light { getRoomConfig(domain: $domain, id: $id) + { jid name subject participants {jid affiliance} } } }">>, + OpName = <<"Q1">>, + Vars = #{<<"domain">> => Domain, <<"id">> => RoomID}, + #{query => Query, operationName => OpName, variables => Vars}. diff --git a/big_tests/tests/muc_light_http_api_SUITE.erl b/big_tests/tests/muc_light_http_api_SUITE.erl index da218f4541..b7db81c4d5 100644 --- a/big_tests/tests/muc_light_http_api_SUITE.erl +++ b/big_tests/tests/muc_light_http_api_SUITE.erl @@ -195,7 +195,7 @@ delete_room_by_non_owner(Config) -> [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate)-> {{<<"403">>, <<"Forbidden">>}, - <<"you can not delete this room">>} = + <<"You cannot delete this room">>} = check_delete_room(Config, RoomName, RoomName, Alice, [Bob, Kate], Bob) end). @@ -205,7 +205,7 @@ delete_non_existent_room(Config) -> escalus:fresh_story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate)-> - {{<<"404">>, _}, <<"room does not exist">>} = + {{<<"404">>, _}, <<"Cannot remove not existing room">>} = check_delete_room(Config, RoomName, <<"some_non_existent_room">>, Alice, [Bob, Kate], Alice) end). @@ -215,7 +215,7 @@ delete_room_without_having_a_membership(Config) -> escalus:fresh_story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate)-> - {{<<"403">>, _}, <<"given user does not occupy this room">>} = + {{<<"403">>, _}, <<"Given user does not occupy any room">>} = check_delete_room(Config, RoomName, RoomName, Alice, [Bob], Kate) end). From 04a586376e60a9a54201c4849cb577647e38cc7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Wojtasik?= Date: Thu, 17 Feb 2022 11:07:06 +0100 Subject: [PATCH 05/10] Use Domain instead of MUCLightServer in requests MUC Light admin rest API required a domain to create a room and a muc light domain for other operations. Now it is unified and uses a domain everywhere. --- big_tests/tests/muc_light_http_api_SUITE.erl | 6 +++--- doc/rest-api/Administration-backend_swagger.yml | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/big_tests/tests/muc_light_http_api_SUITE.erl b/big_tests/tests/muc_light_http_api_SUITE.erl index b7db81c4d5..f546d60a3b 100644 --- a/big_tests/tests/muc_light_http_api_SUITE.erl +++ b/big_tests/tests/muc_light_http_api_SUITE.erl @@ -128,7 +128,7 @@ create_identifiable_room(Config) -> invite_to_room(Config) -> Name = <<"wonderland">>, - Path = path([muc_light_domain(), Name, "participants"]), + Path = path([domain_helper:domain(), Name, "participants"]), escalus:fresh_story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate) -> %% XMPP: Alice creates a room. @@ -155,7 +155,7 @@ invite_to_room(Config) -> send_message_to_room(Config) -> Name = <<"wonderland">>, - Path = path([muc_light_domain(), Name, "messages"]), + Path = path([domain_helper:domain(), Name, "messages"]), Text = <<"Hello everyone!">>, escalus:fresh_story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], @@ -290,7 +290,7 @@ check_delete_room(_Config, RoomNameToCreate, RoomNameToDelete, RoomOwner, escalus:assert(is_iq_result, CreationResult), muc_light_helper:verify_aff_bcast(Members, Affiliations), ShortJID = escalus_client:short_jid(UserToExecuteDelete), - Path = path([muc_light_domain(), RoomNameToDelete, ShortJID, "management"]), + Path = path([domain_helper:domain(), RoomNameToDelete, ShortJID, "management"]), rest_helper:delete(admin, Path). diff --git a/doc/rest-api/Administration-backend_swagger.yml b/doc/rest-api/Administration-backend_swagger.yml index 9c971c66c0..20a523f432 100644 --- a/doc/rest-api/Administration-backend_swagger.yml +++ b/doc/rest-api/Administration-backend_swagger.yml @@ -472,7 +472,7 @@ paths: schema: title: roomJID type: string - /muc-lights/{XMPPMUCHost}/{roomName}/participants: + /muc-lights/{XMPPHost}/{roomName}/participants: parameters: - $ref: '#/parameters/MUCServer' - $ref: '#/parameters/roomName' @@ -504,7 +504,7 @@ paths: responses: 204: description: An invite was sent out - /muc-lights/{XMPPMUCHost}/{roomName}/messages: + /muc-lights/{XMPPHost}/{roomName}/messages: parameters: - $ref: '#/parameters/MUCServer' - $ref: '#/parameters/roomName' @@ -536,7 +536,7 @@ paths: responses: 204: description: Message was sent to the MUC Light room - /muc-lights/{XMPPMUCHost}/{roomName}/{user}/management: + /muc-lights/{XMPPHost}/{roomName}/{user}/management: parameters: - $ref: '#/parameters/MUCServer' - $ref: '#/parameters/roomName' From e2601087e4444cf94a87a92c7b3e4ddbdbce20a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Wojtasik?= Date: Wed, 23 Feb 2022 09:43:16 +0100 Subject: [PATCH 06/10] Make MUC Light API to use room JID Also use MUCLightDomain in commands instead of Domain. Apply review comments. --- priv/graphql/schemas/admin/muc_light.gql | 16 +- ...goose_graphql_muc_light_admin_mutation.erl | 30 +- ...mongoose_graphql_muc_light_admin_query.erl | 20 +- .../mongoose_client_api_rooms.erl | 24 +- .../mongoose_client_api_rooms_config.erl | 7 +- .../mongoose_client_api_rooms_users.erl | 23 +- src/muc_light/mod_muc_light.erl | 3 +- src/muc_light/mod_muc_light_api.erl | 405 ++++++++---------- src/muc_light/mod_muc_light_commands.erl | 61 +-- 9 files changed, 263 insertions(+), 326 deletions(-) diff --git a/priv/graphql/schemas/admin/muc_light.gql b/priv/graphql/schemas/admin/muc_light.gql index 85dc6bd668..58f970e272 100644 --- a/priv/graphql/schemas/admin/muc_light.gql +++ b/priv/graphql/schemas/admin/muc_light.gql @@ -5,15 +5,15 @@ type MUCLightAdminMutation @protected{ "Create a MUC light room under the given XMPP hostname" createRoom(domain: String!, name: String!, owner: JID!, subject: String!, id: String): Room "Change configuration of a MUC Light room" - changeRoomConfiguration(id: String!, domain: String!, name: String!, owner: JID!, subject: String!): Room + changeRoomConfiguration(room: JID!, owner: JID!, name: String!, subject: String!): Room "Invite a user to a MUC Light room" - inviteUser(domain: String!, name: String!, sender: JID!, recipient: JID!): String + inviteUser(room: JID!, sender: JID!, recipient: JID!): String "Remove a MUC Light room" - deleteRoom(id: String!, domain: String!): String + deleteRoom(room: JID!): String "Kick a user from a MUC Light room" - kickUser(domain: String!, id: String!, user: JID!): String + kickUser(room: JID!, user: JID!): String "Send a message to a MUC Light room" - sendMessageToRoom(domain: String!, name: String!, from: JID!, body: String!): String + sendMessageToRoom(room: JID!, from: JID!, body: String!): String } """ @@ -21,11 +21,11 @@ Allow admin to get information about Multi-User Chat Light rooms. """ type MUCLightAdminQuery @protected{ "Get the MUC Light room archived messages" - getRoomMessages(domain: String!, id: String!, pageSize: Int!, before: DateTime): StanzasPayload + getRoomMessages(room: JID!, pageSize: Int!, before: DateTime): StanzasPayload "Get configuration of the MUC Light room" - getRoomConfig(domain: String!, id: String!): Room + getRoomConfig(room: JID!): Room "Get users list of given MUC Light room" - listRoomUsers(domain: String!, id: String!): [RoomUser!] + listRoomUsers(room: JID!): [RoomUser!] "Get the list of MUC Light rooms that the user participates in" listUserRooms(user: JID!): [JID!] } diff --git a/src/graphql/admin/mongoose_graphql_muc_light_admin_mutation.erl b/src/graphql/admin/mongoose_graphql_muc_light_admin_mutation.erl index 7cc1b373e4..2fa99508e0 100644 --- a/src/graphql/admin/mongoose_graphql_muc_light_admin_mutation.erl +++ b/src/graphql/admin/mongoose_graphql_muc_light_admin_mutation.erl @@ -35,34 +35,32 @@ create_room(#{<<"id">> := RoomID, <<"domain">> := Domain, <<"name">> := RoomName end. -spec change_room_config(map()) -> {ok, map()} | {error, resolver_error()}. -change_room_config(#{<<"id">> := RoomID, <<"domain">> := Domain, <<"name">> := RoomName, +change_room_config(#{<<"room">> := RoomJID, <<"name">> := RoomName, <<"owner">> := OwnerJID, <<"subject">> := Subject}) -> - case mod_muc_light_api:change_room_config(Domain, RoomID, RoomName, OwnerJID, Subject) of + case mod_muc_light_api:change_room_config(RoomJID, OwnerJID, RoomName, Subject) of {ok, Room} -> {ok, make_room(Room)}; Err -> - make_error(Err, #{domain => Domain, id => RoomID, owner => OwnerJID}) + make_error(Err, #{room => RoomJID, owner => OwnerJID}) end. -spec delete_room(map()) -> {ok, binary()} | {error, resolver_error()}. -delete_room(#{<<"domain">> := Domain, <<"id">> := RoomID}) -> - Result = mod_muc_light_api:delete_room(Domain, RoomID), - format_result(Result, #{domain => Domain, id => RoomID}). +delete_room(#{<<"room">> := RoomJID}) -> + Result = mod_muc_light_api:delete_room(RoomJID), + format_result(Result, #{room => RoomJID}). -spec invite_user(map()) -> {ok, binary()} | {error, resolver_error()}. -invite_user(#{<<"domain">> := Domain, <<"name">> := Name, <<"sender">> := SenderJID, +invite_user(#{<<"room">> := RoomJID, <<"sender">> := SenderJID, <<"recipient">> := RecipientJID}) -> - Result = mod_muc_light_api:invite_to_room(Domain, Name, SenderJID, RecipientJID), - format_result(Result, #{domain => Domain, name => Name, - sender => sender, recipient => RecipientJID}). + Result = mod_muc_light_api:invite_to_room(RoomJID, SenderJID, RecipientJID), + format_result(Result, #{room => RoomJID, sender => SenderJID, recipient => RecipientJID}). -spec kick_user(map()) -> {ok, binary()} | {error, resolver_error()}. -kick_user(#{<<"domain">> := Domain, <<"id">> := RoomID, <<"user">> := UserJID}) -> - Result = mod_muc_light_api:remove_user_from_room(Domain, RoomID, UserJID, UserJID), +kick_user(#{<<"room">> := RoomJID, <<"user">> := UserJID}) -> + Result = mod_muc_light_api:remove_user_from_room(RoomJID, UserJID, UserJID), format_result(Result, #{user => UserJID}). -spec send_msg_to_room(map()) -> {ok, binary()} | {error, resolver_error()}. -send_msg_to_room(#{<<"domain">> := Domain, <<"name">> := RoomName, <<"from">> := FromJID, - <<"body">> := Message}) -> - Result = mod_muc_light_api:send_message(Domain, RoomName, FromJID, Message), - format_result(Result, #{domain => Domain, name => RoomName, from => FromJID}). +send_msg_to_room(#{<<"room">> := RoomJID, <<"from">> := FromJID, <<"body">> := Message}) -> + Result = mod_muc_light_api:send_message(RoomJID, FromJID, Message), + format_result(Result, #{room => RoomJID, from => FromJID}). diff --git a/src/graphql/admin/mongoose_graphql_muc_light_admin_query.erl b/src/graphql/admin/mongoose_graphql_muc_light_admin_query.erl index e20385ab80..27a6f7fa58 100644 --- a/src/graphql/admin/mongoose_graphql_muc_light_admin_query.erl +++ b/src/graphql/admin/mongoose_graphql_muc_light_admin_query.erl @@ -28,33 +28,33 @@ list_user_rooms(#{<<"user">> := UserJID}) -> end. -spec list_room_users(map()) -> {ok, [map()]} | {error, resolver_error()}. -list_room_users(#{<<"domain">> := Domain, <<"id">> := RoomID}) -> - case mod_muc_light_api:get_room_aff(Domain, RoomID) of +list_room_users(#{<<"room">> := RoomJID}) -> + case mod_muc_light_api:get_room_aff(RoomJID) of {ok, Affs} -> {ok, [make_ok_user(A) || A <- Affs]}; Err -> - make_error(Err, #{domain => Domain, id => RoomID}) + make_error(Err, #{room => RoomJID}) end. -spec get_room_config(map()) -> {ok, map()} | {error, resolver_error()}. -get_room_config(#{<<"domain">> := Domain, <<"id">> := RoomID}) -> - case mod_muc_light_api:get_room_info(Domain, RoomID) of +get_room_config(#{<<"room">> := RoomJID}) -> + case mod_muc_light_api:get_room_info(RoomJID) of {ok, Room} -> {ok, make_room(Room)}; Err -> - make_error(Err, #{domain => Domain, id => RoomID}) + make_error(Err, #{room => RoomJID}) end. -spec get_room_messages(map()) -> {ok, map()} | {error, resolver_error()}. -get_room_messages(#{<<"domain">> := Domain, <<"id">> := RoomID, - <<"pageSize">> := PageSize, <<"before">> := Before}) -> +get_room_messages(#{<<"room">> := RoomJID, <<"pageSize">> := PageSize, + <<"before">> := Before}) -> Before2 = null_to_undefined(Before), - case mod_muc_light_api:get_room_messages(Domain, RoomID, PageSize, Before2) of + case mod_muc_light_api:get_room_messages(RoomJID, PageSize, Before2) of {ok, Rows} -> Maps = lists:map(fun mongoose_graphql_stanza_helper:row_to_map/1, Rows), {ok, #{<<"stanzas">> => Maps, <<"limit">> => null}}; Err -> - make_error(Err, #{domain => Domain, id => RoomID}) + make_error(Err, #{room => RoomJID}) end. %% Helpers diff --git a/src/mongoose_client_api/mongoose_client_api_rooms.erl b/src/mongoose_client_api/mongoose_client_api_rooms.erl index c117850324..e4e55e1274 100644 --- a/src/mongoose_client_api/mongoose_client_api_rooms.erl +++ b/src/mongoose_client_api/mongoose_client_api_rooms.erl @@ -123,13 +123,12 @@ from_json(Req, State) -> handle_request(Method, JSONData, Req, State) -> case handle_request_by_method(Method, JSONData, Req, State) of - {error, Short, Desc} -> - mongoose_client_api:bad_request(Req, format_error(Short, Desc), State); - Room -> - RoomJID = jid:from_binary(Room), + {ok, #{jid := RoomJID}} -> RespBody = #{<<"id">> => RoomJID#jid.luser}, RespReq = cowboy_req:set_resp_body(jiffy:encode(RespBody), Req), - {true, RespReq, State} + {true, RespReq, State}; + {Short, Desc} -> + mongoose_client_api:bad_request(Req, format_error(Short, Desc), State) end. format_error(Short, Desc) -> @@ -142,19 +141,16 @@ term_to_bin(Term) -> iolist_to_binary(io_lib:format("~p", [Term])). handle_request_by_method(<<"POST">>, JSONData, _Req, - #{user := User, jid := #jid{lserver = Server}}) -> + #{jid := #jid{lserver = LServer} = UserJID}) -> #{<<"name">> := RoomName, <<"subject">> := Subject} = JSONData, - mod_muc_light_commands:create_unique_room(Server, RoomName, User, Subject); - + MUCServer = muc_light_domain(LServer), + mod_muc_light_api:create_room(MUCServer, <<>>, RoomName, UserJID, Subject); handle_request_by_method(<<"PUT">>, JSONData, Req, State) -> assert_room_id_set(Req, State), - #{user := User, jid := #jid{lserver = Server}, room_id := RoomID} = State, + #{jid := #jid{lserver = LServer} = UserJID, room_id := RoomID} = State, #{<<"name">> := RoomName, <<"subject">> := Subject} = JSONData, - mod_muc_light_commands:create_identifiable_room(Server, - RoomID, - RoomName, - User, - Subject). + MUCServer = muc_light_domain(LServer), + mod_muc_light_api:create_room(MUCServer, RoomID, RoomName, UserJID, Subject). assert_room_id_set(_Req, #{room_id := _} = _State) -> ok. diff --git a/src/mongoose_client_api/mongoose_client_api_rooms_config.erl b/src/mongoose_client_api/mongoose_client_api_rooms_config.erl index a9b4845ce8..c9ec9df185 100644 --- a/src/mongoose_client_api/mongoose_client_api_rooms_config.erl +++ b/src/mongoose_client_api/mongoose_client_api_rooms_config.erl @@ -63,9 +63,8 @@ handle_request(Method, JSONData, Req, State) -> end. handle_request_by_method(<<"PUT">>, - #{<<"name">> := Name, <<"subject">> := Subject}, + #{<<"name">> := RoomName, <<"subject">> := Subject}, Req, State) -> mongoose_client_api_rooms:assert_room_id_set(Req, State), - #{user := User, jid := #jid{lserver = Server}, room_id := RoomID} = State, - UserJID = jid:from_binary(User), - mod_muc_light_api:change_room_config(Server, RoomID, Name, UserJID, Subject). + #{jid := UserJID, room := #{jid := RoomJID}} = State, + mod_muc_light_api:change_room_config(RoomJID, UserJID, RoomName, Subject). diff --git a/src/mongoose_client_api/mongoose_client_api_rooms_users.erl b/src/mongoose_client_api/mongoose_client_api_rooms_users.erl index d1984796cf..4139a90ec6 100644 --- a/src/mongoose_client_api/mongoose_client_api_rooms_users.erl +++ b/src/mongoose_client_api/mongoose_client_api_rooms_users.erl @@ -44,14 +44,14 @@ resource_exists(Req, State) -> allow_missing_post(Req, State) -> {false, Req, State}. -from_json(Req, #{user := User, - role_in_room := owner, - jid := #jid{lserver = Server}, - room_id := RoomID} = State) -> +from_json(Req, #{role_in_room := owner, + jid := UserJID, + room := #{jid := RoomJID}} = State) -> {ok, Body, Req2} = cowboy_req:read_body(Req), case mongoose_client_api:json_to_map(Body) of {ok, #{<<"user">> := UserToInvite}} when is_binary(UserToInvite) -> - mod_muc_light_commands:change_affiliation(Server, RoomID, User, UserToInvite, <<"member">>), + mod_muc_light_api:change_affiliation(RoomJID, UserJID, + jid:from_binary(UserToInvite), <<"member">>), {true, Req2, State}; _ -> {false, Req, State} @@ -61,20 +61,19 @@ from_json(Req, State) -> delete_resource(Req, #{role_in_room := none} = State) -> mongoose_client_api:forbidden_request(Req, State); -delete_resource(Req, #{role_in_room := owner, - user := User} = State) -> +delete_resource(Req, #{role_in_room := owner} = State) -> UserToRemove = cowboy_req:binding(user, Req), - remove_user_from_room(User, UserToRemove, Req, State); + remove_user_from_room(UserToRemove, Req, State); delete_resource(Req, #{user := User} = State) -> UserToRemove = cowboy_req:binding(user, Req), case UserToRemove of User -> - remove_user_from_room(User, User, Req, State); + remove_user_from_room(User, Req, State); _ -> mongoose_client_api:forbidden_request(Req, State) end. -remove_user_from_room(Remover, Target, Req, - #{jid := #jid{lserver = Server}, room_id := RoomID} = State) -> - mod_muc_light_commands:change_affiliation(Server, RoomID, Remover, Target, <<"none">>), +remove_user_from_room(Target, Req, + #{jid := UserJID, room := #{jid := RoomJID}} = State) -> + mod_muc_light_api:change_affiliation(RoomJID, UserJID, jid:from_binary(Target), <<"none">>), {true, Req, State}. diff --git a/src/muc_light/mod_muc_light.erl b/src/muc_light/mod_muc_light.erl index e2042cbf9b..6f58cf75b0 100644 --- a/src/muc_light/mod_muc_light.erl +++ b/src/muc_light/mod_muc_light.erl @@ -151,7 +151,8 @@ try_to_create_room(CreatorJid, RoomJID, #create{raw_config = RawConfig} = Creati change_room_config(UserJid, RoomID, MUCLightDomain, ConfigReq, Acc1) -> RoomJID = jid:make(RoomID, MUCLightDomain, <<>>), {Acc2, AffUsersRes} = get_room_affiliations_from_acc(Acc1, RoomJID), - case mod_muc_light_room:process_request(UserJid, RoomJID, {set, ConfigReq}, AffUsersRes, Acc2) of + case mod_muc_light_room:process_request(UserJid, RoomJID, {set, ConfigReq}, + AffUsersRes, Acc2) of {set, ConfigResp, _} -> {ok, RoomJID, ConfigResp}; {error, _Reason} = E -> diff --git a/src/muc_light/mod_muc_light_api.erl b/src/muc_light/mod_muc_light_api.erl index 264204d56d..62d639779b 100644 --- a/src/muc_light/mod_muc_light_api.erl +++ b/src/muc_light/mod_muc_light_api.erl @@ -2,17 +2,17 @@ -module(mod_muc_light_api). -export([create_room/5, - invite_to_room/4, - change_room_config/5, - change_affiliation/5, - remove_user_from_room/4, - send_message/4, - delete_room/3, + invite_to_room/3, + change_room_config/4, + change_affiliation/4, + remove_user_from_room/3, + send_message/3, delete_room/2, - get_room_messages/4, + delete_room/1, + get_room_messages/3, get_user_rooms/1, - get_room_info/2, - get_room_aff/2 + get_room_info/1, + get_room_aff/1 ]). -include("mod_muc_light.hrl"). @@ -20,138 +20,114 @@ -include("jlib.hrl"). -include("mongoose_rsm.hrl"). --type create_room_result() :: {ok, room()} | {exist | - max_occupants_reached | - validation_error, iolist()}. - --type change_room_config_result() :: {ok, room()} | {wrong_user | - not_allowed | - not_exists | - validation_error, iolist()}. - --type get_room_messages_result() :: {ok, []} | {domain_not_found | internal, iolist()}. - --type invite_to_room_result() :: {ok | user_without_room | not_found, iolist()}. - --type get_room_info_result() :: {ok, map()} | {domain_not_found | not_exists, iolist()}. - --type get_room_aff_result() :: {ok, [aff_user()]} | {domain_not_found | not_exists, iolist()}. - -type room() :: #{jid := jid:jid(), name := binary(), subject := binary(), - aff_users := aff_users() - }. + aff_users := aff_users()}. --export_type([room/0, create_room_result/0]). +-export_type([room/0]). --define(ROOM_NOT_EXIST_RESULT, {not_exists, "Room does not exist"}). +-define(ROOM_DELETED_SUCC_RESULT, {ok, "Room deleted successfully"}). +-define(USER_NOT_ROOM_MEMBER_RESULT, {not_room_member, "Given user does not occupy this room"}). +-define(ROOM_NOT_FOUND_RESULT, {room_not_found, "Room not found"}). +-define(MUC_SERVER_NOT_FOUND_RESULT, {muc_server_not_found, "MUC Light server not found"}). -define(VALIDATION_ERROR_RESULT(Key, Reason), {validation_error, io_lib:format("Validation failed for key: ~p with reason ~p", [Key, Reason])}). --define(USER_WITHOUT_ROOM_RESULT, {user_without_room, "Given user does not occupy any room"}). --spec create_room(jid:lserver(), binary(), binary(), jid:jid(), binary()) -> create_room_result(). -create_room(Domain, RoomId, RoomTitle, CreatorJID, Subject) -> - case get_muc_hosts(Domain) of - {ok, _HostType, MUCLightDomain} -> - MUCServiceJID = jid:make_bare(RoomId, MUCLightDomain), - Config = make_room_config(RoomTitle, Subject), - case mod_muc_light:try_to_create_room(CreatorJID, MUCServiceJID, Config) of - {ok, RoomJID, #create{aff_users = AffUsers}} -> - {ok, make_room(RoomJID, RoomTitle, Subject, AffUsers)}; - {error, exists} -> - {exist, "Room already exists"}; - {error, max_occupants_reached} -> - {max_occupants_reached, "Max occupants number reached"}; - {error, {Key, Reason}} -> - ?VALIDATION_ERROR_RESULT(Key, Reason) - end; - Error -> - Error +-spec create_room(jid:lserver(), binary(), binary(), jid:jid(), binary()) -> + {ok, room()} | {already_exists | max_occupants_reached | validation_error | + muc_server_not_found, iolist()}. +create_room(MUCLightDomain, RoomID, RoomTitle, CreatorJID, Subject) -> + MUCServiceJID = jid:make_bare(RoomID, MUCLightDomain), + Config = make_room_config(RoomTitle, Subject), + try mod_muc_light:try_to_create_room(CreatorJID, MUCServiceJID, Config) of + {ok, RoomJID, #create{aff_users = AffUsers}} -> + {ok, make_room(RoomJID, RoomTitle, Subject, AffUsers)}; + {error, exists} -> + {already_exist, "Room already exists"}; + {error, max_occupants_reached} -> + {max_occupants_reached, "Max occupants number reached"}; + {error, {Key, Reason}} -> + ?VALIDATION_ERROR_RESULT(Key, Reason) + catch + error:{muc_host_to_host_type_failed, _, _} -> + ?MUC_SERVER_NOT_FOUND_RESULT end. --spec invite_to_room(jid:lserver(), binary(), jid:jid(), jid:jid()) -> invite_to_room_result(). -invite_to_room(Domain, RoomName, SenderJID, RecipientJID) -> - case get_muc_hosts(Domain) of - {ok, HostType, MUCServer} -> + +-spec invite_to_room(jid:jid(), jid:jid(), jid:jid()) -> + {ok | not_room_member | muc_server_not_found, iolist()}. +invite_to_room(#jid{lserver = MUCServer} = RoomJID, SenderJID, RecipientJID) -> + case mongoose_domain_api:get_subdomain_host_type(MUCServer) of + {ok, HostType} -> RecipientBin = jid:to_binary(jid:to_bare(RecipientJID)), - case muc_light_room_name_to_jid_and_aff(HostType, SenderJID, RoomName, MUCServer) of - {ok, R, _Aff} -> + case is_user_room_member(HostType, jid:to_lus(SenderJID), jid:to_lus(RoomJID)) of + true -> S = jid:to_bare(SenderJID), + R = jid:to_bare(RoomJID), Changes = query(?NS_MUC_LIGHT_AFFILIATIONS, [affiliate(RecipientBin, <<"member">>)]), ejabberd_router:route(S, R, iq(jid:to_binary(S), jid:to_binary(R), <<"set">>, [Changes])), {ok, "User invited successfully"}; - {error, given_user_does_not_occupy_any_room} -> - ?USER_WITHOUT_ROOM_RESULT; - {error, not_exists} -> - ?ROOM_NOT_EXIST_RESULT + false -> + ?USER_NOT_ROOM_MEMBER_RESULT end; - Error -> - Error + {error, not_found} -> + ?MUC_SERVER_NOT_FOUND_RESULT end. --spec change_room_config(jid:lserver(), binary(), binary(), jid:jid(), binary()) -> - change_room_config_result(). -change_room_config(Domain, RoomID, RoomName, UserJID, Subject) -> - case get_muc_hosts(Domain) of - {ok, HostType, MUCLightDomain} -> - LServer = jid:nameprep(Domain), + +-spec change_room_config(jid:jid(), jid:jid(), binary(), binary()) -> + {ok, room()} | {not_room_member | not_allowed | room_not_found | + validation_error | muc_server_not_found, iolist()}. +change_room_config(#jid{luser = RoomID, lserver = MUCServer} = RoomJID, + UserJID, RoomName, Subject) -> + case mongoose_domain_api:get_subdomain_info(MUCServer) of + {ok, #{host_type := HostType, parent_domain := LServer}} -> UserUS = jid:to_bare(UserJID), ConfigReq = #config{ raw_config = [{<<"roomname">>, RoomName}, {<<"subject">>, Subject}]}, Acc = mongoose_acc:new(#{location => ?LOCATION, lserver => LServer, host_type => HostType}), - case mod_muc_light:change_room_config(UserUS, RoomID, MUCLightDomain, ConfigReq, Acc) of + case mod_muc_light:change_room_config(UserUS, RoomID, MUCServer, ConfigReq, Acc) of {ok, RoomJID, _} -> {ok, make_room(RoomJID, RoomName, Subject, [])}; {error, item_not_found} -> - {wrong_user, "The given user is not room participant"}; + ?USER_NOT_ROOM_MEMBER_RESULT; {error, not_allowed} -> - {not_allowed, "The given user has not permission to change config"}; + {not_allowed, "Given user does not have permission to change config"}; {error, not_exists} -> - ?ROOM_NOT_EXIST_RESULT; + ?ROOM_NOT_FOUND_RESULT; {error, {error, {Key, Reason}}} -> ?VALIDATION_ERROR_RESULT(Key, Reason) end; - Error -> - Error + {error, not_found} -> + ?MUC_SERVER_NOT_FOUND_RESULT end. --spec change_affiliation(jid:lserver(), binary(), jid:jid(), jid:jid(), binary()) -> - ok | {domain_not_found, iolist()}. -change_affiliation(Domain, RoomID, SenderJID, RecipientJID, Affiliation) -> - case get_muc_hosts(Domain) of - {ok, _HostType, MUCLightDomain} -> +-spec change_affiliation(jid:jid(), jid:jid(), jid:jid(), binary()) -> ok. +change_affiliation(RoomJID, SenderJID, RecipientJID, Affiliation) -> RecipientBare = jid:to_bare(RecipientJID), - R = jid:make_bare(RoomID, MUCLightDomain), S = jid:to_bare(SenderJID), Changes = query(?NS_MUC_LIGHT_AFFILIATIONS, [affiliate(jid:to_binary(RecipientBare), Affiliation)]), - ejabberd_router:route(S, R, iq(jid:to_binary(S), jid:to_binary(R), + ejabberd_router:route(S, RoomJID, iq(jid:to_binary(S), jid:to_binary(RoomJID), <<"set">>, [Changes])), - ok; - Error -> - Error - end. - --spec remove_user_from_room(jid:lserver(), binary(), jid:jid(), jid:jid()) -> - {ok | domain_not_found, iolist()}. -remove_user_from_room(Domain, RoomID, SenderJID, RecipientJID) -> - case change_affiliation(Domain, RoomID, SenderJID, RecipientJID, <<"none">>) of - ok -> - {ok, io_lib:format("User ~s kicked successfully", [jid:to_binary(RecipientJID)])}; - Error -> - Error - end. - --spec send_message(jid:lserver(), binary(), jid:jid(), binary()) -> - {ok | domain_not_found | room_not_found | user_without_room, iolist()}. -send_message(Domain, RoomName, SenderJID, Message) -> - case get_muc_hosts(Domain) of - {ok, HostType, MUCServer} -> + ok. + +-spec remove_user_from_room(jid:jid(), jid:jid(), jid:jid()) -> + {ok, iolist()}. +remove_user_from_room(RoomJID, SenderJID, RecipientJID) -> + ok = change_affiliation(RoomJID, SenderJID, RecipientJID, <<"none">>), + {ok, io_lib:format("User ~s kicked successfully", [jid:to_binary(RecipientJID)])}. + +-spec send_message(jid:jid(), jid:jid(), binary()) -> + {ok | not_room_member | muc_server_not_found, iolist()}. +send_message(#jid{lserver = MUCServer} = RoomJID, SenderJID, Message) -> + case mongoose_domain_api:get_subdomain_host_type(MUCServer) of + {ok, HostType} -> Body = #xmlel{name = <<"body">>, children = [ #xmlcdata{ content = Message } ] }, @@ -160,61 +136,56 @@ send_message(Domain, RoomName, SenderJID, Message) -> children = [ Body ] }, SenderBare = jid:to_bare(SenderJID), - SenderUS = jid:to_lus(SenderBare), - case mod_muc_light_db_backend:get_user_rooms(HostType, SenderUS, MUCServer) of - [] -> - ?USER_WITHOUT_ROOM_RESULT; - RoomJIDs when is_list(RoomJIDs) -> - FindFun = find_room_and_user_aff_by_room_name(HostType, RoomName, SenderUS), - case lists:foldl(FindFun, none, RoomJIDs) of - {ok, {RU, MUCServer}, _Aff} -> - R = jid:make_bare(RU, MUCServer), - ejabberd_router:route(SenderBare, R, Stanza), - {ok, "Message send successfully"}; - none -> - {room_not_found, "Room does not found"} - end + case is_user_room_member(HostType, jid:to_lus(SenderBare), jid:to_lus(RoomJID)) of + true -> + RoomBare = jid:to_bare(RoomJID), + ejabberd_router:route(SenderBare, RoomBare, Stanza), + {ok, "Message sent successfully"}; + false -> + ?USER_NOT_ROOM_MEMBER_RESULT end; - Error -> - Error + {error, not_found}-> + ?MUC_SERVER_NOT_FOUND_RESULT end. --spec delete_room(jid:lserver(), binary(), jid:jid()) -> - { ok | domain_not_found | not_exists | user_without_room, iolist()}. -delete_room(Domain, RoomName, OwnerJID) -> - OwnerBare = jid:to_bare(OwnerJID), - case get_muc_hosts(Domain) of - {ok, HostType, MUCServer} -> - Res = case muc_light_room_name_to_jid_and_aff(HostType, OwnerBare, - RoomName, MUCServer) of - {ok, RoomJID, owner} -> - mod_muc_light:delete_room(jid:to_lus(RoomJID)); - {ok, _, _} -> - {error, not_allowed}; - {error, _} = Err -> - Err - end, - format_delete_error_message(Res); - Error -> - Error +-spec delete_room(jid:jid(), jid:jid()) -> + {ok | not_allowed | room_not_found | muc_server_not_found , iolist()}. +delete_room(#jid{lserver = MUCServer} = RoomJID, UserJID) -> + case mongoose_domain_api:get_subdomain_host_type(MUCServer) of + {ok, HostType} -> + case get_room_user_aff(HostType, RoomJID, UserJID) of + {ok, owner} -> + ok = mod_muc_light_db_backend:destroy_room(HostType, jid:to_lus(RoomJID)), + ?ROOM_DELETED_SUCC_RESULT; + {ok, none} -> + ?USER_NOT_ROOM_MEMBER_RESULT; + {ok, member} -> + {not_allowed, "Given user cannot delete this room"}; + {error, room_not_found} -> + {room_not_found, "Cannot remove not existing room"} + end; + {error, not_found}-> + ?MUC_SERVER_NOT_FOUND_RESULT end. --spec delete_room(jid:lserver(), binary()) -> { ok | domain_not_found | not_exists, iolist()}. -delete_room(Domain, RoomID) -> - case get_muc_hosts(Domain) of - {ok, _HostType, MUCLightDomain} -> - Res = mod_muc_light:delete_room({RoomID, MUCLightDomain}), - format_delete_error_message(Res); - Error -> - Error +-spec delete_room(jid:jid()) -> {ok | room_not_found | muc_server_not_found, iolist()}. +delete_room(RoomJID) -> + try mod_muc_light:delete_room(jid:to_lus(RoomJID)) of + ok -> + ?ROOM_DELETED_SUCC_RESULT; + {error, not_exists} -> + ?ROOM_NOT_FOUND_RESULT + catch + error:{muc_host_to_host_type_failed, _, _} -> + ?MUC_SERVER_NOT_FOUND_RESULT end. --spec get_room_messages(jid:lserver(), binary(), integer() | undefined, - mod_mam:unix_timestamp() | undefined) -> get_room_messages_result(). -get_room_messages(Domain, RoomID, PageSize, Before) -> - case get_muc_hosts(Domain) of - {ok, HostType, MUCLightDomain} -> - RoomJID = jid:make_bare(RoomID, MUCLightDomain), +-spec get_room_messages(jid:jid(), integer() | undefined, + mod_mam:unix_timestamp() | undefined) -> + {ok, list()} | {muc_server_not_found | internal, iolist()}. +get_room_messages(RoomJID, PageSize, Before) -> + case mongoose_domain_api:get_subdomain_host_type(RoomJID#jid.lserver) of + {ok, HostType} -> Now = os:system_time(microsecond), ArchiveID = mod_mam_muc:archive_id_int(HostType, RoomJID), End = maybe_before(Before, Now), @@ -239,68 +210,57 @@ get_room_messages(Domain, RoomID, PageSize, Before) -> {error, Term} -> {internal, io_lib:format("Internal error occured ~p", [Term])} end; - Error -> - Error + {error, not_found}-> + ?MUC_SERVER_NOT_FOUND_RESULT end. --spec get_room_info(jid:lserver(), binary()) -> get_room_info_result(). -get_room_info(Domain, RoomID) -> - case get_muc_hosts(Domain) of - {ok, HostType, MUCServer} -> - case mod_muc_light_db_backend:get_info(HostType, {RoomID, MUCServer}) of + +-spec get_room_info(jid:jid()) -> {ok, room()} | {muc_server_not_found | room_not_found, iolist()}. +get_room_info(#jid{lserver = MUCServer} = RoomJID) -> + case mongoose_domain_api:get_subdomain_host_type(MUCServer) of + {ok, HostType} -> + case mod_muc_light_db_backend:get_info(HostType, jid:to_lus(RoomJID)) of {ok, [{roomname, Name}, {subject, Subject}], AffUsers, _Version} -> - {ok, make_room(jid:make_bare(RoomID, MUCServer), Name, Subject, AffUsers)}; + {ok, make_room(jid:to_binary(RoomJID), Name, Subject, AffUsers)}; {error, not_exists} -> - ?ROOM_NOT_EXIST_RESULT + ?ROOM_NOT_FOUND_RESULT end; - Error -> - Error + {error, not_found}-> + ?MUC_SERVER_NOT_FOUND_RESULT end. --spec get_room_aff(jid:lserver(), binary()) -> get_room_aff_result(). -get_room_aff(Domain, RoomID) -> - case get_room_info(Domain, RoomID) of - {ok, #{aff_users := AffUsers}} -> - {ok, AffUsers}; - Error -> - Error +-spec get_room_aff(jid:jid()) -> + {ok, aff_users()} | {muc_server_not_found | room_not_found, iolist()}. +get_room_aff(#jid{lserver = MUCServer} = RoomJID) -> + case mongoose_domain_api:get_subdomain_host_type(MUCServer) of + {ok, HostType} -> + case mod_muc_light_db_backend:get_aff_users(HostType, jid:to_lus(RoomJID)) of + {ok, AffUsers, _Version} -> + {ok, AffUsers}; + {error, not_exists} -> + ?ROOM_NOT_FOUND_RESULT + end; + {error, not_found}-> + ?MUC_SERVER_NOT_FOUND_RESULT end. -spec get_user_rooms(jid:jid()) -> {ok, [RoomUS :: jid:simple_bare_jid()]} | - {domain_not_found, iolist()}. + {muc_server_not_found, iolist()}. get_user_rooms(#jid{lserver = LServer} = UserJID) -> - case get_muc_hosts(LServer) of - {ok, HostType, MUCServer} -> + case mongoose_domain_api:get_domain_host_type(LServer) of + {ok, HostType} -> UserUS = jid:to_lus(UserJID), + MUCServer = mod_muc_light_utils:server_host_to_muc_host(HostType, LServer), {ok, mod_muc_light_db_backend:get_user_rooms(HostType, UserUS, MUCServer)}; - Error -> - Error + {error, not_found} -> + ?MUC_SERVER_NOT_FOUND_RESULT end. %% Internal --spec get_muc_hosts(jid:lserver()) -> {ok, mongooseim:host_type(), jid:lserver()} | - {domain_not_found, iolist()}. -get_muc_hosts(LServer) -> - case mongoose_domain_api:get_domain_host_type(LServer) of - {ok, HostType} -> - {ok, HostType, mod_muc_light_utils:server_host_to_muc_host(HostType, LServer)}; - {error, not_found} -> - {domain_not_found, io_lib:format("Domain ~s does not exist", [LServer])} - end. - make_room(JID, Name, Subject, AffUsers) -> #{jid => JID, name => Name, subject => Subject, aff_users => AffUsers}. -format_delete_error_message(ok) -> - {ok, "Room deleted successfully!"}; -format_delete_error_message({error, not_allowed}) -> - {not_allowed, "You cannot delete this room"}; -format_delete_error_message({error, not_exists}) -> - {not_exists, "Cannot remove not existing room"}; -format_delete_error_message({error, given_user_does_not_occupy_any_room}) -> - ?USER_WITHOUT_ROOM_RESULT. - iq(To, From, Type, Children) -> UUID = uuid:uuid_to_string(uuid:get_v4(), binary_standard), #xmlel{name = <<"iq">>, @@ -329,54 +289,29 @@ make_room_config(Name, Subject) -> {<<"subject">>, Subject}] }. --spec muc_light_room_name_to_jid_and_aff(HostType :: mongooseim:host_type(), - UserJID :: jid:jid(), - RoomName :: binary(), - Domain :: jid:lserver()) -> - {ok, jid:jid(), aff()} | {error, given_user_does_not_occupy_any_room} | {error, not_exists}. -muc_light_room_name_to_jid_and_aff(HostType, UserJID, RoomName, MUCServer) -> +-spec get_room_user_aff(mongooseim:host_type(), jid:jid(), jid:jid()) -> + {ok, aff()} | {error, room_not_found}. +get_room_user_aff(HostType, RoomJID, UserJID) -> + RoomUS = jid:to_lus(RoomJID), UserUS = jid:to_lus(UserJID), - case mod_muc_light_db_backend:get_user_rooms(HostType, UserUS, MUCServer) of - [] -> - {error, given_user_does_not_occupy_any_room}; - RoomUSs when is_list(RoomUSs) -> - FindFun = find_room_and_user_aff_by_room_name(HostType, RoomName, UserUS), - case lists:foldl(FindFun, none, RoomUSs) of - {ok, {RU, MUCServer}, UserAff} -> - {ok, jid:make_bare(RU, MUCServer), UserAff}; - none -> - {error, not_exists} - end - end. - --spec get_room_name_and_user_aff(mongooseim:host_type(), RoomUS :: jid:simple_bare_jid(), - UserUS :: jid:simple_bare_jid()) -> - {ok, RoomName :: binary(), UserAff :: aff()} | {error, not_exists}. -get_room_name_and_user_aff(HostType, RoomUS, UserUS) -> - case mod_muc_light_db_backend:get_info(HostType, RoomUS) of - {ok, Cfg, Affs, _} -> - {roomname, RoomName} = lists:keyfind(roomname, 1, Cfg), - {_, UserAff} = lists:keyfind(UserUS, 1, Affs), - {ok, RoomName, UserAff}; - Error -> - Error + case mod_muc_light_db_backend:get_aff_users(HostType, RoomUS) of + {ok, Affs, _Version} -> + case lists:keyfind(UserUS, 1, Affs) of + {_, Aff} -> {ok, Aff}; + false -> {ok, none} + end; + {error, not_exists} -> + {error, room_not_found} end. --type find_room_acc() :: {ok, RoomUS :: jid:simple_bare_jid(), UserAff :: aff()} | none. - --spec find_room_and_user_aff_by_room_name(mongooseim:host_type(), RoomName :: binary(), - UserUS :: jid:simple_bare_jid()) -> - fun((RoomUS :: jid:simple_bare_jid(), find_room_acc()) -> find_room_acc()). -find_room_and_user_aff_by_room_name(HostType, RoomName, UserUS) -> - fun (RoomUS, none) -> - case get_room_name_and_user_aff(HostType, RoomUS, UserUS) of - {ok, RoomName, UserAff} -> - {ok, RoomUS, UserAff}; - _ -> - none - end; - (_, Acc) when Acc =/= none -> - Acc +-spec is_user_room_member(mongooseim:host_type(), jid:simple_bare_jid(), + jid:simple_bare_jid()) -> boolean(). +is_user_room_member(HostType, UserUS, {_, MUCServer} = RoomLUS) -> + case mod_muc_light_db_backend:get_user_rooms(HostType, UserUS, MUCServer) of + [] -> + false; + RoomJIDs when is_list(RoomJIDs) -> + lists:any(fun(LUS) -> LUS =:= RoomLUS end, RoomJIDs) end. maybe_before(undefined, Now) -> diff --git a/src/muc_light/mod_muc_light_commands.erl b/src/muc_light/mod_muc_light_commands.erl index d759ed5b41..52836ee004 100644 --- a/src/muc_light/mod_muc_light_commands.erl +++ b/src/muc_light/mod_muc_light_commands.erl @@ -28,11 +28,11 @@ -export([create_identifiable_room/5]). -export([send_message/4]). -export([invite_to_room/4]). --export([change_affiliation/5]). -export([delete_room/3]). -export([change_room_config/5]). --ignore_xref([delete_room/3, invite_to_room/4, send_message/4, change_room_config/5]). +-ignore_xref([create_identifiable_room/5, create_unique_room/4, delete_room/3, + invite_to_room/4, send_message/4, change_room_config/5]). %%-------------------------------------------------------------------- %% `gen_mod' callbacks @@ -159,57 +159,66 @@ commands() -> %% Internal procedures %%-------------------------------------------------------------------- -create_unique_room(Domain, RoomName, Creator, Subject) -> +-spec create_unique_room(jid:server(), jid:user(), jid:literal_jid(), binary()) -> + jid:literal_jid() | {error, not_found | denied, iolist()}. +create_unique_room(MUCServer, RoomName, Creator, Subject) -> CreatorJID = jid:from_binary(Creator), - case mod_muc_light_api:create_room(Domain, <<>>, RoomName, CreatorJID, Subject) of + case mod_muc_light_api:create_room(MUCServer, <<>>, RoomName, CreatorJID, Subject) of {ok, #{jid := JID}} -> jid:to_binary(JID); Error -> format_err_result(Error) end. -create_identifiable_room(Domain, Identifier, RoomName, Creator, Subject) -> +-spec create_identifiable_room(jid:server(), jid:user(), binary(), + jid:literal_jid(), binary()) -> + jid:literal_jid() | {error, not_found | denied, iolist()}. +create_identifiable_room(MUCServer, Identifier, RoomName, Creator, Subject) -> CreatorJID = jid:from_binary(Creator), - case mod_muc_light_api:create_room(Domain, Identifier, RoomName, CreatorJID, Subject) of + case mod_muc_light_api:create_room(MUCServer, Identifier, RoomName, CreatorJID, Subject) of {ok, #{jid := JID}} -> jid:to_binary(JID); Error -> format_err_result(Error) end. -invite_to_room(Domain, RoomName, Sender, Recipient) -> +-spec invite_to_room(jid:server(), jid:user(), jid:literal_jid(), jid:literal_jid()) -> + ok | {error, not_found | denied, iolist()}. +invite_to_room(MUCServer, RoomID, Sender, Recipient) -> SenderJID = jid:from_binary(Sender), RecipientJID = jid:from_binary(Recipient), - Result = mod_muc_light_api:invite_to_room(Domain, RoomName, SenderJID, RecipientJID), + RoomJID = jid:make_bare(RoomID, MUCServer), + Result = mod_muc_light_api:invite_to_room(RoomJID, SenderJID, RecipientJID), format_result_no_msg(Result). -change_affiliation(Domain, RoomID, Sender, Recipient, Affiliation) -> - % FIXME use mod_muc_light_api instead of this in the client api - SenderJID = jid:from_binary(Sender), - RecipientJID = jid:from_binary(Recipient), - mod_muc_light_api:change_affiliation(Domain, RoomID, SenderJID, RecipientJID, Affiliation). - -change_room_config(Domain, RoomID, RoomName, User, Subject) -> +-spec change_room_config(jid:server(), jid:user(), binary(), jid:literal_jid(), binary()) -> + ok | {error, not_found | denied, iolist()}. +change_room_config(MUCServer, RoomID, RoomName, User, Subject) -> UserJID = jid:from_binary(User), - Result = mod_muc_light_api:change_room_config(Domain, RoomID, RoomName, UserJID, Subject), + RoomJID = jid:make_bare(RoomID, MUCServer), + Result = mod_muc_light_api:change_room_config(RoomJID, UserJID, RoomName, Subject), format_result_no_msg(Result). -send_message(Domain, RoomName, Sender, Message) -> +-spec send_message(jid:server(), jid:user(), jid:literal_jid(), jid:literal_jid()) -> + ok | {error, not_found | denied, iolist()}. +send_message(MUCServer, RoomID, Sender, Message) -> SenderJID = jid:from_binary(Sender), - Result = mod_muc_light_api:send_message(Domain, RoomName, SenderJID, Message), + RoomJID = jid:make_bare(RoomID, MUCServer), + Result = mod_muc_light_api:send_message(RoomJID, SenderJID, Message), format_result_no_msg(Result). --spec delete_room(jid:lserver(), binary(), jid:literal_jid()) -> ok | {error, atom(), term()}. -delete_room(DomainName, RoomName, Owner) -> +-spec delete_room(jid:server(), jid:user(), jid:literal_jid()) -> + ok | {error, not_found | denied, iolist()}. +delete_room(MUCServer, RoomID, Owner) -> OwnerJID = jid:from_binary(Owner), - Result = mod_muc_light_api:delete_room(DomainName, RoomName, OwnerJID), + RoomJID = jid:make_bare(RoomID, MUCServer), + Result = mod_muc_light_api:delete_room(RoomJID, OwnerJID), format_result_no_msg(Result). format_result_no_msg({ok, _}) -> ok; format_result_no_msg(Res) -> format_err_result(Res). -format_err_result({ResStatus, Msg}) when not_exists =:= ResStatus; - room_not_found =:= ResStatus; - domain_not_found =:= ResStatus -> +format_err_result({ResStatus, Msg}) when room_not_found =:= ResStatus; + muc_server_not_found =:= ResStatus -> {error, not_found, Msg}; -format_err_result({ResStatus, Msg}) when exist =:= ResStatus; +format_err_result({ResStatus, Msg}) when already_exist =:= ResStatus; not_allowed =:= ResStatus; - user_without_room =:= ResStatus -> + not_room_member =:= ResStatus -> {error, denied, Msg}; format_err_result({_, Reason}) -> {error, internal, Reason}. From e84b9c3d5256f945121f68da5a31ce685e608d4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Wojtasik?= Date: Wed, 23 Feb 2022 09:26:08 +0100 Subject: [PATCH 07/10] Add affiliance enum and format JIDs in errors --- priv/graphql/schemas/admin/muc_light.gql | 9 ++------- priv/graphql/schemas/global/muc.gql | 5 +++++ .../mongoose_graphql_muc_light_admin_mutation.erl | 15 ++++++++------- src/graphql/mongoose_graphql_enum.erl | 9 +++++++-- src/graphql/mongoose_graphql_muc_light_helper.erl | 2 +- 5 files changed, 23 insertions(+), 17 deletions(-) create mode 100644 priv/graphql/schemas/global/muc.gql diff --git a/priv/graphql/schemas/admin/muc_light.gql b/priv/graphql/schemas/admin/muc_light.gql index 58f970e272..8ac229df1b 100644 --- a/priv/graphql/schemas/admin/muc_light.gql +++ b/priv/graphql/schemas/admin/muc_light.gql @@ -3,7 +3,7 @@ Allow admin to manage Multi-User Chat Light rooms. """ type MUCLightAdminMutation @protected{ "Create a MUC light room under the given XMPP hostname" - createRoom(domain: String!, name: String!, owner: JID!, subject: String!, id: String): Room + createRoom(mucDomain: String!, name: String!, owner: JID!, subject: String!, id: String): Room "Change configuration of a MUC Light room" changeRoomConfiguration(room: JID!, owner: JID!, name: String!, subject: String!): Room "Invite a user to a MUC Light room" @@ -30,11 +30,6 @@ type MUCLightAdminQuery @protected{ listUserRooms(user: JID!): [JID!] } -type RoomPayload{ - message: String! - room: Room -} - type Room{ jid: JID name: String @@ -44,5 +39,5 @@ type Room{ type RoomUser{ jid: JID - affiliance: String + affiliation: Affiliation } diff --git a/priv/graphql/schemas/global/muc.gql b/priv/graphql/schemas/global/muc.gql new file mode 100644 index 0000000000..e3dd9e7ee5 --- /dev/null +++ b/priv/graphql/schemas/global/muc.gql @@ -0,0 +1,5 @@ +enum Affiliation{ + OWNER + MEMBER + NONE +} diff --git a/src/graphql/admin/mongoose_graphql_muc_light_admin_mutation.erl b/src/graphql/admin/mongoose_graphql_muc_light_admin_mutation.erl index 2fa99508e0..3a1007bbcd 100644 --- a/src/graphql/admin/mongoose_graphql_muc_light_admin_mutation.erl +++ b/src/graphql/admin/mongoose_graphql_muc_light_admin_mutation.erl @@ -25,13 +25,13 @@ execute(_Ctx, _Obj, <<"sendMessageToRoom">>, Args) -> -spec create_room(map()) -> {ok, map()} | {error, resolver_error()}. create_room(#{<<"id">> := null} = Args) -> create_room(Args#{<<"id">> => <<>>}); -create_room(#{<<"id">> := RoomID, <<"domain">> := Domain, <<"name">> := RoomName, +create_room(#{<<"id">> := RoomID, <<"mucDomain">> := MUCDomain, <<"name">> := RoomName, <<"owner">> := CreatorJID, <<"subject">> := Subject}) -> - case mod_muc_light_api:create_room(Domain, RoomID, RoomName, CreatorJID, Subject) of + case mod_muc_light_api:create_room(MUCDomain, RoomID, RoomName, CreatorJID, Subject) of {ok, Room} -> {ok, make_room(Room)}; Err -> - make_error(Err, #{domain => Domain, roomID => RoomID, creator => CreatorJID}) + make_error(Err, #{mucDomain => MUCDomain, id => RoomID, creator => CreatorJID}) end. -spec change_room_config(map()) -> {ok, map()} | {error, resolver_error()}. @@ -41,19 +41,20 @@ change_room_config(#{<<"room">> := RoomJID, <<"name">> := RoomName, {ok, Room} -> {ok, make_room(Room)}; Err -> - make_error(Err, #{room => RoomJID, owner => OwnerJID}) + make_error(Err, #{room => jid:to_binary(RoomJID), owner => jid:to_binary(OwnerJID)}) end. -spec delete_room(map()) -> {ok, binary()} | {error, resolver_error()}. delete_room(#{<<"room">> := RoomJID}) -> Result = mod_muc_light_api:delete_room(RoomJID), - format_result(Result, #{room => RoomJID}). + format_result(Result, #{room => jid:to_binary(RoomJID)}). -spec invite_user(map()) -> {ok, binary()} | {error, resolver_error()}. invite_user(#{<<"room">> := RoomJID, <<"sender">> := SenderJID, <<"recipient">> := RecipientJID}) -> Result = mod_muc_light_api:invite_to_room(RoomJID, SenderJID, RecipientJID), - format_result(Result, #{room => RoomJID, sender => SenderJID, recipient => RecipientJID}). + format_result(Result, #{room => jid:to_binary(RoomJID), sender => jid:to_binary(SenderJID), + recipient => jid:to_binary(RecipientJID)}). -spec kick_user(map()) -> {ok, binary()} | {error, resolver_error()}. kick_user(#{<<"room">> := RoomJID, <<"user">> := UserJID}) -> @@ -63,4 +64,4 @@ kick_user(#{<<"room">> := RoomJID, <<"user">> := UserJID}) -> -spec send_msg_to_room(map()) -> {ok, binary()} | {error, resolver_error()}. send_msg_to_room(#{<<"room">> := RoomJID, <<"from">> := FromJID, <<"body">> := Message}) -> Result = mod_muc_light_api:send_message(RoomJID, FromJID, Message), - format_result(Result, #{room => RoomJID, from => FromJID}). + format_result(Result, #{room => jid:to_binary(RoomJID), from => jid:to_binary(FromJID)}). diff --git a/src/graphql/mongoose_graphql_enum.erl b/src/graphql/mongoose_graphql_enum.erl index b67f50b8ce..861979a9b3 100644 --- a/src/graphql/mongoose_graphql_enum.erl +++ b/src/graphql/mongoose_graphql_enum.erl @@ -9,11 +9,16 @@ input(<<"PresenceShow">>, Show) -> input(<<"PresenceType">>, Type) -> {ok, list_to_binary(string:to_lower(binary_to_list(Type)))}; input(<<"AuthStatus">>, <<"AUTHORIZED">>) -> {ok, 'AUTHORIZED'}; -input(<<"AuthStatus">>, <<"UNAUTHORIZED">>) -> {ok, 'UNAUTHORIZED'}. +input(<<"AuthStatus">>, <<"UNAUTHORIZED">>) -> {ok, 'UNAUTHORIZED'}; +input(<<"Affiliation">>, <<"OWNER">>) -> {ok, owner}; +input(<<"Affiliation">>, <<"MEMBER">>) -> {ok, member}; +input(<<"Affiliation">>, <<"NONE">>) -> {ok, none}. output(<<"PresenceShow">>, Show) -> {ok, list_to_binary(string:to_upper(binary_to_list(Show)))}; output(<<"PresenceType">>, Type) -> {ok, list_to_binary(string:to_upper(binary_to_list(Type)))}; output(<<"AuthStatus">>, Status) -> - {ok, atom_to_binary(Status, utf8)}. + {ok, atom_to_binary(Status, utf8)}; +output(<<"Affiliation">>, Aff) -> + {ok, list_to_binary(string:to_upper(atom_to_list(Aff)))}. diff --git a/src/graphql/mongoose_graphql_muc_light_helper.erl b/src/graphql/mongoose_graphql_muc_light_helper.erl index abe97da494..6f194dbe2f 100644 --- a/src/graphql/mongoose_graphql_muc_light_helper.erl +++ b/src/graphql/mongoose_graphql_muc_light_helper.erl @@ -9,4 +9,4 @@ make_room(#{jid := JID, name := Name, subject := Subject, aff_users := Users}) - <<"participants">> => Participants}. make_ok_user({JID, Aff}) -> - {ok, #{<<"jid">> => JID, <<"affiliance">> => atom_to_binary(Aff)}}. + {ok, #{<<"jid">> => JID, <<"affiliation">> => Aff}}. From e14f8f6470f4e2c768bbb7cc2073e86f5d1e35ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Wojtasik?= Date: Wed, 23 Feb 2022 09:44:42 +0100 Subject: [PATCH 08/10] Adapt tests and doc to the Rest API changes Revert `XMPPMUCHOST` for admin REST API instead of `XMPPHOST`. Apply review comments. --- big_tests/tests/graphql_muc_light_SUITE.erl | 320 ++++++++++-------- big_tests/tests/muc_light_http_api_SUITE.erl | 65 ++-- big_tests/tests/rest_client_SUITE.erl | 6 +- .../Administration-backend_swagger.yml | 8 +- 4 files changed, 220 insertions(+), 179 deletions(-) diff --git a/big_tests/tests/graphql_muc_light_SUITE.erl b/big_tests/tests/graphql_muc_light_SUITE.erl index 83d9ed1670..949b6e3e1c 100644 --- a/big_tests/tests/graphql_muc_light_SUITE.erl +++ b/big_tests/tests/graphql_muc_light_SUITE.erl @@ -62,13 +62,8 @@ init_modules(Config) -> dynamic_modules:ensure_modules(HostType, required_modules(suite)), Config2. -required_modules(SuiteOrTC) -> - [{mod_muc_light, common_muc_light_opts() ++ muc_light_opts(SuiteOrTC)}]. - -muc_light_opts(config_can_be_changed_by_all) -> - [{all_can_configure, true}]; -muc_light_opts(suite) -> - []. +required_modules(_) -> + [{mod_muc_light, common_muc_light_opts()}]. common_muc_light_opts() -> MucPattern = distributed_helper:subhost_pattern(muc_light_helper:muc_host_pattern()), @@ -98,81 +93,91 @@ admin_create_room(Config) -> admin_create_room_story(Config, Alice) -> AliceBin = escalus_client:short_jid(Alice), AliceBinLower = escalus_utils:jid_to_lower(AliceBin), - Domain = escalus_client:server(Alice), MucServer = ?config(muc_light_host, Config), Name = <<"first room">>, Subject = <<"testing">>, - Res = execute_auth(admin_create_room_body(Domain, Name, AliceBin, Subject, null), Config), + Res = execute_auth(admin_create_room_body(MucServer, Name, AliceBin, Subject, null), Config), Path = [data, muc_light, createRoom], #{<<"jid">> := JID, <<"name">> := Name, <<"subject">> := Subject, <<"participants">> := Participants} = get_ok_value(Path, Res), ?assertMatch(#jid{server = MucServer}, jid:from_binary(JID)), - ?assertEqual([#{<<"jid">> => AliceBinLower, <<"affiliance">> => <<"owner">>}], Participants), + ?assertEqual([#{<<"jid">> => AliceBinLower, <<"affiliation">> => <<"OWNER">>}], Participants), % Try with a non-existing domain - Res2 = execute_auth(admin_create_room_body(?UNKNOWN_DOMAIN, Name, AliceBin, Subject, null), Config), - ?assertNotEqual(nomatch, binary:match(get_err_msg(Res2), <<"does not exist">>)). + Res2 = execute_auth(admin_create_room_body(?UNKNOWN_DOMAIN, Name, AliceBin, Subject, null), + Config), + ?assertNotEqual(nomatch, binary:match(get_err_msg(Res2), <<"not found">>)). admin_create_identified_room(Config) -> escalus:fresh_story_with_config(Config, [{alice, 1}], fun admin_create_identified_room_story/2). admin_create_identified_room_story(Config, Alice) -> AliceBin = escalus_client:short_jid(Alice), - Domain = escalus_client:server(Alice), MucServer = ?config(muc_light_host, Config), Name = <<"first room">>, Subject = <<"testing">>, Id = <<"my_room">>, - Res = execute_auth(admin_create_room_body(Domain, Name, AliceBin, Subject, Id), Config), + Res = execute_auth(admin_create_room_body(MucServer, Name, AliceBin, Subject, Id), Config), Path = [data, muc_light, createRoom], #{<<"jid">> := JID, <<"name">> := Name, <<"subject">> := Subject} = get_ok_value(Path, Res), ?assertMatch(#jid{user = Id, server = MucServer}, jid:from_binary(JID)), % Create a room with an existing ID - Res2 = execute_auth(admin_create_room_body(Domain, <<"snd room">>, AliceBin, Subject, Id), Config), + Res2 = execute_auth(admin_create_room_body(MucServer, <<"snd room">>, AliceBin, Subject, Id), + Config), ?assertNotEqual(nomatch, binary:match(get_err_msg(Res2), <<"already exists">>)), % Try with a non-existing domain - Res3 = execute_auth(admin_create_room_body(?UNKNOWN_DOMAIN, <<>>, AliceBin, Subject, Id), Config), - ?assertNotEqual(nomatch, binary:match(get_err_msg(Res3), <<"does not exist">>)). + Res3 = execute_auth(admin_create_room_body(?UNKNOWN_DOMAIN, <<"name">>, AliceBin, Subject, Id), + Config), + ?assertNotEqual(nomatch, binary:match(get_err_msg(Res3), <<"not found">>)). admin_change_room_config(Config) -> escalus:fresh_story_with_config(Config, [{alice, 1}], fun admin_change_room_config_story/2). admin_change_room_config_story(Config, Alice) -> AliceBin = escalus_client:short_jid(Alice), - Domain = escalus_client:server(Alice), + MUCServer = ?config(muc_light_host, Config), Name = <<"first room">>, Subject = <<"testing">>, - Id = atom_to_binary(?FUNCTION_NAME), % Create a new room - execute_auth(admin_create_room_body(Domain, Name, AliceBin, Subject, Id), Config), + {ok, #{jid := RoomJID}} = create_room(<<>>, MUCServer, Name, Subject, AliceBin), % Try to change the room configuration Name2 = <<"changed room">>, Subject2 = <<"not testing">>, - Res = execute_auth(admin_change_room_configuration_body(Id, Domain, AliceBin, Name2, Subject2), Config), + Res = execute_auth(admin_change_room_configuration_body(jid:to_binary(RoomJID), + AliceBin, Name2, Subject2), Config), Path = [data, muc_light, changeRoomConfiguration], ?assertMatch(#{<<"name">> := Name2, <<"subject">> := Subject2}, get_ok_value(Path, Res)). admin_change_room_config_errors(Config) -> - escalus:fresh_story_with_config(Config, [{alice, 1}, {bob, 1}], fun admin_change_room_config_errors_story/3). + escalus:fresh_story_with_config(Config, [{alice, 1}, {bob, 1}], + fun admin_change_room_config_errors_story/3). admin_change_room_config_errors_story(Config, Alice, Bob) -> AliceBin = escalus_client:short_jid(Alice), BobBin = escalus_client:short_jid(Bob), - Domain = escalus_client:server(Alice), + MUCServer = ?config(muc_light_host, Config), RoomName = <<"first room">>, - {ok, #{jid := #jid{luser = RoomID}}} = create_room(<<>>, Domain, RoomName, <<>>, AliceBin), - {ok, _} = invite_user(Domain, RoomName, AliceBin, BobBin), + {ok, #{jid := #jid{luser = RoomID} = RoomJID}} = + create_room(<<>>, MUCServer, RoomName, <<"subject">>, AliceBin), + {ok, _} = invite_user(RoomJID, AliceBin, BobBin), % Try to change the config with a non-existing domain - Res = execute_auth(admin_change_room_configuration_body(RoomID, ?UNKNOWN_DOMAIN, AliceBin, RoomName, <<>>), Config), - ?assertNotEqual(nomatch, binary:match(get_err_msg(Res), <<"does not exist">>)), + Res = execute_auth(admin_change_room_configuration_body( + make_bare_jid(RoomID, ?UNKNOWN_DOMAIN), AliceBin, RoomName, <<"subject2">>), Config), + ?assertNotEqual(nomatch, binary:match(get_err_msg(Res), <<"not found">>)), % Try to change the config of the non-existing room - Res2 = execute_auth(admin_change_room_configuration_body(<<"unknown">>, Domain, AliceBin, RoomName, <<>>), Config), - ?assertNotEqual(nomatch, binary:match(get_err_msg(Res2), <<"does not exist">>)), + Res2 = execute_auth(admin_change_room_configuration_body( + make_bare_jid(<<"unknown">>, MUCServer), AliceBin, + RoomName, <<"subject2">>), Config), + ?assertNotEqual(nomatch, binary:match(get_err_msg(Res2), <<"not found">>)), % Try to change the config by the non-existing user - Res3 = execute_auth(admin_change_room_configuration_body(RoomID, Domain, <<"wrong-user@wrong-domain">>, RoomName, <<>>), Config), - ?assertNotEqual(nomatch, binary:match(get_err_msg(Res3), <<"not room participant">>)), + Res3 = execute_auth(admin_change_room_configuration_body( + jid:to_binary(RoomJID), <<"wrong-user@wrong-domain">>, + RoomName, <<"subject2">>), Config), + ?assertNotEqual(nomatch, binary:match(get_err_msg(Res3), <<"not occupy this room">>)), % Try to change a config by the user without permission - Res4 = execute_auth(admin_change_room_configuration_body(RoomID, Domain, BobBin, RoomName, <<>>), Config), - ?assertNotEqual(nomatch, binary:match(get_err_msg(Res4), <<"not permission to change">>)). + Res4 = execute_auth(admin_change_room_configuration_body( + jid:to_binary(RoomJID), BobBin, RoomName, <<"subject2">>), Config), + ?assertNotEqual(nomatch, binary:match(get_err_msg(Res4), + <<"does not have permission to change">>)). admin_invite_user(Config) -> escalus:fresh_story_with_config(Config, [{alice, 1}, {bob, 1}], fun admin_invite_user_story/3). @@ -181,12 +186,11 @@ admin_invite_user_story(Config, Alice, Bob) -> AliceBin = escalus_client:short_jid(Alice), BobBin = escalus_client:short_jid(Bob), Domain = escalus_client:server(Alice), + MUCServer = ?config(muc_light_host, Config), Name = <<"first room">>, - Name2 = <<"second room">>, - {ok, #{jid := RoomJID}} = create_room(<<>>, Domain, Name, <<>>, AliceBin), - {ok, _} = create_room(<<>>, Domain, Name2, <<>>, AliceBin), + {ok, #{jid := RoomJID}} = create_room(<<>>, MUCServer, Name, <<"subject2">>, AliceBin), - Res = execute_auth(admin_invite_user_body(Domain, Name, AliceBin, BobBin), Config), + Res = execute_auth(admin_invite_user_body(jid:to_binary(RoomJID), AliceBin, BobBin), Config), ?assertNotEqual(nomatch, binary:match(get_ok_value([data, muc_light, inviteUser], Res), <<"successfully">>)), BobName = escalus_utils:jid_to_lower(escalus_client:username(Bob)), @@ -196,40 +200,47 @@ admin_invite_user_story(Config, Alice, Bob) -> ?assertMatch(ExpectedAff, lists:sort(get_room_aff(RoomJID))). admin_invite_user_errors(Config) -> - escalus:fresh_story_with_config(Config, [{alice, 1}, {bob, 1}], fun admin_invite_user_errors_story/3). + escalus:fresh_story_with_config(Config, [{alice, 1}, {bob, 1}], + fun admin_invite_user_errors_story/3). admin_invite_user_errors_story(Config, Alice, Bob) -> AliceBin = escalus_client:short_jid(Alice), BobBin = escalus_client:short_jid(Bob), - Domain = escalus_client:server(Alice), - Name = <<"first room">>, - {ok, #{jid := _RoomJID}} = create_room(<<>>, Domain, Name, <<>>, AliceBin), + MUCServer = ?config(muc_light_host, Config), + {ok, #{jid := #jid{luser = RoomID}}} = + create_room(<<>>, MUCServer, <<"first room">>, <<"subject">>, AliceBin), % Try to invite a user to not existing room - Res = execute_auth(admin_invite_user_body(Domain, <<>>, AliceBin, BobBin), Config), - ?assertNotEqual(nomatch, binary:match(get_err_msg(Res), <<"does not exist">>)), + Res = execute_auth(admin_invite_user_body( + make_bare_jid(?UNKNOWN, MUCServer), AliceBin, BobBin), Config), + ?assertNotEqual(nomatch, binary:match(get_err_msg(Res), <<"does not occupy this room">>)), % User without rooms tries to invite a user - Res2 = execute_auth(admin_invite_user_body(Domain, <<>>, BobBin, AliceBin), Config), - ?assertNotEqual(nomatch, binary:match(get_err_msg(Res2), <<"does not occupy any room">>)), + Res2 = execute_auth(admin_invite_user_body( + make_bare_jid(?UNKNOWN, MUCServer), BobBin, AliceBin), Config), + ?assertNotEqual(nomatch, binary:match(get_err_msg(Res2), <<"does not occupy this room">>)), % Try with a non-existing domain - Res3 = execute_auth(admin_invite_user_body(?UNKNOWN_DOMAIN, Name, AliceBin, BobBin), Config), - ?assertNotEqual(nomatch, binary:match(get_err_msg(Res3), <<"does not exist">>)). + Res3 = execute_auth(admin_invite_user_body( + make_bare_jid(RoomID, ?UNKNOWN_DOMAIN), AliceBin, BobBin), Config), + ?assertNotEqual(nomatch, binary:match(get_err_msg(Res3), <<"not found">>)). admin_delete_room(Config) -> escalus:fresh_story_with_config(Config, [{alice, 1}], fun admin_delete_room_story/2). admin_delete_room_story(Config, Alice) -> AliceBin = escalus_client:short_jid(Alice), - Domain = escalus_client:server(Alice), + MUCServer = ?config(muc_light_host, Config), Name = <<"first room">>, RoomID = <<"delete_room_id">>, - {ok, #{jid := RoomJID}} = create_room(RoomID, Domain, Name, <<>>, AliceBin), - Res = execute_auth(admin_delete_room_body(Domain, RoomID), Config), + {ok, #{jid := RoomJID}} = create_room(RoomID, MUCServer, Name, <<"subject">>, AliceBin), + Res = execute_auth(admin_delete_room_body(jid:to_binary(RoomJID)), Config), ?assertNotEqual(nomatch, binary:match(get_ok_value([data, muc_light, deleteRoom], Res), <<"successfully">>)), ?assertEqual({error, not_exists}, get_room_info(jid:from_binary(RoomJID))), % Try with a non-existing domain - Res2 = execute_auth(admin_delete_room_body(?UNKNOWN_DOMAIN, RoomID), Config), - ?assertNotEqual(nomatch, binary:match(get_err_msg(Res2), <<"does not exist">>)). + Res2 = execute_auth(admin_delete_room_body(make_bare_jid(RoomID, ?UNKNOWN_DOMAIN)), Config), + ?assertNotEqual(nomatch, binary:match(get_err_msg(Res2), <<"not found">>)), + % Try with a non-existing room + Res3 = execute_auth(admin_delete_room_body(make_bare_jid(?UNKNOWN, MUCServer)), Config), + ?assertNotEqual(nomatch, binary:match(get_err_msg(Res3), <<"not found">>)). admin_kick_user(Config) -> escalus:fresh_story_with_config(Config, [{alice, 1}, {bob, 1}], fun admin_kick_user_story/3). @@ -237,19 +248,16 @@ admin_kick_user(Config) -> admin_kick_user_story(Config, Alice, Bob) -> AliceBin = escalus_client:short_jid(Alice), BobBin = escalus_client:short_jid(Bob), - Domain = escalus_client:server(Alice), + MUCServer = ?config(muc_light_host, Config), RoomName = <<"first room">>, RoomID = <<"kick_user_test_room">>, - {ok, #{jid := RoomJID}} = create_room(RoomID, Domain, RoomName, <<>>, AliceBin), - {ok, _} = invite_user(Domain, RoomName, AliceBin, BobBin), + {ok, #{jid := RoomJID}} = create_room(RoomID, MUCServer, RoomName, <<"subject">>, AliceBin), + {ok, _} = invite_user(RoomJID, AliceBin, BobBin), ?assertEqual(2, length(get_room_aff(RoomJID))), - Res = execute_auth(admin_kick_user_body(Domain, RoomID, BobBin), Config), + Res = execute_auth(admin_kick_user_body(jid:to_binary(RoomJID), BobBin), Config), ?assertNotEqual(nomatch, binary:match(get_ok_value([data, muc_light, kickUser], Res), <<"successfully">>)), - ?assertEqual(1, length(get_room_aff(RoomJID))), - % Try with a non-existing domain - Res2 = execute_auth(admin_kick_user_body(?UNKNOWN_DOMAIN, RoomID, BobBin), Config), - ?assertNotEqual(nomatch, binary:match(get_err_msg(Res2), <<"does not exist">>)). + ?assertEqual(1, length(get_room_aff(RoomJID))). admin_send_message_to_room(Config) -> escalus:fresh_story_with_config(Config, [{alice, 1}, {bob, 1}], @@ -258,13 +266,13 @@ admin_send_message_to_room(Config) -> admin_send_message_to_room_story(Config, Alice, Bob) -> AliceBin = escalus_client:short_jid(Alice), BobBin = escalus_client:short_jid(Bob), - Domain = escalus_client:server(Alice), + MUCServer = ?config(muc_light_host, Config), RoomName = <<"first room">>, - RoomID = <<"send_message_test_room">>, MsgBody = <<"Hello there!">>, - {ok, #{jid := _RoomJID}} = create_room(RoomID, Domain, RoomName, <<>>, AliceBin), - {ok, _} = invite_user(Domain, RoomName, AliceBin, BobBin), - Res = execute_auth(admin_send_message_to_room_body(Domain, RoomName, AliceBin, MsgBody), Config), + {ok, #{jid := RoomJID}} = create_room(<<>>, MUCServer, RoomName, <<"subject">>, AliceBin), + {ok, _} = invite_user(RoomJID, AliceBin, BobBin), + Res = execute_auth(admin_send_message_to_room_body( + jid:to_binary(RoomJID), AliceBin, MsgBody), Config), ?assertNotEqual(nomatch, binary:match(get_ok_value([data, muc_light, sendMessageToRoom], Res), <<"successfully">>)). @@ -275,21 +283,23 @@ admin_send_message_to_room_errors(Config) -> admin_send_message_to_room_errors_story(Config, Alice, Bob) -> AliceBin = escalus_client:short_jid(Alice), BobBin = escalus_client:short_jid(Bob), - Domain = escalus_client:server(Alice), - ARoomName = <<"alice room">>, - BRoomName = <<"bob room">>, + MUCServer = ?config(muc_light_host, Config), MsgBody = <<"Hello there!">>, - {ok, #{jid := _RoomJID}} = create_room(<<>>, Domain, ARoomName, <<>>, AliceBin), + {ok, #{jid := #jid{luser = ARoomID} = ARoomJID}} = + create_room(<<>>, MUCServer, <<"alice room">>, <<"subject">>, AliceBin), % Try with a non-existing domain - Res2 = execute_auth(admin_send_message_to_room_body(?UNKNOWN_DOMAIN, ARoomName, AliceBin, MsgBody), Config), - ?assertNotEqual(nomatch, binary:match(get_err_msg(Res2), <<"does not exist">>)), + Res2 = execute_auth(admin_send_message_to_room_body( + make_bare_jid(ARoomID, ?UNKNOWN_DOMAIN), AliceBin, MsgBody), Config), + ?assertNotEqual(nomatch, binary:match(get_err_msg(Res2), <<"not found">>)), % Try with a user without rooms - Res3 = execute_auth(admin_send_message_to_room_body(Domain, ARoomName, BobBin, MsgBody), Config), - ?assertNotEqual(nomatch, binary:match(get_err_msg(Res3), <<"does not occupy any room">>)), + Res3 = execute_auth(admin_send_message_to_room_body( + jid:to_binary(ARoomJID), BobBin, MsgBody), Config), + ?assertNotEqual(nomatch, binary:match(get_err_msg(Res3), <<"does not occupy this room">>)), % Try with a room not occupied by this user - {ok, #{jid := _RoomJID2}} = create_room(<<>>, Domain, BRoomName, <<>>, BobBin), - Res4 = execute_auth(admin_send_message_to_room_body(Domain, ARoomName, BobBin, MsgBody), Config), - ?assertNotEqual(nomatch, binary:match(get_err_msg(Res4), <<"does not found">>)). + {ok, #{jid := _RoomJID2}} = create_room(<<>>, MUCServer, <<"bob room">>, <<"subject">>, BobBin), + Res4 = execute_auth(admin_send_message_to_room_body( + jid:to_binary(ARoomJID), BobBin, MsgBody), Config), + ?assertNotEqual(nomatch, binary:match(get_err_msg(Res4), <<"does not occupy this room">>)). admin_get_room_messages(Config) -> escalus:fresh_story_with_config(Config, [{alice, 1}], fun admin_get_room_messages_story/2). @@ -297,26 +307,28 @@ admin_get_room_messages(Config) -> admin_get_room_messages_story(Config, Alice) -> Path = [data, muc_light, getRoomMessages, stanzas], AliceBin = escalus_client:short_jid(Alice), - Domain = escalus_client:server(Alice), + %Domain = escalus_client:server(Alice), + MUCServer = ?config(muc_light_host, Config), RoomName = <<"first room">>, RoomName2 = <<"second room">>, - RoomID = <<"get_messages_test_room">>, - {ok, #{jid := _RoomJID}} = create_room(RoomID, Domain, RoomName, <<>>, AliceBin), - {ok, _} = create_room(<<>>, Domain, RoomName2, <<>>, AliceBin), + {ok, #{jid := #jid{luser = RoomID} = RoomJID}} = + create_room(<<>>, MUCServer, RoomName, <<"subject">>, AliceBin), + {ok, _} = create_room(<<>>, MUCServer, RoomName2, <<"subject">>, AliceBin), Message = <<"Hello friends">>, - send_message_to_room(Domain, RoomName, jid:from_binary(AliceBin), Message), + send_message_to_room(RoomJID, jid:from_binary(AliceBin), Message), mam_helper:maybe_wait_for_archive(Config), % Get messages so far - Res = execute_auth(admin_get_room_messages_body(Domain, RoomID, 50, null), Config), + Res = execute_auth(admin_get_room_messages_body(jid:to_binary(RoomJID), 50, null), Config), [#{<<"stanza">> := StanzaXML}] = get_ok_value(Path, Res), ?assertMatch({ok, #xmlel{name = <<"message">>}}, exml:parse(StanzaXML)), % Get messages before the given date and time Before = <<"2022-02-17T04:54:13+00:00">>, - Res2 = execute_auth(admin_get_room_messages_body(Domain, RoomID, 50, Before), Config), + Res2 = execute_auth(admin_get_room_messages_body(jid:to_binary(RoomJID), 50, Before), Config), ?assertMatch([], get_ok_value(Path, Res2)), % Try with a non-existing domain - Res3 = execute_auth(admin_get_room_messages_body(?UNKNOWN_DOMAIN, RoomID, 50, null), Config), - ?assertNotEqual(nomatch, binary:match(get_err_msg(Res3), <<"does not exist">>)). + Res3 = execute_auth(admin_get_room_messages_body( + make_bare_jid(RoomID, ?UNKNOWN_DOMAIN), 50, null), Config), + ?assertNotEqual(nomatch, binary:match(get_err_msg(Res3), <<"not found">>)). admin_list_user_rooms(Config) -> escalus:fresh_story_with_config(Config, [{alice, 1}], fun admin_list_user_rooms_story/2). @@ -325,10 +337,11 @@ admin_list_user_rooms_story(Config, Alice) -> Path = [data, muc_light, listUserRooms], AliceBin = escalus_client:short_jid(Alice), Domain = escalus_client:server(Alice), + MUCServer = ?config(muc_light_host, Config), RoomName = <<"first room">>, RoomName2 = <<"second room">>, - {ok, #{jid := RoomJID}} = create_room(<<>>, Domain, RoomName, <<>>, AliceBin), - {ok, #{jid := RoomJID2}} = create_room(<<>>, Domain, RoomName2, <<>>, AliceBin), + {ok, #{jid := RoomJID}} = create_room(<<>>, MUCServer, RoomName, <<"subject">>, AliceBin), + {ok, #{jid := RoomJID2}} = create_room(<<>>, MUCServer, RoomName2, <<"subject">>, AliceBin), Res = execute_auth(admin_list_user_rooms_body(AliceBin), Config), ?assertEqual(lists:sort([jid:to_binary(RoomJID), jid:to_binary(RoomJID2)]), lists:sort(get_ok_value(Path, Res))), @@ -337,7 +350,7 @@ admin_list_user_rooms_story(Config, Alice) -> ?assertEqual([], lists:sort(get_ok_value(Path, Res2))), % Try with a non-existing domain Res3 = execute_auth(admin_list_user_rooms_body(<<"not-exist@not-exist">>), Config), - ?assertNotEqual(nomatch, binary:match(get_err_msg(Res3), <<"does not exist">>)). + ?assertNotEqual(nomatch, binary:match(get_err_msg(Res3), <<"not found">>)). admin_list_room_users(Config) -> escalus:fresh_story_with_config(Config, [{alice, 1}], fun admin_list_room_users_story/2). @@ -345,15 +358,20 @@ admin_list_room_users(Config) -> admin_list_room_users_story(Config, Alice) -> AliceBin = escalus_client:short_jid(Alice), AliceLower = escalus_utils:jid_to_lower(AliceBin), - Domain = escalus_client:server(Alice), + MUCServer = ?config(muc_light_host, Config), RoomName = <<"first room">>, - {ok, #{jid := #jid{luser = RoomID}}} = create_room(<<>>, Domain, RoomName, <<>>, AliceBin), - Res = execute_auth(admin_list_room_users_body(Domain, RoomID), Config), - ?assertEqual([#{<<"jid">> => AliceLower, <<"affiliance">> => <<"owner">>}], + {ok, #{jid := RoomJID}} = create_room(<<>>, MUCServer, RoomName, <<"subject">>, AliceBin), + Res = execute_auth(admin_list_room_users_body(jid:to_binary(RoomJID)), Config), + ?assertEqual([#{<<"jid">> => AliceLower, <<"affiliation">> => <<"OWNER">>}], get_ok_value([data, muc_light, listRoomUsers], Res)), % Try with a non-existing domain - Res2 = execute_auth(admin_list_room_users_body(?UNKNOWN_DOMAIN, RoomID), Config), - ?assertNotEqual(nomatch, binary:match(get_err_msg(Res2), <<"does not exist">>)). + Res2 = execute_auth(admin_list_room_users_body( + make_bare_jid(RoomJID#jid.luser, ?UNKNOWN_DOMAIN)), Config), + ?assertNotEqual(nomatch, binary:match(get_err_msg(Res2), <<"not found">>)), + % Try with a non-existing room + Res3 = execute_auth(admin_list_room_users_body( + make_bare_jid(?UNKNOWN, MUCServer)), Config), + ?assertNotEqual(nomatch, binary:match(get_err_msg(Res3), <<"not found">>)). admin_get_room_config(Config) -> escalus:fresh_story_with_config(Config, [{alice, 1}], fun admin_get_room_config_story/2). @@ -361,27 +379,32 @@ admin_get_room_config(Config) -> admin_get_room_config_story(Config, Alice) -> AliceBin = escalus_client:short_jid(Alice), AliceLower = escalus_utils:jid_to_lower(AliceBin), - Domain = escalus_client:server(Alice), + MUCServer = ?config(muc_light_host, Config), RoomName = <<"first room">>, RoomSubject = <<"Room about nothing">>, - {ok, #{jid := #jid{luser = RoomID} = RoomJID}} = create_room(<<>>, Domain, RoomName, - RoomSubject, AliceBin), + {ok, #{jid := #jid{luser = RoomID} = RoomJID}} = + create_room(<<>>, MUCServer, RoomName, RoomSubject, AliceBin), RoomJIDBin = jid:to_binary(RoomJID), - Res = execute_auth(admin_get_room_config_body(Domain, RoomID), Config), + Res = execute_auth(admin_get_room_config_body(jid:to_binary(RoomJID)), Config), ?assertEqual(#{<<"jid">> => RoomJIDBin, <<"subject">> => RoomSubject, <<"name">> => RoomName, - <<"participants">> => [#{<<"jid">> => AliceLower, <<"affiliance">> => <<"owner">>}]}, + <<"participants">> => [#{<<"jid">> => AliceLower, + <<"affiliation">> => <<"OWNER">>}]}, get_ok_value([data, muc_light, getRoomConfig], Res)), % Try with a non-existing domain - Res2 = execute_auth(admin_get_room_config_body(?UNKNOWN_DOMAIN, RoomID), Config), - ?assertNotEqual(nomatch, binary:match(get_err_msg(Res2), <<"does not exist">>)), + Res2 = execute_auth(admin_get_room_config_body(make_bare_jid(RoomID, ?UNKNOWN_DOMAIN)), Config), + ?assertNotEqual(nomatch, binary:match(get_err_msg(Res2), <<"not found">>)), % Try with a non-existing room - Res3 = execute_auth(admin_get_room_config_body(Domain, ?UNKNOWN), Config), - ?assertNotEqual(nomatch, binary:match(get_err_msg(Res3), <<"does not exist">>)). + Res3 = execute_auth(admin_get_room_config_body(make_bare_jid(?UNKNOWN, MUCServer)), Config), + ?assertNotEqual(nomatch, binary:match(get_err_msg(Res3), <<"not found">>)). %% Helpers -send_message_to_room(Domain, RoomName, SenderJID, Message) -> - rpc(mim(), mod_muc_light_api, send_message, [Domain, RoomName, SenderJID, Message]). +make_bare_jid(User, Server) -> + JID = jid:make_bare(User, Server), + jid:to_binary(JID). + +send_message_to_room(RoomJID, SenderJID, Message) -> + rpc(mim(), mod_muc_light_api, send_message, [RoomJID, SenderJID, Message]). get_room_messages(ID, Domain) -> {ok, Messages} = rpc(mim(), mod_muc_light_api, get_room_messages, [Domain, ID]), @@ -391,10 +414,10 @@ create_room(Id, Domain, Name, Subject, CreatorBin) -> CreatorJID = jid:from_binary(CreatorBin), rpc(mim(), mod_muc_light_api, create_room, [Domain, Id, Name, CreatorJID, Subject]). -invite_user(Domain, RoomName, SenderBin, RecipientBin) -> +invite_user(RoomJID, SenderBin, RecipientBin) -> SenderJID = jid:from_binary(SenderBin), RecipientJID = jid:from_binary(RecipientBin), - rpc(mim(), mod_muc_light_api, invite_to_room, [Domain, RoomName, SenderJID, RecipientJID]). + rpc(mim(), mod_muc_light_api, invite_to_room, [RoomJID, SenderJID, RecipientJID]). get_room_info(JID) -> HostType = domain_helper:host_type(), @@ -407,57 +430,58 @@ get_room_aff(JID) -> %% Request bodies -admin_create_room_body(Domain, Name, Owner, Subject, Id) -> - Query = <<"mutation M1($domain: String!, $name: String!, $owner: JID!, $subject: String!, $id: String) - { muc_light { createRoom(domain: $domain, name: $name, owner: $owner, subject: $subject, id: $id) - { jid name subject participants {jid affiliance} } } }">>, +admin_create_room_body(MUCDomain, Name, Owner, Subject, Id) -> + Query = <<"mutation M1($mucDomain: String!, $name: String!, $owner: JID!, $subject: String!, $id: String) + { muc_light { createRoom(mucDomain: $mucDomain, name: $name, owner: $owner, subject: $subject, id: $id) + { jid name subject participants {jid affiliation} } } }">>, OpName = <<"M1">>, - Vars = #{<<"domain">> => Domain, <<"name">> => Name, <<"owner">> => Owner, <<"subject">> => Subject, <<"id">> => Id}, + Vars = #{<<"mucDomain">> => MUCDomain, <<"name">> => Name, <<"owner">> => Owner, + <<"subject">> => Subject, <<"id">> => Id}, #{query => Query, operationName => OpName, variables => Vars}. -admin_change_room_configuration_body(Id, Domain, Owner, Name, Subject) -> - Query = <<"mutation M1($id: String!, $domain: String!, $name: String!, $owner: JID!, $subject: String!) - { muc_light { changeRoomConfiguration(id: $id, domain: $domain, name: $name, owner: $owner, subject: $subject) - { jid name subject participants {jid affiliance} } } }">>, +admin_change_room_configuration_body(RoomJID, OwnerJID, Name, Subject) -> + Query = <<"mutation M1($room: JID!, $name: String!, $owner: JID!, $subject: String!) + { muc_light { changeRoomConfiguration(room: $room, name: $name, owner: $owner, subject: $subject) + { jid name subject participants {jid affiliation} } } }">>, OpName = <<"M1">>, - Vars = #{<<"id">> => Id, <<"domain">> => Domain, <<"name">> => Name, <<"owner">> => Owner, <<"subject">> => Subject}, + Vars = #{<<"room">> => RoomJID, <<"name">> => Name, <<"owner">> => OwnerJID, + <<"subject">> => Subject}, #{query => Query, operationName => OpName, variables => Vars}. -admin_invite_user_body(Domain, Name, Sender, Recipient) -> - Query = <<"mutation M1($domain: String!, $name: String!, $sender: JID!, $recipient: JID!) - { muc_light { inviteUser(domain: $domain, name: $name, sender: $sender, recipient: $recipient) } }">>, +admin_invite_user_body(RoomJID, Sender, Recipient) -> + Query = <<"mutation M1($room: JID!, $sender: JID!, $recipient: JID!) + { muc_light { inviteUser(room: $room, sender: $sender, recipient: $recipient) } }">>, OpName = <<"M1">>, - Vars = #{<<"domain">> => Domain, <<"name">> => Name, <<"sender">> => Sender, <<"recipient">> => Recipient}, + Vars = #{<<"room">> => RoomJID, <<"sender">> => Sender, <<"recipient">> => Recipient}, #{query => Query, operationName => OpName, variables => Vars}. -admin_delete_room_body(Domain, RoomID) -> - Query = <<"mutation M1($domain: String!, $id: String!) - { muc_light { deleteRoom(domain: $domain, id: $id)} }">>, +admin_delete_room_body(RoomJID) -> + Query = <<"mutation M1($room: JID!) + { muc_light { deleteRoom(room: $room) } }">>, OpName = <<"M1">>, - Vars = #{<<"domain">> => Domain, <<"id">> => RoomID}, + Vars = #{<<"room">> => RoomJID}, #{query => Query, operationName => OpName, variables => Vars}. -admin_kick_user_body(Domain, RoomID, User) -> - Query = <<"mutation M1($domain: String!, $id: String!, $user: JID!) - { muc_light { kickUser(domain: $domain, id: $id, user: $user)} }">>, +admin_kick_user_body(RoomJID, User) -> + Query = <<"mutation M1($room: JID!, $user: JID!) + { muc_light { kickUser(room: $room, user: $user)} }">>, OpName = <<"M1">>, - Vars = #{<<"domain">> => Domain, <<"id">> => RoomID, <<"user">> => User}, + Vars = #{<<"room">> => RoomJID, <<"user">> => User}, #{query => Query, operationName => OpName, variables => Vars}. -admin_send_message_to_room_body(Domain, RoomName, From, Body) -> - Query = <<"mutation M1($domain: String!, $name: String!, $from: JID!, $body: String!) - { muc_light { sendMessageToRoom(domain: $domain, name: $name, from: $from, body: $body)} }">>, +admin_send_message_to_room_body(RoomJID, From, Body) -> + Query = <<"mutation M1($room: JID!, $from: JID!, $body: String!) + { muc_light { sendMessageToRoom(room: $room, from: $from, body: $body)} }">>, OpName = <<"M1">>, - Vars = #{<<"domain">> => Domain, <<"name">> => RoomName, <<"from">> => From, <<"body">> => Body}, + Vars = #{<<"room">> => RoomJID, <<"from">> => From, <<"body">> => Body}, #{query => Query, operationName => OpName, variables => Vars}. -admin_get_room_messages_body(Domain, RoomID, PageSize, Before) -> - Query = <<"query Q1($domain: String!, $id: String!, $pageSize: Int!, $before: DateTime) - { muc_light { getRoomMessages(domain: $domain, id: $id, pageSize: $pageSize, before: $before) +admin_get_room_messages_body(RoomJID, PageSize, Before) -> + Query = <<"query Q1($room: JID!, $pageSize: Int!, $before: DateTime) + { muc_light { getRoomMessages(room: $room, pageSize: $pageSize, before: $before) { stanzas { stanza } } } }">>, OpName = <<"Q1">>, - Vars = #{<<"domain">> => Domain, <<"id">> => RoomID, - <<"pageSize">> => PageSize, <<"before">> => Before}, + Vars = #{<<"room">> => RoomJID, <<"pageSize">> => PageSize, <<"before">> => Before}, #{query => Query, operationName => OpName, variables => Vars}. admin_list_user_rooms_body(User) -> @@ -467,18 +491,18 @@ admin_list_user_rooms_body(User) -> Vars = #{<<"user">> => User}, #{query => Query, operationName => OpName, variables => Vars}. -admin_list_room_users_body(Domain, RoomID) -> - Query = <<"query Q1($domain: String!, $id: String!) - { muc_light { listRoomUsers(domain: $domain, id: $id) - { jid affiliance } } }">>, +admin_list_room_users_body(RoomJID) -> + Query = <<"query Q1($room: JID!) + { muc_light { listRoomUsers(room: $room) + { jid affiliation} } }">>, OpName = <<"Q1">>, - Vars = #{<<"domain">> => Domain, <<"id">> => RoomID}, + Vars = #{<<"room">> => RoomJID}, #{query => Query, operationName => OpName, variables => Vars}. -admin_get_room_config_body(Domain, RoomID) -> - Query = <<"query Q1($domain: String!, $id: String!) - { muc_light { getRoomConfig(domain: $domain, id: $id) - { jid name subject participants {jid affiliance} } } }">>, +admin_get_room_config_body(RoomJID) -> + Query = <<"query Q1($room: JID!) + { muc_light { getRoomConfig(room: $room) + { jid name subject participants {jid affiliation} } } }">>, OpName = <<"Q1">>, - Vars = #{<<"domain">> => Domain, <<"id">> => RoomID}, + Vars = #{<<"room">> => RoomJID}, #{query => Query, operationName => OpName, variables => Vars}. diff --git a/big_tests/tests/muc_light_http_api_SUITE.erl b/big_tests/tests/muc_light_http_api_SUITE.erl index f546d60a3b..794bb5b98d 100644 --- a/big_tests/tests/muc_light_http_api_SUITE.erl +++ b/big_tests/tests/muc_light_http_api_SUITE.erl @@ -54,7 +54,8 @@ negative_response() -> [delete_room_by_non_owner, delete_non_existent_room, delete_room_without_having_a_membership, - create_non_unique_room + create_non_unique_room, + create_room_on_non_existing_muc_server ]. %%-------------------------------------------------------------------- @@ -92,7 +93,8 @@ end_per_testcase(CaseName, Config) -> create_unique_room(Config) -> escalus:fresh_story(Config, [{alice, 1}], fun(Alice) -> - Path = path([domain()]), + MUCLightDomain = muc_light_domain(), + Path = path([MUCLightDomain]), Name = <<"wonderland">>, Body = #{ name => Name, owner => escalus_client:short_jid(Alice), @@ -100,14 +102,14 @@ create_unique_room(Config) -> }, {{<<"201">>, _}, _} = rest_helper:post(admin, Path, Body), [Item] = get_disco_rooms(Alice), - MUCLightDomain = muc_light_domain(), true = is_room_name(Name, Item), true = is_room_domain(MUCLightDomain, Item) end). create_identifiable_room(Config) -> escalus:fresh_story(Config, [{alice, 1}], fun(Alice) -> - Path = path([domain()]), + MUCLightDomain = muc_light_domain(), + Path = path([MUCLightDomain]), RandBits = base16:encode(crypto:strong_rand_bytes(5)), Name = <<"wonderland">>, RoomID = <<"just_some_id_", RandBits/binary>>, @@ -120,20 +122,19 @@ create_identifiable_room(Config) -> {{<<"201">>, _}, RoomJID} = rest_helper:putt(admin, Path, Body), [Item] = get_disco_rooms(Alice), [RoomIDescaped, MUCLightDomain] = binary:split(RoomJID, <<"@">>), - MUCLightDomain = muc_light_domain(), true = is_room_name(Name, Item), true = is_room_domain(MUCLightDomain, Item), true = is_room_id(RoomIDescaped, Item) end). invite_to_room(Config) -> - Name = <<"wonderland">>, - Path = path([domain_helper:domain(), Name, "participants"]), escalus:fresh_story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate) -> + RoomID = atom_to_binary(?FUNCTION_NAME), + Path = path([muc_light_domain(), RoomID, "participants"]), %% XMPP: Alice creates a room. - Stt = stanza_create_room(undefined, - [{<<"roomname">>, Name}], [{Kate, member}]), + Stt = stanza_create_room(RoomID, + [{<<"roomname">>, <<"wonderland">>}], [{Kate, member}]), escalus:send(Alice, Stt), %% XMPP: Alice recieves a affiliation message to herself and %% an IQ result when creating the MUC Light room. @@ -154,15 +155,15 @@ invite_to_room(Config) -> end). send_message_to_room(Config) -> - Name = <<"wonderland">>, - Path = path([domain_helper:domain(), Name, "messages"]), + RoomID = atom_to_binary(?FUNCTION_NAME), + Path = path([muc_light_domain(), RoomID, "messages"]), Text = <<"Hello everyone!">>, escalus:fresh_story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate) -> %% XMPP: Alice creates a room. - escalus:send(Alice, stanza_create_room(undefined, - [{<<"roomname">>, Name}], [{Bob, member}, {Kate, member}])), + escalus:send(Alice, stanza_create_room(RoomID, + [{<<"roomname">>, <<"wonderland">>}], [{Bob, member}, {Kate, member}])), %% XMPP: Alice gets her own affiliation info escalus:wait_for_stanza(Alice), %% XMPP: And Alice gets IQ result @@ -180,50 +181,55 @@ send_message_to_room(Config) -> end). delete_room_by_owner(Config) -> + RoomID = atom_to_binary(?FUNCTION_NAME), RoomName = <<"wonderland">>, escalus:fresh_story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate)-> {{<<"204">>, <<"No Content">>}, <<"">>} = - check_delete_room(Config, RoomName, RoomName, + check_delete_room(Config, RoomName, RoomID, RoomID, Alice, [Bob, Kate], Alice) end). delete_room_by_non_owner(Config) -> + RoomID = atom_to_binary(?FUNCTION_NAME), RoomName = <<"wonderland">>, escalus:fresh_story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate)-> {{<<"403">>, <<"Forbidden">>}, - <<"You cannot delete this room">>} = - check_delete_room(Config, RoomName, RoomName, + <<"Given user cannot delete this room">>} = + check_delete_room(Config, RoomName, RoomID, RoomID, Alice, [Bob, Kate], Bob) end). delete_non_existent_room(Config) -> + RoomID = atom_to_binary(?FUNCTION_NAME), RoomName = <<"wonderland">>, escalus:fresh_story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate)-> {{<<"404">>, _}, <<"Cannot remove not existing room">>} = - check_delete_room(Config, RoomName, <<"some_non_existent_room">>, + check_delete_room(Config, RoomName, RoomID, + <<"some_non_existent_room">>, Alice, [Bob, Kate], Alice) end). delete_room_without_having_a_membership(Config) -> + RoomID = atom_to_binary(?FUNCTION_NAME), RoomName = <<"wonderland">>, escalus:fresh_story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate)-> - {{<<"403">>, _}, <<"Given user does not occupy any room">>} = - check_delete_room(Config, RoomName, RoomName, + {{<<"403">>, _}, <<"Given user does not occupy this room">>} = + check_delete_room(Config, RoomName, RoomID, RoomID, Alice, [Bob], Kate) end). create_non_unique_room(Config) -> escalus:fresh_story(Config, [{alice, 1}], fun(Alice) -> - Path = path([domain()]), + Path = path([muc_light_domain()]), RandBits = base16:encode(crypto:strong_rand_bytes(5)), Name = <<"wonderland">>, RoomID = <<"just_some_id_", RandBits/binary>>, @@ -237,6 +243,17 @@ create_non_unique_room(Config) -> ok end). +create_room_on_non_existing_muc_server(Config) -> + escalus:fresh_story(Config, [{alice, 1}], fun(Alice) -> + Path = path([domain_helper:domain()]), + Name = <<"wonderland">>, + Body = #{ name => Name, + owner => escalus_client:short_jid(Alice), + subject => <<"Lewis Carol">> + }, + {{<<"404">>,<<"Not Found">>}, _} = rest_helper:post(admin, Path, Body) + end). + %%-------------------------------------------------------------------- %% Ancillary (borrowed and adapted from the MUC and MUC Light suites) %%-------------------------------------------------------------------- @@ -276,11 +293,11 @@ member_is_affiliated(Stanza, User) -> Data = exml_query:path(Stanza, [{element, <<"x">>}, {element, <<"user">>}, cdata]), MemberJID == Data. -check_delete_room(_Config, RoomNameToCreate, RoomNameToDelete, RoomOwner, +check_delete_room(_Config, RoomName, RoomIDToCreate, RoomIDToDelete, RoomOwner, RoomMembers, UserToExecuteDelete) -> Members = [{Member, member} || Member <- RoomMembers], - escalus:send(RoomOwner, stanza_create_room(undefined, - [{<<"roomname">>, RoomNameToCreate}], + escalus:send(RoomOwner, stanza_create_room(RoomIDToCreate, + [{<<"roomname">>, RoomName}], Members)), %% XMPP RoomOwner gets affiliation and IQ result Affiliations = [{RoomOwner, owner} | Members], @@ -290,7 +307,7 @@ check_delete_room(_Config, RoomNameToCreate, RoomNameToDelete, RoomOwner, escalus:assert(is_iq_result, CreationResult), muc_light_helper:verify_aff_bcast(Members, Affiliations), ShortJID = escalus_client:short_jid(UserToExecuteDelete), - Path = path([domain_helper:domain(), RoomNameToDelete, ShortJID, "management"]), + Path = path([muc_light_domain(), RoomIDToDelete, ShortJID, "management"]), rest_helper:delete(admin, Path). diff --git a/big_tests/tests/rest_client_SUITE.erl b/big_tests/tests/rest_client_SUITE.erl index c19872c30c..2ca443fab6 100644 --- a/big_tests/tests/rest_client_SUITE.erl +++ b/big_tests/tests/rest_client_SUITE.erl @@ -39,14 +39,14 @@ all() -> {group, security}]. groups() -> - G = [{messages_with_props, [parallel], message_with_props_test_cases()}, + [{messages_with_props, [parallel], message_with_props_test_cases()}, {messages_with_thread, [parallel], message_with_thread_test_cases()}, {messages, [parallel], message_test_cases()}, {muc, [pararell], muc_test_cases()}, {muc_config, [], muc_config_cases()}, {roster, [parallel], roster_test_cases()}, - {security, [], security_test_cases()}], - ct_helper:repeat_all_until_all_ok(G). + {security, [], security_test_cases()}]. + %ct_helper:repeat_all_until_all_ok(G). message_test_cases() -> [msg_is_sent_and_delivered_over_xmpp, diff --git a/doc/rest-api/Administration-backend_swagger.yml b/doc/rest-api/Administration-backend_swagger.yml index 20a523f432..4cddd88294 100644 --- a/doc/rest-api/Administration-backend_swagger.yml +++ b/doc/rest-api/Administration-backend_swagger.yml @@ -391,7 +391,7 @@ paths: responses: 204: description: "The operation was successful." - /muc-lights/{XMPPHost}: + /muc-lights/{XMPPMUCHost}: parameters: - $ref: '#/parameters/hostName' post: @@ -472,7 +472,7 @@ paths: schema: title: roomJID type: string - /muc-lights/{XMPPHost}/{roomName}/participants: + /muc-lights/{XMPPMUCHost}/{roomName}/participants: parameters: - $ref: '#/parameters/MUCServer' - $ref: '#/parameters/roomName' @@ -504,7 +504,7 @@ paths: responses: 204: description: An invite was sent out - /muc-lights/{XMPPHost}/{roomName}/messages: + /muc-lights/{XMPPMUCHost}/{roomName}/messages: parameters: - $ref: '#/parameters/MUCServer' - $ref: '#/parameters/roomName' @@ -536,7 +536,7 @@ paths: responses: 204: description: Message was sent to the MUC Light room - /muc-lights/{XMPPHost}/{roomName}/{user}/management: + /muc-lights/{XMPPMUCHost}/{roomName}/{user}/management: parameters: - $ref: '#/parameters/MUCServer' - $ref: '#/parameters/roomName' From 28f484f6bd009a904981df6f88a31d2d73c1508b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Wojtasik?= Date: Tue, 1 Mar 2022 18:37:12 +0100 Subject: [PATCH 09/10] Apply review --- big_tests/tests/rest_client_SUITE.erl | 13 ++++++------- priv/graphql/schemas/admin/muc_light.gql | 12 ++++++------ src/muc_light/mod_muc_light_api.erl | 22 +++++++++++----------- 3 files changed, 23 insertions(+), 24 deletions(-) diff --git a/big_tests/tests/rest_client_SUITE.erl b/big_tests/tests/rest_client_SUITE.erl index 2ca443fab6..37f4047601 100644 --- a/big_tests/tests/rest_client_SUITE.erl +++ b/big_tests/tests/rest_client_SUITE.erl @@ -40,13 +40,12 @@ all() -> groups() -> [{messages_with_props, [parallel], message_with_props_test_cases()}, - {messages_with_thread, [parallel], message_with_thread_test_cases()}, - {messages, [parallel], message_test_cases()}, - {muc, [pararell], muc_test_cases()}, - {muc_config, [], muc_config_cases()}, - {roster, [parallel], roster_test_cases()}, - {security, [], security_test_cases()}]. - %ct_helper:repeat_all_until_all_ok(G). + {messages_with_thread, [parallel], message_with_thread_test_cases()}, + {messages, [parallel], message_test_cases()}, + {muc, [pararell], muc_test_cases()}, + {muc_config, [], muc_config_cases()}, + {roster, [parallel], roster_test_cases()}, + {security, [], security_test_cases()}]. message_test_cases() -> [msg_is_sent_and_delivered_over_xmpp, diff --git a/priv/graphql/schemas/admin/muc_light.gql b/priv/graphql/schemas/admin/muc_light.gql index 8ac229df1b..dbbd7830df 100644 --- a/priv/graphql/schemas/admin/muc_light.gql +++ b/priv/graphql/schemas/admin/muc_light.gql @@ -31,13 +31,13 @@ type MUCLightAdminQuery @protected{ } type Room{ - jid: JID - name: String - subject: String - participants: [RoomUser!] + jid: JID! + name: String! + subject: String! + participants: [RoomUser!]! } type RoomUser{ - jid: JID - affiliation: Affiliation + jid: JID! + affiliation: Affiliation! } diff --git a/src/muc_light/mod_muc_light_api.erl b/src/muc_light/mod_muc_light_api.erl index 62d639779b..009b5889f3 100644 --- a/src/muc_light/mod_muc_light_api.erl +++ b/src/muc_light/mod_muc_light_api.erl @@ -21,9 +21,9 @@ -include("mongoose_rsm.hrl"). -type room() :: #{jid := jid:jid(), - name := binary(), - subject := binary(), - aff_users := aff_users()}. + name := binary(), + subject := binary(), + aff_users := aff_users()}. -export_type([room/0]). @@ -109,13 +109,13 @@ change_room_config(#jid{luser = RoomID, lserver = MUCServer} = RoomJID, -spec change_affiliation(jid:jid(), jid:jid(), jid:jid(), binary()) -> ok. change_affiliation(RoomJID, SenderJID, RecipientJID, Affiliation) -> - RecipientBare = jid:to_bare(RecipientJID), - S = jid:to_bare(SenderJID), - Changes = query(?NS_MUC_LIGHT_AFFILIATIONS, - [affiliate(jid:to_binary(RecipientBare), Affiliation)]), - ejabberd_router:route(S, RoomJID, iq(jid:to_binary(S), jid:to_binary(RoomJID), - <<"set">>, [Changes])), - ok. + RecipientBare = jid:to_bare(RecipientJID), + S = jid:to_bare(SenderJID), + Changes = query(?NS_MUC_LIGHT_AFFILIATIONS, + [affiliate(jid:to_binary(RecipientBare), Affiliation)]), + ejabberd_router:route(S, RoomJID, iq(jid:to_binary(S), jid:to_binary(RoomJID), + <<"set">>, [Changes])), + ok. -spec remove_user_from_room(jid:jid(), jid:jid(), jid:jid()) -> {ok, iolist()}. @@ -182,7 +182,7 @@ delete_room(RoomJID) -> -spec get_room_messages(jid:jid(), integer() | undefined, mod_mam:unix_timestamp() | undefined) -> - {ok, list()} | {muc_server_not_found | internal, iolist()}. + {ok, [mod_mam:message_row()]} | {muc_server_not_found | internal, iolist()}. get_room_messages(RoomJID, PageSize, Before) -> case mongoose_domain_api:get_subdomain_host_type(RoomJID#jid.lserver) of {ok, HostType} -> From 198cc90cfcc00553104017582babe21a5cced9dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Wojtasik?= Date: Wed, 2 Mar 2022 12:59:30 +0100 Subject: [PATCH 10/10] Fix indentation --- src/muc_light/mod_muc_light_api.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/muc_light/mod_muc_light_api.erl b/src/muc_light/mod_muc_light_api.erl index 009b5889f3..14306cb955 100644 --- a/src/muc_light/mod_muc_light_api.erl +++ b/src/muc_light/mod_muc_light_api.erl @@ -114,7 +114,7 @@ change_affiliation(RoomJID, SenderJID, RecipientJID, Affiliation) -> Changes = query(?NS_MUC_LIGHT_AFFILIATIONS, [affiliate(jid:to_binary(RecipientBare), Affiliation)]), ejabberd_router:route(S, RoomJID, iq(jid:to_binary(S), jid:to_binary(RoomJID), - <<"set">>, [Changes])), + <<"set">>, [Changes])), ok. -spec remove_user_from_room(jid:jid(), jid:jid(), jid:jid()) ->