Skip to content

Commit

Permalink
Wip6
Browse files Browse the repository at this point in the history
  • Loading branch information
Premwoik committed Apr 26, 2022
1 parent 6127638 commit 661ab1f
Show file tree
Hide file tree
Showing 7 changed files with 114 additions and 25 deletions.
6 changes: 3 additions & 3 deletions rel/fed1.vars-toml.config
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
{http_api_endpoint_port, 5294}.
{http_api_old_endpoint_port, 5293}.
{http_api_client_endpoint_port, 8095}.
{http_qraphql_api_admin_endpoint_port, 5556}.
{http_qraphql_api_domain_admin_endpoint_port, 5546}.
{http_graphql_api_admin_endpoint_port, 5556}.
{http_graphql_api_domain_admin_endpoint_port, 5546}.
{http_graphql_api_user_endpoint_port, 5566}.

%% This node is for s2s testing.
Expand Down Expand Up @@ -53,7 +53,7 @@
{http_graphql_api_admin_endpoint, "ip_address = \"127.0.0.1\"
port = {{http_graphql_api_admin_endpoint_port}}"}.
{http_graphql_api_domain_admin_endpoint, "ip_address = \"0.0.0.0\"
port = {{http_qraphql_api_domain_admin_endpoint_port}}"}.
port = {{http_graphql_api_domain_admin_endpoint_port}}"}.
{http_graphql_api_user_endpoint, "ip_address = \"0.0.0.0\"
port = {{http_graphql_api_user_endpoint_port}}"}.

Expand Down
6 changes: 3 additions & 3 deletions rel/mim2.vars-toml.config
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
{http_api_endpoint_port, 8090}.
{http_api_client_endpoint_port, 8091}.
{service_port, 8899}.
{http_qraphql_api_admin_endpoint_port, 5552}.
{http_qraphql_api_domain_admin_endpoint_port, 5552}.
{http_graphql_api_admin_endpoint_port, 5552}.
{http_graphql_api_domain_admin_endpoint_port, 5542}.
{http_graphql_api_user_endpoint_port, 5562}.

{hosts, "\"localhost\", \"anonymous.localhost\", \"localhost.bis\""}.
Expand All @@ -23,7 +23,7 @@
{highload_vm_args, ""}.

{http_graphql_api_admin_endpoint, "ip_address = \"127.0.0.1\"
port = {{http_qraphql_api_admin_endpoint_port}}"}.
port = {{http_graphql_api_admin_endpoint_port}}"}.
{http_graphql_api_domain_admin_endpoint, "ip_address = \"0.0.0.0\"
port = {{http_graphql_api_domain_admin_endpoint_port}}"}.
{http_graphql_api_user_endpoint, "ip_address = \"0.0.0.0\"
Expand Down
6 changes: 3 additions & 3 deletions rel/mim3.vars-toml.config
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
{http_api_old_endpoint_port, 5292}.
{http_api_endpoint_port, 8092}.
{http_api_client_endpoint_port, 8193}.
{http_qraphql_api_admin_endpoint_port, 5553}.
{http_qraphql_api_admin_endpoint_port, 5543}.
{http_graphql_api_admin_endpoint_port, 5553}.
{http_graphql_api_domain_admin_endpoint_port, 5543}.
{http_graphql_api_user_endpoint_port, 5563}.

{hosts, "\"localhost\", \"anonymous.localhost\", \"localhost.bis\""}.
Expand Down Expand Up @@ -40,7 +40,7 @@
tls.ciphers = \"ECDHE-RSA-AES256-GCM-SHA384\""}.

{http_graphql_api_admin_endpoint, "ip_address = \"127.0.0.1\"
port = {{http_qraphql_api_admin_endpoint_port}}"}.
port = {{http_graphql_api_admin_endpoint_port}}"}.
{http_graphql_api_domain_admin_endpoint, "ip_address = \"0.0.0.0\"
port = {{http_graphql_api_domain_admin_endpoint_port}}"}.
{http_graphql_api_user_endpoint, "ip_address = \"0.0.0.0\"
Expand Down
2 changes: 1 addition & 1 deletion rel/reg1.vars-toml.config
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
{http_api_old_endpoint_port, 5273}.
{http_api_client_endpoint_port, 8075}.
{http_qraphql_api_admin_endpoint_port, 5554}.
{http_qraphql_api_domain_admin_endpoint_port, 5544}.
{http_graphql_api_domain_admin_endpoint_port, 5544}.
{http_graphql_api_user_endpoint_port, 5564}.

%% This node is for global distribution testing.
Expand Down
36 changes: 26 additions & 10 deletions src/graphql/mongoose_graphql_permissions.erl
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,24 @@
type := atom(),
invalid := [binary()]}.
-type document() :: #document{}.
-type definitions() :: [any()].

%% @doc Checks if query can be executed by unauthorized request. If not, throws
%% an error. When request is authorized, just skip.
%% @end
-spec check_permissions(map(), document()) -> ok.
-spec check_permissions(auth_ctx(), document()) -> ok.
check_permissions(#{operation_name := OpName, authorized := false},
#document{definitions = Definitions}) ->
check_unauthorized_request_permissions(OpName, Definitions);
check_permissions(#{operation_name := OpName, authorized_as := domain_admin,
admin := #jid{lserver = Domain}, params := Params},
#document{definitions = Definitions}) ->
check_domain_authorized_request_permissions(OpName, Domain, Params, Definitions);
check_permissions(#{authorized := true}, _) ->
ok.

-spec check_unauthorized_request_permissions(binary(), definitions()) -> ok.
check_unauthorized_request_permissions(OpName, Definitions) ->
Op = lists:filter(fun(D) -> is_req_operation(D, OpName) end, Definitions),
case Op of
[#op{schema = Schema, selection_set = Set} = Op1] ->
Expand All @@ -70,10 +81,11 @@ check_permissions(#{operation_name := OpName, authorized := false},
end;
_ ->
ok
end;
check_permissions(#{operation_name := OpName, authorized_as := domain_admin,
admin := #jid{lserver = Domain}, params := Params},
#document{definitions = Definitions}) ->
end.

-spec check_domain_authorized_request_permissions(binary(), binary(),
params(), definitions()) -> ok.
check_domain_authorized_request_permissions(OpName, Domain, Params, Definitions) ->
Op = lists:filter(fun(D) -> is_req_operation(D, OpName) end, Definitions),
case Op of
[#op{selection_set = Set}] ->
Expand All @@ -88,9 +100,7 @@ check_permissions(#{operation_name := OpName, authorized_as := domain_admin,
end;
_ ->
ok
end;
check_permissions(#{authorized := true}, _) ->
ok.
end.

% Internal

Expand All @@ -100,13 +110,14 @@ check_fields(AuthCtx, Params, Fields) ->
lists:flatten(lists:filtermap(fun(F) -> check_field(F, AuthCtx, Params) end, Fields)).

-spec check_field(field(), auth_ctx(), params()) -> false | {true, fail_acc() | [fail_acc()]}.
check_field(#field{id = Name, selection_set = Set,
check_field(#field{id = Name, selection_set = Set, args = Args,
schema = #schema_field{directives = Directives}}, AuthCtx, Params) ->
Args2 = maps:from_list([prepare_arg(ArgName, Type, Params) || {ArgName, Type} <- Args]),
Res =
case lists:filter(fun is_protected_directive/1, Directives) of
[#directive{} = Dir] ->
#{type := Type, args := PArgs} = protected_dir_args_to_map(Dir),
Acc = check_field_args(Type, AuthCtx, PArgs, Params),
Acc = check_field_args(Type, AuthCtx, PArgs, Args2),
acc_path(name(Name), Acc);
_ ->
false
Expand All @@ -121,6 +132,11 @@ check_field_type(false, AuthCtx, Params, Set, Name) ->
check_field_type(Acc, _, _, _, _) ->
Acc.

prepare_arg(ArgName, #{value := #var{id = Name}}, Vars) ->
{ArgName, maps:get(name(Name), Vars)};
prepare_arg(ArgName, #{value := Val}, _) ->
{ArgName, Val}.

check_field_args({enum, <<"DOMAIN">>}, #{domain := Domain}, ProtectedArgs, Params) ->
Res = lists:filter(fun(Arg) -> not arg_eq(maps:get(Arg, Params), Domain) end, ProtectedArgs),
acc(Res, domain);
Expand Down
70 changes: 66 additions & 4 deletions test/mongoose_graphql_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@
-define(assertPermissionsSuccess(Config, Doc),
?assertMatch(ok, check_permissions(Config, Doc))).

-define(assertDomainPermissionsFailed(Config, Domain, Args, Doc),
?assertThrow({error, #{error_term := {no_permissions, _, domain, Args}}},
check_domain_permissions(Config, Domain, Doc))).
-define(assertPermissionsSuccess(Config, Domain, Doc),
?assertMatch(ok, check_domain_permissions(Config, Domain, Doc))).

-define(assertErrMsg(Code, MsgContains, ErrorMsg),
assert_err_msg(Code, MsgContains, ErrorMsg)).

Expand All @@ -26,6 +32,7 @@ all() ->
{group, error_handling},
{group, error_formatting},
{group, permissions},
{group, domain_permissions},
{group, user_listener},
{group, admin_listener}].

Expand All @@ -35,6 +42,7 @@ groups() ->
{error_handling, [parallel], error_handling()},
{error_formatting, [parallel], error_formatting()},
{permissions, [parallel], permissions()},
{domain_permissions, [parallel], domain_permissions()},
{admin_listener, [parallel], admin_listener()},
{user_listener, [parallel], user_listener()}].

Expand Down Expand Up @@ -81,6 +89,12 @@ permissions() ->
check_union_permissions
].

domain_permissions() ->
[check_field_domain_permissions,
check_child_object_field_domain_permissions,
check_interface_field_domain_permissions
].

user_listener() ->
[auth_user_can_access_protected_types | common_tests()].
admin_listener() ->
Expand Down Expand Up @@ -168,7 +182,10 @@ init_per_testcase(C, Config) when C =:= check_object_permissions;
C =:= check_interface_permissions;
C =:= check_interface_field_permissions;
C =:= check_inline_fragment_permissions;
C =:= check_union_permissions ->
C =:= check_union_permissions;
C =:= check_field_domain_permissions;
C =:= check_child_object_field_domain_permissions;
C =:= check_interface_field_domain_permissions ->
{Mapping, Pattern} = example_permissions_schema_data(Config),
{ok, _} = mongoose_graphql:create_endpoint(C, Mapping, [Pattern]),
Ep = mongoose_graphql:get_endpoint(C),
Expand Down Expand Up @@ -396,6 +413,36 @@ check_union_permissions(Config) ->
?assertPermissionsFailed(Config, FDoc),
?assertPermissionsFailed(Config, FDoc2).

%% Domain permissions

check_field_domain_permissions(Config) ->
Domain = <<"my-domain.com">>,
Config2 = [{op, <<"Q1">>}, {args, #{<<"domain">> => Domain}} | Config],
Doc = <<"{ field protectedField }">>,
Doc2 = <<"query Q1($domain: String) { protectedField domainProtectedField(argA: $domain"
", argB: \"domain\") }">>,
FDoc = <<"{protectedField domainProtectedField(argA: \"domain.com\","
" argB: \"domain.com\") }">>,
?assertPermissionsSuccess(Config, Domain, Doc),
?assertPermissionsSuccess(Config2, Domain, Doc2),
?assertDomainPermissionsFailed(Config, Domain, [<<"argA">>], FDoc).

check_child_object_field_domain_permissions(Config) ->
Domain = <<"my-domain.com">>,
Config2 = [{op, <<"Q1">>}, {args, #{<<"domain">> => Domain}} | Config],
Doc = <<"{ obj { field protectedField } }">>,
Doc2 = <<"query Q1($domain: String) { obj { protectedField domainProtectedField(argA: $domain"
", argB: \"domain\") } }">>,
FDoc = <<"{ obj {protectedField domainProtectedField(argA: \"domain.com\","
" argB: \"domain.com\") } }">>,
?assertPermissionsSuccess(Config, Domain, Doc),
?assertPermissionsSuccess(Config2, Domain, Doc2),
?assertDomainPermissionsFailed(Config, Domain, [<<"argA">>], FDoc).

check_interface_field_domain_permissions(_Config) ->
%% FIXME provide implementation
ok.

%% Error formatting

format_internal_crash(_Config) ->
Expand Down Expand Up @@ -609,7 +656,21 @@ check_permissions(Config, Doc) ->
{ok, Ast} = graphql:parse(Doc),
{ok, #{ast := Ast2}} = graphql:type_check(Ep, Ast),
ok = graphql:validate(Ast2),
ok = mongoose_graphql_permissions:check_permissions(Op, false, Ast2).
Ctx = #{operation_name => Op, authorized => false, params => #{}},
ok = mongoose_graphql_permissions:check_permissions(Ctx, Ast2).

check_domain_permissions(Config, Domain, Doc) ->
Ep = ?config(endpoint, Config),
Args = proplists:get_value(args, Config, #{}),
Op = proplists:get_value(op, Config, undefined),
{ok, Ast} = graphql:parse(Doc),
{ok, #{ast := Ast2, fun_env := FunEnv}} = graphql:type_check(Ep, Ast),
ok = graphql:validate(Ast2),
Coerced = graphql:type_check_params(Ep, FunEnv, Op, Args),
Admin = jid:make_bare(<<"admin">>, Domain),
Ctx = #{operation_name => Op, authorized => true, authorized_as => domain_admin,
admin => Admin, params => Coerced},
ok = mongoose_graphql_permissions:check_permissions(Ctx, Ast2).

request(Doc, Authorized) ->
request(undefined, Doc, Authorized).
Expand Down Expand Up @@ -656,8 +717,9 @@ example_permissions_schema_data(Config) ->
#{'UserQuery' => mongoose_graphql_default_resolver,
'UserMutation' => mongoose_graphql_default_resolver,
default => mongoose_graphql_default_resolver},
interfaces => #{default => mongoose_graphql_default_resolver},
unions => #{default => mongoose_graphql_default_resolver}},
enums => #{default => mongoose_graphql_enum},
interfaces => #{default => mongoose_graphql_default_resolver},
unions => #{default => mongoose_graphql_default_resolver}},
{Mapping, Pattern}.

example_listener_schema_data(Config) ->
Expand Down
13 changes: 12 additions & 1 deletion test/mongoose_graphql_SUITE_data/permissions_schema.gql
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,20 @@ schema{
mutation: UserMutation
}

directive @protected on FIELD_DEFINITION | OBJECT | INTERFACE
#directive @protected on FIELD_DEFINITION | OBJECT | INTERFACE
directive @protected (type: ProtectionType = DEFAULT, args: [String!] = [])
on FIELD_DEFINITION | OBJECT | INTERFACE

enum ProtectionType{
DOMAIN
DEFAULT
}

type UserQuery{
field: String
protectedField: String @protected
domainProtectedField(argA: String, argB: String):
String @protected(type: DOMAIN, args: ["argA"])
interface: Interface
union: UnionT
obj: Object
Expand Down Expand Up @@ -35,6 +44,8 @@ type Object implements Interface{
otherName: String @protected
protectedName: String
protectedField: String @protected
domainProtectedField(argA: String, argB: String):
String @protected(type: DOMAIN, args: ["argA"])
field: String
}

Expand Down

0 comments on commit 661ab1f

Please sign in to comment.