Skip to content

Commit

Permalink
Adding graphql resolvers and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Janusz Jakubiec authored and Janusz Jakubiec committed Jul 20, 2022
1 parent 21716dd commit f48bb16
Show file tree
Hide file tree
Showing 14 changed files with 309 additions and 15 deletions.
1 change: 1 addition & 0 deletions big_tests/default.spec
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
{suites, "tests", graphql_session_SUITE}.
{suites, "tests", graphql_stanza_SUITE}.
{suites, "tests", graphql_stats_SUITE}.
{suites, "tests", graphql_token_SUITE}.
{suites, "tests", graphql_vcard_SUITE}.
{suites, "tests", graphql_http_upload_SUITE}.
{suites, "tests", graphql_metric_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 @@ -55,6 +55,7 @@
{suites, "tests", graphql_vcard_SUITE}.
{suites, "tests", graphql_offline_SUITE}.
{suites, "tests", graphql_stats_SUITE}.
{suites, "tests", graphql_token_SUITE}.
{suites, "tests", graphql_http_upload_SUITE}.
{suites, "tests", graphql_metric_SUITE}.

Expand Down
193 changes: 193 additions & 0 deletions big_tests/tests/graphql_token_SUITE.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
-module(graphql_token_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").

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

all() ->
[{group, user},
{group, admin}].

groups() ->
[{user, [], user_cases()},
{admin, [], admin_cases()}].

user_cases() ->
[user_request_token,
user_revoke_token_no_token_before,
user_revoke_token].

admin_cases() ->
[admin_request_token,
admin_request_token_no_user,
admin_revoke_token_no_user,
admin_revoke_token_no_token,
admin_revoke_token].

init_per_suite(Config0) ->
case mongoose_helper:is_rdbms_enabled(domain_helper:host_type()) of
true ->
HostType = domain_helper:host_type(),
Config = dynamic_modules:save_modules(HostType, Config0),
dynamic_modules:ensure_modules(HostType, required_modules()),
escalus:init_per_suite(Config);
false ->
{skip, "RDBMS not available"}
end.

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

required_modules() ->
KeyOpts = #{keys => #{token_secret => ram,
provision_pre_shared => ram}},
KeyStoreOpts = config_parser_helper:mod_config(mod_keystore, KeyOpts),
[{mod_keystore, KeyStoreOpts},
{mod_auth_token, auth_token_opts()}].

auth_token_opts() ->
Defaults = config_parser_helper:default_mod_config(mod_auth_token),
Defaults#{validity_period => #{access => #{value => 60, unit => minutes},
refresh => #{value => 1, unit => days}}}.

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

end_per_group(_, _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_request_token(Config) ->
escalus:fresh_story_with_config(Config, [{alice, 1}], fun user_request_token/2).

user_request_token(Config, Alice) ->
Req = #{query => user_request_token_mutation(), operationName => <<"M1">>,
variables => #{}},
Res = execute_user(Req, Alice, Config),
#{<<"refresh">> := Refresh, <<"access">> := Access} = ok_result(<<"token">>,
<<"requestToken">>, Res),
?assert(is_binary(Refresh)),
?assert(is_binary(Access)).

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

user_revoke_token_no_token_before(Config, Alice) ->
Req = #{query => user_revoke_token_mutation(), operationName => <<"M1">>,
variables => #{}},
Res = execute_user(Req, Alice, Config),
ParsedRes = error_result(<<"extensions">>, <<"code">>, Res),
?assertEqual(<<"not_found">>, ParsedRes).

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

user_revoke_token(Config, Alice) ->
Req = #{query => user_request_token_mutation(), operationName => <<"M1">>,
variables => #{}},
execute_user(Req, Alice, Config),
Req2 = #{query => user_revoke_token_mutation(), operationName => <<"M1">>,
variables => #{}},
Res2 = execute_user(Req2, Alice, Config),
ParsedRes = ok_result(<<"token">>, <<"revokeToken">>, Res2),
?assertEqual(<<"Revoked">>, ParsedRes).

% Admin tests

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

admin_request_token(Config, Alice) ->
Req = #{query => admin_request_token_mutation(), operationName => <<"M1">>,
variables => #{<<"user">> => user_to_bin(Alice)}},
Res = execute_auth(Req, Config),
#{<<"refresh">> := Refresh, <<"access">> := Access} = ok_result(<<"token">>,
<<"requestToken">>, Res),
?assert(is_binary(Refresh)),
?assert(is_binary(Access)).

