Skip to content

Commit

Permalink
Merge pull request #3652 from esl/graphql/private
Browse files Browse the repository at this point in the history
Graphql/private
  • Loading branch information
chrzaszcz authored May 25, 2022
2 parents 950e0b3 + 45ac004 commit f403c83
Show file tree
Hide file tree
Showing 18 changed files with 422 additions and 68 deletions.
1 change: 1 addition & 0 deletions big_tests/default.spec
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
{suites, "tests", graphql_domain_SUITE}.
{suites, "tests", graphql_muc_SUITE}.
{suites, "tests", graphql_muc_light_SUITE}.
{suites, "tests", graphql_private_SUITE}.
{suites, "tests", graphql_roster_SUITE}.
{suites, "tests", graphql_session_SUITE}.
{suites, "tests", graphql_stanza_SUITE}.
Expand Down
1 change: 1 addition & 0 deletions big_tests/dynamic_domains.spec
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
{suites, "tests", graphql_domain_SUITE}.
{suites, "tests", graphql_muc_SUITE}.
{suites, "tests", graphql_muc_light_SUITE}.
{suites, "tests", graphql_private_SUITE}.
{suites, "tests", graphql_roster_SUITE}.
{suites, "tests", graphql_session_SUITE}.
{suites, "tests", graphql_stanza_SUITE}.
Expand Down
220 changes: 220 additions & 0 deletions big_tests/tests/graphql_private_SUITE.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
-module(graphql_private_SUITE).

-compile([export_all, nowarn_export_all]).

-import(distributed_helper, [require_rpc_nodes/1]).
-import(graphql_helper, [execute_user/3, execute_auth/2, user_to_bin/1]).

-include_lib("common_test/include/ct.hrl").
-include_lib("eunit/include/eunit.hrl").
-include_lib("exml/include/exml.hrl").
-include_lib("escalus/include/escalus.hrl").
-include("../../include/mod_roster.hrl").

suite() ->
require_rpc_nodes([mim]) ++ escalus:suite().

all() ->
[{group, user_private}, {group, admin_private}].

groups() ->
[{user_private, [], user_private_handler()},
{admin_private, [], admin_private_handler()}].

user_private_handler() ->
[user_set_private,
user_get_private,
parse_xml_error].

admin_private_handler() ->
[admin_set_private,
admin_get_private,
no_user_error_set,
no_user_error_get].

init_per_suite(Config0) ->
HostType = domain_helper:host_type(),
Config1 = dynamic_modules:save_modules(HostType, Config0),
Backend = mongoose_helper:get_backend_mnesia_rdbms_riak(HostType),
ModConfig = create_config(Backend),
dynamic_modules:ensure_modules(HostType, ModConfig),
escalus:init_per_suite([{backend, Backend} | Config1]).