admin_request_token_no_user(Config) ->
Req = #{query => admin_request_token_mutation(), operationName => <<"M1">>,
variables => #{<<"user">> => <<"AAAA">>}},
Res = execute_auth(Req, Config),
ParsedRes = error_result(<<"extensions">>, <<"code">>, Res),
?assertEqual(<<"not_found">>, ParsedRes).

admin_revoke_token_no_user(Config) ->
Req = #{query => admin_revoke_token_mutation(), operationName => <<"M1">>,
variables => #{<<"user">> => <<"AAAA">>}},
Res = execute_auth(Req, Config),
ParsedRes = error_result(<<"extensions">>, <<"code">>, Res),
?assertEqual(<<"not_found">>, ParsedRes).

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

admin_revoke_token_no_token(Config, Alice) ->
Req = #{query => admin_revoke_token_mutation(), operationName => <<"M1">>,
variables => #{<<"user">> => user_to_bin(Alice)}},
Res = execute_auth(Req, Config),
ParsedRes = error_result(<<"extensions">>, <<"code">>, Res),
?assertEqual(<<"not_found">>, ParsedRes).

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

admin_revoke_token(Config, Alice) ->
Req = #{query => admin_request_token_mutation(), operationName => <<"M1">>,
variables => #{<<"user">> => user_to_bin(Alice)}},
execute_auth(Req, Config),
Req2 = #{query => admin_revoke_token_mutation(), operationName => <<"M1">>,
variables => #{<<"user">> => user_to_bin(Alice)}},
Res2 = execute_auth(Req2, Config),
ParsedRes = ok_result(<<"token">>, <<"revokeToken">>, Res2),
?assertEqual(<<"Revoked">>, ParsedRes).

user_request_token_mutation() ->
<<"mutation M1
{ token {
requestToken {access refresh}
} }">>.

user_revoke_token_mutation() ->
<<"mutation M1
{ token {
revokeToken
} }">>.

admin_request_token_mutation() ->
<<"mutation M1($user: JID!)
{ token {
requestToken(user: $user) {access refresh}
} }">>.

admin_revoke_token_mutation() ->
<<"mutation M1($user: JID!)
{ token {
revokeToken(user: $user)
} }">>.

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