create_config(riak) ->
[{mod_private, #{backend => riak,
iqdisc => one_queue,
riak => #{bucket_type => <<"private">>}}}];
create_config(Backend) ->
[{mod_private, #{backend => Backend, iqdisc => one_queue}}].

end_per_suite(Config) ->
dynamic_modules:restore_modules(Config),
escalus:end_per_suite(Config).

init_per_group(admin_private, Config) ->
graphql_helper:init_admin_handler(Config);
init_per_group(user_private, Config) ->
[{schema_endpoint, user} | Config].

end_per_group(admin_private, _Config) ->
escalus_fresh:clean();
end_per_group(user_private, _Config) ->
escalus_fresh:clean().

init_per_testcase(CaseName, Config) ->
escalus:init_per_testcase(CaseName, Config).

end_per_testcase(CaseName, Config) ->
escalus:end_per_testcase(CaseName, Config).

% User tests

user_set_private(Config) ->
escalus:fresh_story_with_config(Config, [{alice, 1}], fun user_set_private/2).

user_set_private(Config, Alice) ->
QuerySet = user_private_mutation(),
Expected = exml:to_binary(private_input()),
BodySet = #{query => QuerySet, operationName => <<"M1">>,
variables => #{elementString => Expected}},
GraphQlRequestSet = execute_user(BodySet, Alice, Config),
ParsedResultSet = ok_result(<<"private">>, <<"setPrivate">>, GraphQlRequestSet),
?assertEqual(<<"[]">>, ParsedResultSet),
Vars = #{element => <<"my_element">>, nameSpace => <<"alice:private:ns">>},
QueryGet = user_private_query(),
BodyGet = #{query => QueryGet, operationName => <<"Q1">>, variables => Vars},
GraphQlRequestGet = execute_user(BodyGet, Alice, Config),
ParsedResultGet = ok_result(<<"private">>, <<"getPrivate">>, GraphQlRequestGet),
?assertEqual(Expected, ParsedResultGet).

user_get_private(Config) ->
escalus:fresh_story_with_config(Config, [{alice, 1}], fun user_get_private/2).

user_get_private(Config, Alice) ->
Expected = exml:to_binary(private_input()),
IQ = escalus_stanza:to(escalus_stanza:private_set(private_input()),
escalus_users:get_jid(Config, alice)),
escalus_client:send_and_wait(Alice, IQ),
Vars = #{element => <<"my_element">>, nameSpace => <<"alice:private:ns">>},
QueryGet = user_private_query(),
BodyGet = #{query => QueryGet, operationName => <<"Q1">>, variables => Vars},
GraphQlRequestGet = execute_user(BodyGet, Alice, Config),
ParsedResultGet = ok_result(<<"private">>, <<"getPrivate">>, GraphQlRequestGet),
?assertEqual(Expected, ParsedResultGet).

parse_xml_error(Config) ->
escalus:fresh_story_with_config(Config, [{alice, 1}], fun parse_xml_error/2).

parse_xml_error(Config, Alice) ->
QuerySet = user_private_mutation(),
Input = <<"AAAABBBB">>,
BodySet = #{query => QuerySet, operationName => <<"M1">>,
variables => #{elementString => Input}},
GraphQlRequestSet = execute_user(BodySet, Alice, Config),
ParsedResultSet = error_result2(<<"extensions">>, <<"code">>, GraphQlRequestSet),
?assertEqual(<<"parse_error">>, ParsedResultSet).

% Admin tests

admin_set_private(Config) ->
escalus:fresh_story_with_config(Config, [{alice, 1}], fun admin_set_private/2).

admin_set_private(Config, Alice) ->
QuerySet = admin_private_mutation(),
Expected = exml:to_binary(private_input()),
BodySet = #{query => QuerySet, operationName => <<"M1">>,
variables => #{elementString => Expected, user => user_to_bin(Alice)}},
GraphQlRequestSet = execute_auth(BodySet, Config),
ParsedResultSet = ok_result(<<"private">>, <<"setPrivate">>, GraphQlRequestSet),
?assertEqual(<<"[]">>, ParsedResultSet),
Vars = #{element => <<"my_element">>, nameSpace => <<"alice:private:ns">>,
user => user_to_bin(Alice)},
QueryGet = admin_private_query(),
BodyGet = #{query => QueryGet, operationName => <<"Q1">>, variables => Vars},
GraphQlRequestGet = execute_auth(BodyGet, Config),
ParsedResultGet = ok_result(<<"private">>, <<"getPrivate">>, GraphQlRequestGet),
?assertEqual(Expected, ParsedResultGet).

admin_get_private(Config) ->
escalus:fresh_story_with_config(Config, [{alice, 1}], fun admin_get_private/2).

admin_get_private(Config, Alice) ->
Expected = exml:to_binary(private_input()),
IQ = escalus_stanza:to(escalus_stanza:private_set(private_input()),
escalus_users:get_jid(Config, alice)),
escalus_client:send_and_wait(Alice, IQ),
Vars = #{element => <<"my_element">>, nameSpace => <<"alice:private:ns">>,
user => user_to_bin(Alice)},
QueryGet = admin_private_query(),
BodyGet = #{query => QueryGet, operationName => <<"Q1">>, variables => Vars},
GraphQlRequestGet = execute_auth(BodyGet, Config),
ParsedResultGet = ok_result(<<"private">>, <<"getPrivate">>, GraphQlRequestGet),
?assertEqual(Expected, ParsedResultGet).

no_user_error_get(Config) ->
Vars = #{element => <<"my_element">>, nameSpace => <<"alice:private:ns">>,
user => <<"AAAAA">>},
QueryGet = admin_private_query(),
BodyGet = #{query => QueryGet, operationName => <<"Q1">>, variables => Vars},
GraphQlRequestGet = execute_auth(BodyGet, Config),
ParsedResultGet = error_result2(<<"extensions">>, <<"code">>, GraphQlRequestGet),
?assertEqual(<<"not_found">>, ParsedResultGet).

no_user_error_set(Config) ->
QuerySet = admin_private_mutation(),
Expected = exml:to_binary(private_input()),
BodySet = #{query => QuerySet, operationName => <<"M1">>,
variables => #{elementString => Expected, user => <<"AAAAA">>}},
GraphQlRequestSet = execute_auth(BodySet, Config),
ParsedResultSet = error_result2(<<"extensions">>, <<"code">>, GraphQlRequestSet),
?assertEqual(<<"not_found">>, ParsedResultSet).

private_input() ->
#xmlel{name = <<"my_element">>,
attrs = [{<<"xmlns">>, "alice:private:ns"}],
children = [{xmlcdata, <<"DATA">>}]}.