error_result(What1, What2, {{<<"200">>, <<"OK">>}, #{<<"errors">> := [Data]}}) ->
maps:get(What2, maps:get(What1, Data)).
2 changes: 2 additions & 0 deletions priv/graphql/schemas/admin/admin_schema.gql
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,6 @@ type AdminMutation @protected{
httpUpload: HttpUploadAdminMutation
"Offline deleting old messages"
offline: OfflineAdminMutation
"OAUTH token management"
token: TokenAdminMutation
}
9 changes: 9 additions & 0 deletions priv/graphql/schemas/admin/token.gql
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
"""
Allow admin to get and revoke user's auth tokens
"""
type TokenAdminMutation @protected {
"Request auth token for an user"
requestToken(user: JID!): Token
"Revoke any tokens for an user"
revokeToken(user: JID!): String
}
4 changes: 4 additions & 0 deletions priv/graphql/schemas/global/token.gql
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
type Token {
access: String
refresh: String
}
9 changes: 9 additions & 0 deletions priv/graphql/schemas/user/token.gql
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
"""
Allow user to get and revoke tokens.
"""
type TokenUserMutation @protected{
"Get a new token"
requestToken: Token
"Revoke any tokens"
revokeToken: String
}
2 changes: 2 additions & 0 deletions priv/graphql/schemas/user/user_schema.gql
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,6 @@ type UserMutation @protected{
private: PrivateUserMutation
"Http upload"
httpUpload: HttpUploadUserMutation
"OAUTH token management"
token: TokenUserMutation
}
4 changes: 3 additions & 1 deletion src/graphql/admin/mongoose_graphql_admin_mutation.erl
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,6 @@ execute(_Ctx, _Obj, <<"session">>, _Args) ->
execute(_Ctx, _Obj, <<"stanza">>, _Args) ->
{ok, stanza};
execute(_Ctx, _Obj, <<"vcard">>, _Args) ->
{ok, vcard}.
{ok, vcard};
execute(_Ctx, _Obj, <<"token">>, _Args) ->
{ok, token}.
31 changes: 31 additions & 0 deletions src/graphql/admin/mongoose_graphql_token_admin_mutation.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
-module(mongoose_graphql_token_admin_mutation).
-behaviour(mongoose_graphql).

-export([execute/4]).

-ignore_xref([execute/4]).

-include("../mongoose_graphql_types.hrl").

-import(mongoose_graphql_helper, [make_error/2, format_result/2]).

-type token_info() :: map().

execute(_Ctx, token, <<"requestToken">>, #{<<"user">> := JID}) ->
request_token(JID);
execute(_Ctx, token, <<"revokeToken">>, #{<<"user">> := JID}) ->
revoke_token(JID).

-spec request_token(jid:jid()) -> {ok, token_info()} | {error, resolver_error()}.
request_token(JID) ->
case mod_auth_token_api:create_token(JID) of
{ok, _} = Result -> Result;
Error -> make_error(Error, #{user => JID})
end.

-spec revoke_token(jid:jid()) -> {ok, binary()} | {error, resolver_error()}.
revoke_token(JID) ->
case mod_auth_token_api:revoke_token_command(JID) of
{ok, _} = Result -> Result;
Error -> make_error(Error, #{user => JID})
end.
29 changes: 16 additions & 13 deletions src/graphql/mod_auth_token_api.erl
Original file line number Diff line number Diff line change
Expand Up @@ -3,45 +3,48 @@
-include("jlib.hrl").
-include("mod_auth_token.hrl").

-export([revoke_token_command/1, create_token/2]).
-export([revoke_token_command/1, create_token/1]).

-import(mod_auth_token, [token/3, serialize/1]).

-spec revoke_token_command(User) -> ResTuple when
User :: binary(),
ResCode :: ok | not_found | error,
ResCode :: ok | not_found | internal_server_error,
ResTuple :: {ResCode, string()}.
revoke_token_command(User) ->
#jid{lserver = LServer} = Jid = convert_user(User),
case mongoose_domain_api:get_domain_host_type(LServer) of
{ok, HostType} ->
try mod_auth_token:revoke(HostType, Jid) of
not_found ->
{not_found, "User or token not found."};
{not_found, "User or token not found"};
ok ->
{ok, "Revoked."};
{ok, "Revoked"};
error ->
{error, "Internal server error"}
{internal_server_error, "Internal server error"}
catch _:_ ->
{error, "Internal server error"}
{internal_server_error, "Internal server error"}
end;
_ ->
{not_found, "User or token not found."}
{not_found, "User or token not found"}
end.

-spec create_token(User, Type) -> ResTuple when
-spec create_token(User) -> ResTuple when
User :: jid:jid(),
Type :: access | refresh,
ResCode :: ok | internal_server_error | not_found,
ResTuple :: {ResCode, string()}.
create_token(User, Type) ->
create_token(User) ->
#jid{lserver = LServer} = Jid = convert_user(User),
case mongoose_domain_api:get_domain_host_type(LServer) of
{ok, HostType} ->
case mod_auth_token:token(HostType, Jid, Type) of
#token{} = Token -> {ok, mod_auth_token:serialize(Token)};
case {token(HostType, Jid, access), token(HostType, Jid, refresh)} of
{#token{} = AccessToken, #token{} = RefreshToken} ->
{ok, #{<<"access">> => serialize(AccessToken),
<<"refresh">> => serialize(RefreshToken)}};
_ -> {internal_server_error, "Internal server errror"}
end;
_ ->
{not_found, "User or token not found."}
{not_found, "User or token not found"}
end.

convert_user(User) when is_binary(User) ->
Expand Down
2 changes: 2 additions & 0 deletions src/graphql/mongoose_graphql.erl
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ admin_mapping_rules() ->
'SessionAdminQuery' => mongoose_graphql_session_admin_query,
'StanzaAdminMutation' => mongoose_graphql_stanza_admin_mutation,
'StatsAdminQuery' => mongoose_graphql_stats_admin_query,
'TokenAdminMutation' => mongoose_graphql_token_admin_mutation,
'GlobalStats' => mongoose_graphql_stats_global,
'DomainStats' => mongoose_graphql_stats_domain,
'StanzaAdminQuery' => mongoose_graphql_stanza_admin_query,
Expand Down Expand Up @@ -185,6 +186,7 @@ user_mapping_rules() ->
'LastUserQuery' => mongoose_graphql_last_user_query,
'SessionUserQuery' => mongoose_graphql_session_user_query,
'StanzaUserMutation' => mongoose_graphql_stanza_user_mutation,
'TokenUserMutation' => mongoose_graphql_token_user_mutation,
'StanzaUserQuery' => mongoose_graphql_stanza_user_query,
'HttpUploadUserMutation' => mongoose_graphql_http_upload_user_mutation,
'UserAuthInfo' => mongoose_graphql_user_auth_info,
Expand Down
Loading

0 comments on commit f48bb16

Please sign in to comment.