ok_result(What1, What2, {{<<"200">>, <<"OK">>}, #{<<"data">> := Data}}) ->
maps:get(What2, maps:get(What1, Data)).

error_result(What, {{<<"200">>, <<"OK">>}, #{<<"errors">> := [Data]}}) ->
maps:get(What, Data).

error_result2(What1, What2, {{<<"200">>, <<"OK">>}, #{<<"errors">> := [Data]}}) ->
maps:get(What2, maps:get(What1, Data)).

user_private_mutation() ->
<<"mutation M1($elementString: String!)
{
private
{
setPrivate(elementString: $elementString)
}
}">>.

user_private_query() ->
<<"query Q1($element: String! $nameSpace: String!)
{
private
{
getPrivate(element: $element, nameSpace: $nameSpace)
}
}">>.

admin_private_mutation() ->
<<"mutation M1($elementString: String!, $user: JID!)
{
private
{
setPrivate(user: $user, elementString: $elementString)
}
}">>.

admin_private_query() ->
<<"query Q1($user: JID!, $element: String! $nameSpace: String!)
{
private
{
getPrivate(user: $user, element: $element, nameSpace: $nameSpace)
}
}">>.
4 changes: 4 additions & 0 deletions priv/graphql/schemas/admin/admin_schema.gql
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ type AdminQuery{
roster: RosterAdminQuery
"Vcard management"
vcard: VcardAdminQuery
"Private storage management"
private: PrivateAdminQuery
}

"""
Expand All @@ -49,4 +51,6 @@ type AdminMutation @protected{
roster: RosterAdminMutation
"Vcard management"
vcard: VcardAdminMutation
"Private storage management"
private: PrivateAdminMutation
}
15 changes: 15 additions & 0 deletions priv/graphql/schemas/admin/private.gql
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"""
Allow admin to set user's private
"""
type PrivateAdminMutation @protected {
"Set user's private"
setPrivate(user: JID!, elementString: String!): String
}

"""
Allow admin to get user's private
"""
type PrivateAdminQuery @protected {
"Get user's private"
getPrivate(user: JID!, element: String!, nameSpace: String!): String
}
15 changes: 15 additions & 0 deletions priv/graphql/schemas/user/private.gql
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"""
Allow user to set own private
"""
type PrivateUserMutation @protected {
"Set user's own private"
setPrivate(elementString: String!): String
}

"""
Allow user to get own private
"""
type PrivateUserQuery @protected {
"Get user's own private"
getPrivate(element: String!, nameSpace: String!): String
}
4 changes: 4 additions & 0 deletions priv/graphql/schemas/user/user_schema.gql
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ type UserQuery{
roster: RosterUserQuery
"Vcard management"
vcard: VcardUserQuery
"User's private storage management"
private: PrivateUserQuery
}

"""
Expand All @@ -43,4 +45,6 @@ type UserMutation @protected{
roster: RosterUserMutation
"Vcard management"
vcard: VcardUserMutation
"User's private storage management"
private: PrivateUserMutation
}
74 changes: 6 additions & 68 deletions src/admin_extra/service_admin_extra_private.erl
Original file line number Diff line number Diff line change
Expand Up @@ -70,79 +70,17 @@ commands() ->
%% <aa xmlns='bb'>Cluth</aa>

-spec private_get(jid:user(), jid:server(), binary(), binary()) ->
{error, string()} | string().
{not_found, string()} | string().
private_get(Username, Host, Element, Ns) ->
JID = jid:make(Username, Host, <<>>),
case ejabberd_auth:does_user_exist(JID) of
true ->
do_private_get(JID, Element, Ns);
false ->
{error, io_lib:format("User ~s@~s does not exist", [Username, Host])}
case mod_private_api:private_get(JID, Element, Ns) of
{ok, String} -> String;
Error -> Error
end.

do_private_get(JID, Element, Ns) ->
From = To = JID,
{ok, HostType} = mongoose_domain_api:get_domain_host_type(From#jid.lserver),
IQ = {iq, <<"">>, get, ?NS_PRIVATE, <<"">>,
#xmlel{ name = <<"query">>,
attrs = [{<<"xmlns">>, ?NS_PRIVATE}],
children = [#xmlel{ name = Element, attrs = [{<<"xmlns">>, Ns}]}] } },
Acc = mongoose_acc:new(#{ location => ?LOCATION,
from_jid => From,
to_jid => To,
lserver => From#jid.lserver,
host_type => HostType,
element => jlib:iq_to_xml(IQ) }),
{_, ResIq} = mod_private:process_iq(Acc, From, To, IQ, #{}),
[#xmlel{ name = <<"query">>,
attrs = [{<<"xmlns">>, ?NS_PRIVATE}],
children = [SubEl] }] = ResIq#iq.sub_el,
exml:to_binary(SubEl).

-spec private_set(jid:user(), jid:server(),
ElementString :: binary()) -> {Res, string()} when
Res :: ok | user_does_not_exist | user_does_not_exist | not_loaded.
Res :: ok | not_found | not_loaded | parse_error.
private_set(Username, Host, ElementString) ->
case exml:parse(ElementString) of
{error, Error} ->
String = io_lib:format("Error found parsing the element:~n ~p~nError: ~p~n",
[ElementString, Error]),
{parse_error, String};
{ok, Xml} ->
private_set2(Username, Host, Xml)
end.


private_set2(Username, Host, Xml) ->
JID = jid:make(Username, Host, <<>>),
case ejabberd_auth:does_user_exist(JID) of
true ->
do_private_set2(JID, Xml);
false ->
{user_does_not_exist, io_lib:format("User ~s does not exist", [jid:to_binary(JID)])}
end.

do_private_set2(#jid{lserver = Domain} = JID, Xml) ->
{ok, HostType} = mongoose_domain_api:get_domain_host_type(Domain),
case is_private_module_loaded(HostType) of
true ->
From = To = JID,
IQ = {iq, <<"">>, set, ?NS_PRIVATE, <<"">>,
#xmlel{ name = <<"query">>,
attrs = [{<<"xmlns">>, ?NS_PRIVATE}],
children = [Xml]}},
Acc = mongoose_acc:new(#{ location => ?LOCATION,
from_jid => From,
to_jid => To,
lserver => Domain,
host_type => HostType,
element => jlib:iq_to_xml(IQ) }),
mod_private:process_iq(Acc, From, To, IQ, #{}),
{ok, ""};
false ->
{not_loaded, io_lib:format("Module mod_private is not loaded on domain ~s", [Domain])}
end.

-spec is_private_module_loaded(jid:server()) -> true | false.
is_private_module_loaded(Server) ->
lists:member(mod_private, gen_mod:loaded_modules(Server)).
mod_private_api:private_set(JID, ElementString).
2 changes: 2 additions & 0 deletions src/graphql/admin/mongoose_graphql_admin_mutation.erl
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ execute(_Ctx, _Obj, <<"session">>, _Args) ->
{ok, session};
execute(_Ctx, _Obj, <<"stanza">>, _Args) ->
{ok, stanza};
execute(_Ctx, _Obj, <<"private">>, _Args) ->
{ok, private};
execute(_Ctx, _Obj, <<"roster">>, _Args) ->
{ok, roster};
execute(_Ctx, _Obj, <<"vcard">>, _Args) ->
Expand Down
Loading

0 comments on commit f403c83

Please sign in to comment.