1: -module(mongoose_graphql_SUITE). 2: 3: -compile([export_all, nowarn_export_all]). 4: 5: -include_lib("eunit/include/eunit.hrl"). 6: -include_lib("common_test/include/ct.hrl"). 7: -include_lib("graphql/src/graphql_schema.hrl"). 8: -include_lib("jid/include/jid.hrl"). 9: 10: -type listener_opts() :: #{endpoint_schema := binary(), 11: atom() => any()}. 12: 13: -define(assertPermissionsFailed(Config, Doc), 14: ?assertThrow({error, #{error_term := {no_permissions, _}}}, 15: check_permissions(Config, false, Doc))). 16: -define(assertPermissionsSuccess(Config, Doc), 17: ?assertMatch(#document{}, check_permissions(Config, false, Doc))). 18: 19: -define(assertDomainPermissionsFailed(Config, Domain, Args, Doc), 20: ?assertThrow({error, #{error_term := {no_permissions, _, #{type := domain, 21: invalid_args := Args}}}}, 22: check_domain_permissions(Config, Domain, Doc))). 23: -define(assertPermissionsSuccess(Config, Domain, Doc), 24: ?assertMatch(#document{}, check_domain_permissions(Config, Domain, Doc))). 25: 26: -define(assertErrMsg(Code, MsgContains, ErrorMsg), 27: assert_err_msg(Code, MsgContains, ErrorMsg)). 28: 29: all() -> 30: [can_create_endpoint, 31: can_load_split_schema, 32: unexpected_internal_error, 33: admin_and_user_load_global_types, 34: {group, unprotected_graphql}, 35: {group, protected_graphql}, 36: {group, error_handling}, 37: {group, error_formatting}, 38: {group, permissions}, 39: {group, domain_permissions}, 40: {group, use_directive}, 41: {group, user_listener}, 42: {group, admin_listener}, 43: {group, domain_admin_listener}]. 44: 45: groups() -> 46: [{protected_graphql, [parallel], protected_graphql()}, 47: {unprotected_graphql, [parallel], unprotected_graphql()}, 48: {error_handling, [parallel], error_handling()}, 49: {error_formatting, [parallel], error_formatting()}, 50: {permissions, [parallel], permissions()}, 51: {domain_permissions, [parallel], domain_permissions()}, 52: {use_directive, [parallel], use_directive()}, 53: {admin_listener, [parallel], admin_listener()}, 54: {domain_admin_listener, [parallel], domain_admin_listener()}, 55: {user_listener, [parallel], user_listener()}]. 56: 57: protected_graphql() -> 58: [auth_can_execute_protected_query, 59: auth_can_execute_protected_mutation, 60: unauth_cannot_execute_protected_query, 61: unauth_cannot_execute_protected_mutation]. 62: 63: unprotected_graphql() -> 64: [can_execute_query_with_vars, 65: auth_can_execute_query, 66: auth_can_execute_mutation, 67: unauth_can_execute_query, 68: unauth_can_execute_mutation]. 69: 70: error_handling() -> 71: [should_catch_parsing_error, 72: should_catch_type_check_params_error, 73: should_catch_type_check_error, 74: should_catch_validation_error]. 75: 76: error_formatting() -> 77: [format_internal_crash, 78: format_parse_errors, 79: format_decode_errors, 80: format_authorize_error, 81: format_validate_error, 82: format_type_check_error, 83: format_execute_error, 84: format_uncategorized_error, 85: format_any_error]. 86: 87: permissions() -> 88: [check_object_permissions, 89: check_field_permissions, 90: check_child_object_permissions, 91: check_child_object_field_permissions, 92: check_fragment_permissions, 93: check_interface_permissions, 94: check_interface_field_permissions, 95: check_inline_fragment_permissions, 96: check_union_permissions 97: ]. 98: 99: domain_permissions() -> 100: [check_field_domain_permissions, 101: check_field_input_arg_domain_permissions, 102: check_field_list_arg_domain_permissions, 103: check_field_null_arg_domain_permissions, 104: check_field_jid_arg_domain_permissions, 105: check_child_object_field_domain_permissions, 106: check_field_subdomain_permissions, 107: check_field_global_permissions, 108: check_interface_field_domain_permissions 109: ]. 110: 111: use_directive() -> 112: [use_dir_module_not_loaded, 113: use_dir_all_modules_loaded, 114: use_dir_all_modules_and_services_loaded, 115: use_dir_module_and_service_not_loaded, 116: use_dir_object_module_and_service_not_loaded, 117: use_dir_object_all_modules_and_services_loaded, 118: use_dir_auth_admin_all_modules_and_services_loaded, 119: use_dir_auth_user_all_modules_and_services_loaded, 120: use_dir_auth_admin_module_and_service_not_loaded, 121: use_dir_auth_user_module_and_service_not_loaded 122: ]. 123: 124: user_listener() -> 125: [auth_user_can_access_protected_types, 126: use_directive_can_use_auth_user_domain | common_tests()]. 127: 128: admin_listener() -> 129: [no_creds_defined_admin_can_access_protected, 130: auth_admin_can_access_protected_types | common_tests()]. 131: 132: domain_admin_listener() -> 133: [auth_domain_admin_can_access_protected_types, 134: auth_domain_admin_wrong_password_error, 135: auth_domain_admin_nonexistent_domain_error, 136: auth_domain_admin_cannot_access_other_domain, 137: auth_domain_admin_cannot_access_global, 138: auth_domain_admin_can_access_owned_domain, 139: use_directive_can_use_auth_domain_admin_domain 140: | common_tests()]. 141: 142: common_tests() -> 143: [malformed_auth_header_error, 144: auth_wrong_creds_error, 145: invalid_json_body_error, 146: no_query_supplied_error, 147: variables_invalid_json_error, 148: listener_reply_with_parsing_error, 149: listener_reply_with_type_check_error, 150: listener_reply_with_validation_error, 151: listener_unauth_cannot_access_protected_types, 152: listener_unauth_can_access_unprotected_types, 153: listener_can_execute_query_with_variables]. 154: 155: init_per_suite(Config) -> 156: %% Register atoms for `binary_to_existing_atom` 157: [mod_x, mod_z, service_x, service_d], 158: application:ensure_all_started(cowboy), 159: application:ensure_all_started(jid), 160: Config. 161: 162: end_per_suite(_Config) -> 163: ok. 164: 165: init_per_group(user_listener, Config) -> 166: Config1 = meck_module_and_service_checking(Config), 167: Config2 = meck_domain_api(Config1), 168: meck:new(mongoose_api_common, [no_link]), 169: meck:expect(mongoose_api_common, check_password, 170: fun 171: (#jid{luser = <<"alice">>}, <<"makota">>) -> {true, {}}; 172: (_, _) -> false 173: end), 174: ListenerOpts = #{schema_endpoint => user}, 175: init_ep_listener(5557, user_schema_ep, ListenerOpts, Config2); 176: init_per_group(admin_listener, Config) -> 177: ListenerOpts = #{username => <<"admin">>, 178: password => <<"secret">>, 179: schema_endpoint => admin}, 180: init_ep_listener(5558, admin_schema_ep, ListenerOpts, Config); 181: init_per_group(domain_admin_listener, Config) -> 182: Config1 = meck_module_and_service_checking(Config), 183: Config2 = meck_domain_api(Config1), 184: meck:expect(mongoose_domain_api, check_domain_password, 185: fun 186: (<<"localhost">>, <<"makota">>) -> ok; 187: (<<"localhost">>, _) -> {error, wrong_password}; 188: (_, _) -> {error, not_found} 189: end), 190: ListenerOpts = #{schema_endpoint => domain_admin}, 191: init_ep_listener(5560, domain_admin_schema_ep, ListenerOpts, Config2); 192: init_per_group(domain_permissions, Config) -> 193: meck:new(mongoose_domain_api, [no_link]), 194: meck:expect(mongoose_domain_api, get_subdomain_info, 195: fun 196: (<<"subdomain.test-domain.com">>) -> 197: {ok, #{parent_domain => <<"test-domain.com">>}}; 198: (<<"subdomain.test-domain2.com">>) -> 199: {ok, #{parent_domain => <<"test-domain2.com">>}}; 200: (_) -> 201: {error, not_found} 202: end), 203: Domains = [{<<"subdomain.test-domain.com">>, <<"test-domain.com">>}, 204: {<<"subdomain.test-domain2.com">>, <<"test-domain2.com">>}], 205: [{domains, Domains} | Config]; 206: init_per_group(use_directive, Config) -> 207: Config1 = meck_domain_api(Config), 208: meck_module_and_service_checking(Config1); 209: init_per_group(_G, Config) -> 210: Config. 211: 212: end_per_group(user_listener, Config) -> 213: unmeck_module_and_service_checking(Config), 214: unmeck_domain_api(Config), 215: ?config(test_process, Config) ! stop, 216: Config; 217: end_per_group(admin_listener, Config) -> 218: ?config(test_process, Config) ! stop, 219: Config; 220: end_per_group(domain_admin_listener, Config) -> 221: unmeck_module_and_service_checking(Config), 222: unmeck_domain_api(Config), 223: ?config(test_process, Config) ! stop, 224: Config; 225: end_per_group(domain_permissions, _Config) -> 226: meck:unload(mongoose_domain_api); 227: end_per_group(use_directive, Config) -> 228: unmeck_domain_api(Config), 229: unmeck_module_and_service_checking(Config); 230: end_per_group(_, Config) -> 231: Config. 232: 233: init_per_testcase(C, Config) when C =:= auth_can_execute_protected_query; 234: C =:= auth_can_execute_protected_mutation; 235: C =:= unauth_cannot_execute_protected_query; 236: C =:= unauth_cannot_execute_protected_mutation -> 237: {Mapping, Pattern} = example_schema_protected_data(Config), 238: {ok, _} = mongoose_graphql:create_endpoint(C, Mapping, [Pattern]), 239: Ep = mongoose_graphql:get_endpoint(C), 240: [{endpoint, Ep} | Config]; 241: init_per_testcase(C, Config) when C =:= can_execute_query_with_vars; 242: C =:= auth_can_execute_query; 243: C =:= auth_can_execute_mutation; 244: C =:= unauth_can_execute_query; 245: C =:= unauth_can_execute_mutation; 246: C =:= should_catch_type_check_params_error; 247: C =:= should_catch_type_check_error; 248: C =:= should_catch_parsing_error; 249: C =:= should_catch_validation_error -> 250: {Mapping, Pattern} = example_schema_data(Config), 251: {ok, _} = mongoose_graphql:create_endpoint(C, Mapping, [Pattern]), 252: Ep = mongoose_graphql:get_endpoint(C), 253: [{endpoint, Ep} | Config]; 254: init_per_testcase(C, Config) when C =:= check_object_permissions; 255: C =:= check_field_permissions; 256: C =:= check_child_object_permissions; 257: C =:= check_child_object_field_permissions; 258: C =:= check_fragment_permissions; 259: C =:= check_interface_permissions; 260: C =:= check_interface_field_permissions; 261: C =:= check_inline_fragment_permissions; 262: C =:= check_union_permissions; 263: C =:= check_field_domain_permissions; 264: C =:= check_field_input_arg_domain_permissions; 265: C =:= check_field_list_arg_domain_permissions; 266: C =:= check_field_null_arg_domain_permissions; 267: C =:= check_field_jid_arg_domain_permissions; 268: C =:= check_field_subdomain_permissions; 269: C =:= check_field_global_permissions; 270: C =:= check_child_object_field_domain_permissions; 271: C =:= check_interface_field_domain_permissions -> 272: {Mapping, Pattern} = example_permissions_schema_data(Config), 273: {ok, _} = mongoose_graphql:create_endpoint(C, Mapping, [Pattern]), 274: Ep = mongoose_graphql:get_endpoint(C), 275: [{endpoint, Ep} | Config]; 276: init_per_testcase(C, Config) when C =:= use_dir_module_not_loaded; 277: C =:= use_dir_all_modules_loaded; 278: C =:= use_dir_module_and_service_not_loaded; 279: C =:= use_dir_all_modules_and_services_loaded; 280: C =:= use_dir_object_module_and_service_not_loaded; 281: C =:= use_dir_object_all_modules_and_services_loaded; 282: C =:= use_dir_auth_user_all_modules_and_services_loaded; 283: C =:= use_dir_auth_admin_all_modules_and_services_loaded; 284: C =:= use_dir_auth_user_module_and_service_not_loaded; 285: C =:= use_dir_auth_admin_module_and_service_not_loaded -> 286: {Mapping, Pattern} = example_directives_schema_data(Config), 287: {ok, _} = mongoose_graphql:create_endpoint(C, Mapping, [Pattern]), 288: Ep = mongoose_graphql:get_endpoint(C), 289: [{endpoint, Ep} | Config]; 290: init_per_testcase(C, Config) -> 291: [{endpoint_name, C} | Config]. 292: 293: end_per_testcase(_, _Config) -> 294: ok. 295: 296: can_create_endpoint(Config) -> 297: Name = ?config(endpoint_name, Config), 298: {Mapping, Pattern} = example_schema_protected_data(Config), 299: {ok, Pid} = mongoose_graphql:create_endpoint(Name, Mapping, [Pattern]), 300: 301: Ep = mongoose_graphql:get_endpoint(Name), 302: ?assertMatch({endpoint_context, Name, Pid, _, _}, Ep), 303: ?assertMatch(#root_schema{id = 'ROOT', query = <<"UserQuery">>, 304: mutation = <<"UserMutation">>}, 305: graphql_schema:get(Ep, 'ROOT')). 306: 307: can_load_split_schema(Config) -> 308: Name = ?config(endpoint_name, Config), 309: {Mapping, Pattern} = example_split_schema_data(Config), 310: {ok, Pid} = mongoose_graphql:create_endpoint(Name, Mapping, [Pattern]), 311: 312: Ep = mongoose_graphql:get_endpoint(Name), 313: ?assertMatch({endpoint_context, Name, Pid, _, _}, Ep), 314: ?assertMatch(#root_schema{id = 'ROOT', query = <<"Query">>, 315: mutation = <<"Mutation">>}, 316: graphql_schema:get(Ep, 'ROOT')), 317: ?assertMatch(#object_type{id = <<"Query">>}, graphql_schema:get(Ep, <<"Query">>)), 318: ?assertMatch(#object_type{id = <<"Mutation">>}, graphql_schema:get(Ep, <<"Mutation">>)). 319: 320: unexpected_internal_error(Config) -> 321: Name = ?config(endpoint_name, Config), 322: Doc = <<"mutation { field }">>, 323: Res = mongoose_graphql:execute(Name, undefined, Doc), 324: ?assertEqual({error, internal_crash}, Res). 325: 326: admin_and_user_load_global_types(_Config) -> 327: mongoose_graphql:init(), 328: AdminEp = mongoose_graphql:get_endpoint(admin), 329: ?assertMatch(#scalar_type{id = <<"JID">>}, graphql_schema:get(AdminEp, <<"JID">>)), 330: ?assertMatch(#directive_type{id = <<"protected">>}, 331: graphql_schema:get(AdminEp, <<"protected">>)), 332: 333: UserEp = mongoose_graphql:get_endpoint(user), 334: ?assertMatch(#scalar_type{id = <<"JID">>}, graphql_schema:get(UserEp, <<"JID">>)), 335: ?assertMatch(#directive_type{id = <<"protected">>}, 336: graphql_schema:get(UserEp, <<"protected">>)). 337: 338: %% Protected graphql 339: 340: auth_can_execute_protected_query(Config) -> 341: Ep = ?config(endpoint, Config), 342: Doc = <<"{ field }">>, 343: Res = mongoose_graphql:execute(Ep, undefined, Doc), 344: ?assertEqual({ok, #{data => #{<<"field">> => <<"Test field">>}}}, Res). 345: 346: auth_can_execute_protected_mutation(Config) -> 347: Ep = ?config(endpoint, Config), 348: Doc = <<"mutation { field }">>, 349: Res = mongoose_graphql:execute(Ep, undefined, Doc), 350: ?assertEqual({ok, #{data => #{<<"field">> => <<"Test field">>}}}, Res). 351: 352: unauth_cannot_execute_protected_query(Config) -> 353: Ep = ?config(endpoint, Config), 354: Doc = <<"query Q1 { field }">>, 355: Res = mongoose_graphql:execute(Ep, request(<<"Q1">>, Doc, false)), 356: ?assertMatch({error, #{error_term := {no_permissions, <<"Q1">>}, path := [<<"Q1">>]}}, Res). 357: 358: unauth_cannot_execute_protected_mutation(Config) -> 359: Ep = ?config(endpoint, Config), 360: Doc = <<"mutation { field }">>, 361: Res = mongoose_graphql:execute(Ep, request(Doc, false)), 362: ?assertMatch({error, #{error_term := {no_permissions, <<"ROOT">>}}}, Res). 363: 364: %% Unprotected graphql 365: 366: can_execute_query_with_vars(Config) -> 367: Ep = ?config(endpoint, Config), 368: Doc = <<"query Q1($value: String!) { id(value: $value)}">>, 369: Req = 370: #{document => Doc, 371: operation_name => <<"Q1">>, 372: vars => #{<<"value">> => <<"Hello">>}, 373: authorized => false, 374: ctx => #{}}, 375: Res = mongoose_graphql:execute(Ep, Req), 376: ?assertEqual({ok, #{data => #{<<"id">> => <<"Hello">>}}}, Res). 377: 378: unauth_can_execute_query(Config) -> 379: Ep = ?config(endpoint, Config), 380: Doc = <<"query { field }">>, 381: Res = mongoose_graphql:execute(Ep, request(Doc, false)), 382: ?assertEqual({ok, #{data => #{<<"field">> => <<"Test field">>}}}, Res). 383: 384: unauth_can_execute_mutation(Config) -> 385: Ep = ?config(endpoint, Config), 386: Doc = <<"mutation { field }">>, 387: Res = mongoose_graphql:execute(Ep, request(Doc, false)), 388: ?assertEqual({ok, #{data => #{<<"field">> => <<"Test field">>}}}, Res). 389: 390: auth_can_execute_query(Config) -> 391: Ep = ?config(endpoint, Config), 392: Doc = <<"query { field }">>, 393: Res = mongoose_graphql:execute(Ep, request(Doc, true)), 394: ?assertEqual({ok, #{data => #{<<"field">> => <<"Test field">>}}}, Res). 395: 396: auth_can_execute_mutation(Config) -> 397: Ep = ?config(endpoint, Config), 398: Doc = <<"mutation { field }">>, 399: Res = mongoose_graphql:execute(Ep, request(Doc, true)), 400: ?assertEqual({ok, #{data => #{<<"field">> => <<"Test field">>}}}, Res). 401: 402: %% Error handling 403: 404: should_catch_parsing_error(Config) -> 405: Ep = ?config(endpoint, Config), 406: Doc = <<"query { field ">>, 407: DocScan = <<"query { id(value: \"ala) }">>, 408: ResParseErr = mongoose_graphql:execute(Ep, request(Doc, false)), 409: ?assertMatch({error, #{phase := parse, error_term := {parser_error, _}}}, ResParseErr), 410: ResScanErr = mongoose_graphql:execute(Ep, request(DocScan, false)), 411: ?assertMatch({error, #{phase := parse, error_term := {scanner_error, _}}}, ResScanErr). 412: 413: should_catch_type_check_error(Config) -> 414: Ep = ?config(endpoint, Config), 415: Doc = <<"query { notExistingField(value: \"Hello\") }">>, 416: Res = mongoose_graphql:execute(Ep, request(Doc, false)), 417: ?assertMatch({error, #{phase := type_check, error_term := unknown_field}}, Res). 418: 419: should_catch_type_check_params_error(Config) -> 420: Ep = ?config(endpoint, Config), 421: Doc = <<"query { id(value: 12) }">>, 422: Res = mongoose_graphql:execute(Ep, request(Doc, false)), 423: ?assertMatch({error, #{phase := type_check, error_term := {input_coercion, _, _, _}}}, Res). 424: 425: should_catch_validation_error(Config) -> 426: Ep = ?config(endpoint, Config), 427: Doc = <<"query Q1{ id(value: \"ok\") } query Q1{ id(value: \"ok\") }">>, 428: % Query name must be unique 429: Res = mongoose_graphql:execute(Ep, request(<<"Q1">>, Doc, false)), 430: ?assertMatch({error, #{phase := validate, error_term := {not_unique, _}}}, Res). 431: 432: %% Permissions 433: 434: check_object_permissions(Config) -> 435: Doc = <<"query { field }">>, 436: FDoc = <<"mutation { field }">>, 437: ?assertPermissionsSuccess(Config, Doc), 438: ?assertPermissionsFailed(Config, FDoc). 439: 440: check_field_permissions(Config) -> 441: Doc = <<"{ field protectedField }">>, 442: ?assertPermissionsFailed(Config, Doc). 443: 444: check_child_object_permissions(Config) -> 445: Doc = <<"{ protectedObj{ type } }">>, 446: ?assertPermissionsFailed(Config, Doc). 447: 448: check_child_object_field_permissions(Config) -> 449: Doc = <<"{ obj { field } }">>, 450: FDoc = <<"{ obj { field protectedField } }">>, 451: ?assertPermissionsSuccess(Config, Doc), 452: ?assertPermissionsFailed(Config, FDoc). 453: 454: check_fragment_permissions(Config) -> 455: Config2 = [{op, <<"Q1">>} | Config], 456: Doc = <<"query Q1{ obj { ...body } } fragment body on Object { name field }">>, 457: FDoc = <<"query Q1{ obj { ...body } } fragment body on Object { name field protectedField }">>, 458: ?assertPermissionsSuccess(Config2, Doc), 459: ?assertPermissionsFailed(Config2, FDoc). 460: 461: check_interface_permissions(Config) -> 462: Doc = <<"{ interface { name } }">>, 463: FDoc = <<"{ protInterface { name } }">>, 464: ?assertPermissionsSuccess(Config, Doc), 465: ?assertPermissionsFailed(Config, FDoc). 466: 467: check_interface_field_permissions(Config) -> 468: Doc = <<"{ interface { protectedName } }">>, 469: FieldProtectedNotEnough = <<"{ obj { protectedName } }">>, 470: FieldProtectedEnough = <<"{ obj { otherName } }">>, 471: % Field is protected in interface and object, so it cannot be accessed. 472: ?assertPermissionsFailed(Config, Doc), 473: ?assertPermissionsFailed(Config, FieldProtectedEnough), 474: % Field is protected only in an interface, so it can be accessed from implementing objects. 475: ?assertPermissionsSuccess(Config, FieldProtectedNotEnough). 476: 477: check_inline_fragment_permissions(Config) -> 478: Doc = <<"{ interface { name otherName ... on Object { field } } }">>, 479: FDoc = <<"{ interface { name otherName ... on Object { field protectedField } } }">>, 480: FDoc2 = <<"{ interface { name ... on Object { field otherName} } }">>, 481: ?assertPermissionsSuccess(Config, Doc), 482: ?assertPermissionsFailed(Config, FDoc), 483: ?assertPermissionsFailed(Config, FDoc2). 484: 485: check_union_permissions(Config) -> 486: Doc = <<"{ union { ... on O1 { field1 } } }">>, 487: FDoc = <<"{ union { ... on O1 { field1 field1Protected } } }">>, 488: FDoc2 = <<"{ union { ... on O1 { field1 } ... on O2 { field2 } } }">>, 489: ?assertPermissionsSuccess(Config, Doc), 490: ?assertPermissionsFailed(Config, FDoc), 491: ?assertPermissionsFailed(Config, FDoc2). 492: 493: %% Domain permissions 494: 495: check_field_domain_permissions(Config) -> 496: Domain = <<"my-domain.com">>, 497: Config2 = [{op, <<"Q1">>}, {args, #{<<"domain">> => Domain}} | Config], 498: Doc = <<"{ field protectedField }">>, 499: Doc2 = <<"query Q1($domain: String) { protectedField domainProtectedField(argA: $domain" 500: ", argB: \"domain\") }">>, 501: FDoc = <<"{protectedField domainProtectedField(argA: \"domain.com\"," 502: " argB: \"domain.com\") }">>, 503: ?assertPermissionsSuccess(Config, Domain, Doc), 504: ?assertPermissionsSuccess(Config2, Domain, Doc2), 505: ?assertDomainPermissionsFailed(Config, Domain, [<<"argA">>], FDoc). 506: 507: check_child_object_field_domain_permissions(Config) -> 508: Domain = <<"my-domain.com">>, 509: Config2 = [{op, <<"Q1">>}, {args, #{<<"domain">> => Domain}} | Config], 510: Doc = <<"{ obj { field protectedField } }">>, 511: Doc2 = <<"query Q1($domain: String) { obj { protectedField domainProtectedField(argA: $domain" 512: ", argB: \"domain\") } }">>, 513: FDoc = <<"{ obj {protectedField domainProtectedField(argA: \"domain.com\"," 514: " argB: \"domain.com\") } }">>, 515: ?assertPermissionsSuccess(Config, Domain, Doc), 516: ?assertPermissionsSuccess(Config2, Domain, Doc2), 517: ?assertDomainPermissionsFailed(Config, Domain, [<<"argA">>], FDoc). 518: 519: check_interface_field_domain_permissions(Config) -> 520: Domain = <<"my-domain.com">>, 521: OkDomain = <<"{ interface { protectedDomainName(domain: \"my-domain.com\") } }">>, 522: OkDomain1 = <<"{ obj { protectedDomainName(domain: \"my-domain.com\") } }">>, 523: OkDomain2 = <<"{ obj { domainName(domain: \"my-domain.com\") } }">>, 524: WrongDomain = <<"{ interface { protectedDomainName(domain: \"domain.com\") } }">>, 525: WrongDomain1 = <<"{ obj { domainName(domain: \"domain.com\") } }">>, 526: ProtectedNotEnough = <<"{ obj { protectedDomainName(domain: \"domain.com\") } }">>, 527: ?assertPermissionsSuccess(Config, Domain, OkDomain), 528: ?assertPermissionsSuccess(Config, Domain, OkDomain1), 529: ?assertPermissionsSuccess(Config, Domain, OkDomain2), 530: % Field is protected in interface and object, so it cannot be accessed with the wrong domain. 531: ?assertDomainPermissionsFailed(Config, Domain, [<<"domain">>], WrongDomain), 532: ?assertDomainPermissionsFailed(Config, Domain, [<<"domain">>], WrongDomain1), 533: % Field is protected only in an interface, so it can be accessed from implementing objects 534: % with the wrong domain. 535: ?assertPermissionsSuccess(Config, Domain, ProtectedNotEnough). 536: 537: check_field_input_arg_domain_permissions(Config) -> 538: Domain = <<"my-domain.com">>, 539: DomainInput = #{<<"domain">> => Domain, <<"notDomain">> => <<"random text here">>}, 540: Config2 = [{op, <<"Q1">>}, {args, #{<<"domain">> => Domain, 541: <<"domainInput">> => DomainInput}} | Config], 542: Doc = <<"query Q1($domain: String, $domainInput: DomainInput!) " 543: "{ domainInputProtectedField(argA: $domain, argB: $domainInput)" 544: " domainProtectedField(argA: $domain, argB: \"domain.com\") }">>, 545: 546: FDoc = <<"{ domainInputProtectedField(argA: \"do.com\", argB: { domain: \"do.com\" }) }">>, 547: ?assertPermissionsSuccess(Config2, Domain, Doc), 548: ?assertDomainPermissionsFailed(Config, Domain, [<<"argA">>, <<"argB.domain">>], FDoc). 549: 550: 551: check_field_list_arg_domain_permissions(Config) -> 552: [{Subdomain, Domain} | _] = ?config(domains, Config), 553: Domains = [#{<<"domain">> => Domain, <<"notDomain">> => <<"random text here">>}, 554: #{<<"domain">> => Subdomain}], 555: Config2 = [{op, <<"Q1">>}, {args, #{<<"domains">> => Domains}} | Config], 556: Doc = <<"query Q1($domains: [DomainInput!]) " 557: "{ domainListInputProtectedField(domains: $domains) }">>, 558: 559: FDoc = <<"{ domainListInputProtectedField(domains: [{ domain: \"do.com\" }]) }">>, 560: ?assertPermissionsSuccess(Config2, Domain, Doc), 561: ?assertDomainPermissionsFailed(Config, Domain, [<<"domains.domain">>], FDoc). 562: 563: check_field_null_arg_domain_permissions(Config) -> 564: [{_, Domain} | _] = ?config(domains, Config), 565: FDoc = <<"{ domainProtectedField domainInputProtectedField }">>, 566: ?assertDomainPermissionsFailed(Config, Domain, [<<"argA">>], FDoc). 567: 568: check_field_jid_arg_domain_permissions(Config) -> 569: Domain = <<"my-domain.com">>, 570: Config2 = [{op, <<"Q1">>}, 571: {args, #{<<"jid">> => <<"bob@", Domain/binary>>}} | Config], 572: Doc = <<"query Q1($jid: JID) { domainJIDProtectedField(argA: $jid, argB: \"bob@bob\") }">>, 573: FDoc = <<"{ domainJIDProtectedField(argA: \"bob@do.com\", argB: \"bob@do.com\") }">>, 574: ?assertPermissionsSuccess(Config2, Domain, Doc), 575: ?assertDomainPermissionsFailed(Config, Domain, [<<"argA">>], FDoc). 576: 577: check_field_subdomain_permissions(Config) -> 578: [{Subdomain, Domain}, {FSubdomain, _Domain2}] = ?config(domains, Config), 579: Config2 = [{op, <<"Q1">>}, {args, #{<<"domain">> => Subdomain}} | Config], 580: FConfig2 = [{op, <<"Q1">>}, {args, #{<<"domain">> => FSubdomain}} | Config], 581: Doc = <<"query Q1($domain: String) " 582: "{ protectedField domainProtectedField(argA: $domain, argB: \"do.com\") }">>, 583: ?assertPermissionsSuccess(Config2, Domain, Doc), 584: ?assertDomainPermissionsFailed(FConfig2, Domain, [<<"argA">>], Doc). 585: 586: check_field_global_permissions(Config) -> 587: Domain = <<"my-domain.com">>, 588: Doc = <<"{ protectedField onlyForGlobalAdmin }">>, 589: ?assertMatch(#document{}, check_permissions(Config, true, Doc)), 590: ?assertThrow({error, #{error_term := {no_permissions, _, #{type := global}}}}, 591: check_domain_permissions(Config, Domain, Doc)). 592: 593: %% Error formatting 594: 595: format_internal_crash(_Config) -> 596: {Code, Res} = mongoose_graphql_errors:format_error(internal_crash), 597: ?assertEqual(500, Code), 598: ?assertMatch(#{extensions := #{code := internal_server_error}}, Res). 599: 600: format_parse_errors(_Config) -> 601: ParserError = make_error(parse, {parser_error, {0, graphql_parser, "parser_error_msg"}}), 602: ScannerError = make_error(parse, {scanner_error, 603: {0, graphql_scanner, {illegal, "illegal_characters"}}}), 604: ScannerError2 = make_error(parse, {scanner_error, 605: {0, graphql_scanner, {user, "user_scanner_err"}}}), 606: 607: {400, ResParser} = mongoose_graphql_errors:format_error(ParserError), 608: {400, ResScanner} = mongoose_graphql_errors:format_error(ScannerError), 609: {400, ResScanner2} = mongoose_graphql_errors:format_error(ScannerError2), 610: ?assertErrMsg(parser_error, <<"parser_error_msg">>, ResParser), 611: ?assertErrMsg(scanner_error, <<"illegal_characters">>, ResScanner), 612: ?assertErrMsg(scanner_error, <<"user_scanner_err">>, ResScanner2). 613: 614: format_decode_errors(_Config) -> 615: {400, Msg1} = mongoose_graphql_errors:format_error(make_error(decode, no_query_supplied)), 616: {400, Msg2} = mongoose_graphql_errors:format_error(make_error(decode, invalid_json_body)), 617: {400, Msg3} = mongoose_graphql_errors:format_error(make_error(decode, variables_invalid_json)), 618: 619: ?assertErrMsg(no_query_supplied, <<"The query was not supplied">>, Msg1), 620: ?assertErrMsg(invalid_json_body, <<"invalid">>, Msg2), 621: ?assertErrMsg(variables_invalid_json, <<"invalid">>, Msg3). 622: 623: format_authorize_error(_Config) -> 624: {401, Msg1} = mongoose_graphql_errors:format_error(make_error(authorize, wrong_credentials)), 625: {401, Msg2} = mongoose_graphql_errors:format_error( 626: make_error([<<"ROOT">>], authorize, {no_permissions, <<"ROOT">>})), 627: {401, Msg3} = mongoose_graphql_errors:format_error( 628: make_error(authorize, {request_error, {header, <<"authorization">>}, 'msg'})), 629: 630: ?assertErrMsg(wrong_credentials, <<"provided credentials are wrong">>, Msg1), 631: ?assertErrMsg(no_permissions, <<"without permissions">>, Msg2), 632: ?assertMatch(#{path := [<<"ROOT">>]}, Msg2), 633: ?assertErrMsg(request_error, <<"Malformed authorization header">>, Msg3). 634: 635: format_validate_error(_Config) -> 636: % Ensure the module can format this phase 637: {400, Msg} = mongoose_graphql_errors:format_error( 638: make_error(validate, {not_unique, <<"OpName">>})), 639: ?assertMatch(#{extensions := #{code := not_unique}}, Msg). 640: 641: format_type_check_error(_Config) -> 642: % Ensure the module can format this phase 643: {400, Msg} = mongoose_graphql_errors:format_error( 644: make_error(type_check, non_null)), 645: ?assertMatch(#{extensions := #{code := non_null}}, Msg). 646: 647: format_execute_error(_Config) -> 648: % Ensure the module can format this phase 649: {400, Msg} = mongoose_graphql_errors:format_error( 650: make_error(execute, {resolver_error, any_error})), 651: ?assertMatch(#{extensions := #{code := resolver_error}}, Msg). 652: 653: format_uncategorized_error(_Config) -> 654: % Ensure the module can format this phase 655: {400, Msg} = mongoose_graphql_errors:format_error( 656: make_error(uncategorized, any_error)), 657: ?assertMatch(#{extensions := #{code := any_error}}, Msg). 658: 659: format_any_error(_Config) -> 660: {400, Msg1} = mongoose_graphql_errors:format_error(any_error), 661: {400, Msg2} = mongoose_graphql_errors:format_error(<<"any_error">>), 662: {400, Msg3} = mongoose_graphql_errors:format_error({1, any_error}), 663: {400, Msg4} = mongoose_graphql_errors:format_error(#{msg => any_error}), 664: ?assertErrMsg(uncategorized, <<"any_error">>, Msg1), 665: ?assertErrMsg(uncategorized, <<"any_error">>, Msg2), 666: ?assertErrMsg(uncategorized, <<"any_error">>, Msg3), 667: ?assertErrMsg(uncategorized, <<"any_error">>, Msg4). 668: 669: %% Listeners 670: 671: auth_user_can_access_protected_types(Config) -> 672: Ep = ?config(endpoint_addr, Config), 673: Body = #{query => "{ field }"}, 674: {Status, Data} = execute(Ep, Body, {<<"alice@localhost">>, <<"makota">>}), 675: assert_access_granted(Status, Data). 676: 677: use_directive_can_use_auth_user_domain(Config) -> 678: Ep = ?config(endpoint_addr, Config), 679: Body = #{query => "{ command }"}, 680: {{<<"200">>, <<"OK">>}, #{<<"errors">> := [Error]}} = 681: execute(Ep, Body, {<<"alice@localhost">>, <<"makota">>}), 682: #{<<"extensions">> := 683: #{<<"code">> := <<"deps_not_loaded">>, 684: <<"not_loaded_modules">> := [<<"mod_x">>], 685: <<"not_loaded_services">> := []} 686: } = Error. 687: 688: no_creds_defined_admin_can_access_protected(_Config) -> 689: Port = 5559, 690: Ep = "http://localhost:" ++ integer_to_list(Port), 691: start_listener(no_creds_admin_listener, Port, #{schema_endpoint => admin}), 692: Body = #{<<"query">> => <<"{ field }">>}, 693: {Status, Data} = execute(Ep, Body, undefined), 694: assert_access_granted(Status, Data). 695: 696: auth_admin_can_access_protected_types(Config) -> 697: Ep = ?config(endpoint_addr, Config), 698: Body = #{query => "{ field }"}, 699: {Status, Data} = execute(Ep, Body, {<<"admin">>, <<"secret">>}), 700: assert_access_granted(Status, Data). 701: 702: auth_domain_admin_can_access_protected_types(Config) -> 703: Ep = ?config(endpoint_addr, Config), 704: Body = #{query => "{ field }"}, 705: {Status, Data} = execute(Ep, Body, {<<"admin@localhost">>, <<"makota">>}), 706: assert_access_granted(Status, Data). 707: 708: use_directive_can_use_auth_domain_admin_domain(Config) -> 709: Ep = ?config(endpoint_addr, Config), 710: Body = #{query => "{ command }"}, 711: {{<<"200">>, <<"OK">>}, #{<<"errors">> := [Error]}} = 712: execute(Ep, Body, {<<"admin@localhost">>, <<"makota">>}), 713: #{<<"extensions">> := 714: #{<<"code">> := <<"deps_not_loaded">>, 715: <<"not_loaded_modules">> := [<<"mod_x">>], 716: <<"not_loaded_services">> := []} 717: } = Error. 718: 719: auth_domain_admin_wrong_password_error(Config) -> 720: Ep = ?config(endpoint_addr, Config), 721: Body = #{query => "{ field }"}, 722: {Status, Data} = execute(Ep, Body, {<<"admin@localhost">>, <<"mapsa">>}), 723: assert_no_permissions(wrong_credentials, Status, Data). 724: 725: auth_domain_admin_nonexistent_domain_error(Config) -> 726: Ep = ?config(endpoint_addr, Config), 727: Body = #{query => "{ field }"}, 728: {Status, Data} = execute(Ep, Body, {<<"admin@localhost2">>, <<"makota">>}), 729: assert_no_permissions(wrong_credentials, Status, Data). 730: 731: auth_domain_admin_can_access_owned_domain(Config) -> 732: Ep = ?config(endpoint_addr, Config), 733: Body = #{query => "{ fieldDP(argA: \"localhost\") }"}, 734: {Status, Data} = execute(Ep, Body, {<<"admin@localhost">>, <<"makota">>}), 735: assert_access_granted(Status, Data). 736: 737: auth_domain_admin_cannot_access_other_domain(Config) -> 738: Ep = ?config(endpoint_addr, Config), 739: Body = #{query => "{ field fieldDP(argA: \"domain.com\") }"}, 740: {Status, Data} = execute(Ep, Body, {<<"admin@localhost">>, <<"makota">>}), 741: assert_no_permissions(no_permissions, Status, Data). 742: 743: auth_domain_admin_cannot_access_global(Config) -> 744: Ep = ?config(endpoint_addr, Config), 745: Body = #{query => "{ fieldGlobal(argA: \"localhost\") }"}, 746: {Status, Data} = execute(Ep, Body, {<<"admin@localhost">>, <<"makota">>}), 747: assert_no_permissions(no_permissions, Status, Data). 748: 749: malformed_auth_header_error(Config) -> 750: Ep = ?config(endpoint_addr, Config), 751: % The encoded credentials value is malformed and cannot be decoded. 752: Headers = [{<<"Authorization">>, <<"Basic YWRtaW46c2VjcmV">>}], 753: {Status, Data} = post_request(Ep, Headers, <<"">>), 754: assert_no_permissions(request_error, Status, Data). 755: 756: auth_wrong_creds_error(Config) -> 757: Ep = ?config(endpoint_addr, Config), 758: Body = #{query => "{ field }"}, 759: {Status, Data} = execute(Ep, Body, {<<"user">>, <<"wrong_password">>}), 760: assert_no_permissions(wrong_credentials, Status, Data). 761: 762: invalid_json_body_error(Config) -> 763: Ep = ?config(endpoint_addr, Config), 764: Body = <<"">>, 765: {Status, Data} = execute(Ep, Body, undefined), 766: ?assertEqual({<<"400">>, <<"Bad Request">>}, Status), 767: assert_code(invalid_json_body, Data). 768: 769: no_query_supplied_error(Config) -> 770: Ep = ?config(endpoint_addr, Config), 771: Body = #{}, 772: {Status, Data} = execute(Ep, Body, undefined), 773: ?assertEqual({<<"400">>, <<"Bad Request">>}, Status), 774: assert_code(no_query_supplied, Data). 775: 776: variables_invalid_json_error(Config) -> 777: Ep = ?config(endpoint_addr, Config), 778: Body = #{<<"query">> => <<"{ field }">>, <<"variables">> => <<"{1: 2}">>}, 779: {Status, Data} = execute(Ep, Body, undefined), 780: ?assertEqual({<<"400">>, <<"Bad Request">>}, Status), 781: assert_code(variables_invalid_json, Data). 782: 783: listener_reply_with_parsing_error(Config) -> 784: Ep = ?config(endpoint_addr, Config), 785: Body = #{<<"query">> => <<"{ field ">>}, 786: {Status, Data} = execute(Ep, Body, undefined), 787: ?assertEqual({<<"400">>, <<"Bad Request">>}, Status), 788: assert_code(parser_error, Data), 789: 790: BodyScanner = #{<<"query">> => <<"mutation { id(value: \"asdfsad) } ">>}, 791: {StatusScanner, DataScanner} = execute(Ep, BodyScanner, undefined), 792: ?assertEqual({<<"400">>, <<"Bad Request">>}, StatusScanner), 793: assert_code(scanner_error, DataScanner). 794: 795: listener_reply_with_type_check_error(Config) -> 796: Ep = ?config(endpoint_addr, Config), 797: Body = #{<<"query">> => <<"mutation { id(value: 12) }">>}, 798: {Status, Data} = execute(Ep, Body, undefined), 799: ?assertEqual({<<"400">>, <<"Bad Request">>}, Status), 800: assert_code(input_coercion, Data). 801: 802: listener_reply_with_validation_error(Config) -> 803: Ep = ?config(endpoint_addr, Config), 804: Body = #{<<"query">> => <<"query Q1 { field } query Q1 { field }">>, 805: <<"operationName">> => <<"Q1">>}, 806: {Status, Data} = execute(Ep, Body, undefined), 807: ?assertEqual({<<"400">>, <<"Bad Request">>}, Status), 808: assert_code(not_unique, Data). 809: 810: listener_can_execute_query_with_variables(Config) -> 811: Ep = ?config(endpoint_addr, Config), 812: Body = #{query => "mutation M1($value: String!){ id(value: $value) } query Q1{ field }", 813: variables => #{value => <<"Hello">>}, 814: operationName => <<"M1">> 815: }, 816: {Status, Data} = execute(Ep, Body, undefined), 817: assert_access_granted(Status, Data), 818: ?assertMatch(#{<<"data">> := #{<<"id">> := <<"Hello">>}}, Data). 819: 820: listener_unauth_cannot_access_protected_types(Config) -> 821: Ep = ?config(endpoint_addr, Config), 822: Body = #{query => "{ field }"}, 823: {Status, Data} = execute(Ep, Body, undefined), 824: ?assertMatch(#{<<"errors">> := [#{<<"path">> := [<<"ROOT">>]}]}, Data), 825: assert_no_permissions(no_permissions, Status, Data). 826: 827: listener_unauth_can_access_unprotected_types(Config) -> 828: Ep = ?config(endpoint_addr, Config), 829: Body = #{query => "mutation { field }"}, 830: {Status, Data} = execute(Ep, Body, undefined), 831: assert_access_granted(Status, Data). 832: 833: %% Use Directive 834: 835: use_dir_all_modules_loaded(Config) -> 836: Doc = <<"{catA { command(domain: \"localhost\")} }">>, 837: Ctx = #{}, 838: {Ast, Ctx2} = check_directives(Config, Ctx, Doc), 839: Res = execute_ast(Config, Ctx2, Ast), 840: ?assertEqual(#{data => #{<<"catA">> => #{<<"command">> => <<"command">>}}}, Res). 841: 842: use_dir_module_not_loaded(Config) -> 843: Doc = <<"{catA { command(domain: \"test-domain.com\")} }">>, 844: Ctx = #{}, 845: {Ast, Ctx2} = check_directives(Config, Ctx, Doc), 846: #{errors := [Error]} = execute_ast(Config, Ctx2, Ast), 847: #{extensions := 848: #{code := deps_not_loaded, 849: not_loaded_modules := [<<"mod_b">>], 850: not_loaded_services := [] 851: }, 852: message := <<"Some of required modules or services are not loaded">>, 853: path := [<<"catA">>, <<"command">>] 854: } = Error. 855: 856: use_dir_all_modules_and_services_loaded(Config) -> 857: Doc = <<"{catA { command2(domain: \"localhost\")} }">>, 858: Ctx = #{}, 859: {Ast, Ctx2} = check_directives(Config, Ctx, Doc), 860: Res = execute_ast(Config, Ctx2, Ast), 861: ?assertEqual(#{data => #{<<"catA">> => #{<<"command2">> => <<"command2">>}}}, Res). 862: 863: use_dir_module_and_service_not_loaded(Config) -> 864: Doc = <<"{catA { command3(domain: \"localhost\")} }">>, 865: Ctx = #{}, 866: {Ast, Ctx2} = check_directives(Config, Ctx, Doc), 867: #{errors := [Error]} = execute_ast(Config, Ctx2, Ast), 868: #{extensions := 869: #{code := deps_not_loaded, 870: not_loaded_modules := [<<"mod_z">>], 871: not_loaded_services := [<<"service_d">>] 872: }, 873: message := <<"Some of required modules or services are not loaded">>, 874: path := [<<"catA">>, <<"command3">>] 875: } = Error. 876: 877: use_dir_object_all_modules_and_services_loaded(Config) -> 878: Doc = <<"{ catC { command(domain: \"localhost\") } }">>, 879: Ctx = #{}, 880: {Ast, Ctx2} = check_directives(Config, Ctx, Doc), 881: Res = execute_ast(Config, Ctx2, Ast), 882: ?assertEqual(#{data => #{<<"catC">> => #{<<"command">> => <<"command">>}}}, Res). 883: 884: use_dir_object_module_and_service_not_loaded(Config) -> 885: Doc = <<"{ catB { command(domain: \"localhost\") } }">>, 886: Ctx = #{}, 887: {Ast, Ctx2} = check_directives(Config, Ctx, Doc), 888: #{errors := [Error]} = execute_ast(Config, Ctx2, Ast), 889: #{extensions := 890: #{code := deps_not_loaded, 891: not_loaded_modules := [<<"mod_x">>], 892: not_loaded_services := [<<"service_x">>] 893: }, 894: message := <<"Some of required modules or services are not loaded">>, 895: path := [<<"catB">>, <<"command">>] 896: } = Error. 897: 898: use_dir_auth_user_all_modules_and_services_loaded(Config) -> 899: Doc = <<"{ catC { command2 } }">>, 900: Ctx = #{user => jid:make_bare(<<"user">>, <<"localhost">>)}, 901: {Ast, Ctx2} = check_directives(Config, Ctx, Doc), 902: Res = execute_ast(Config, Ctx2, Ast), 903: ?assertEqual(#{data => #{<<"catC">> => #{<<"command2">> => <<"command2">>}}}, Res). 904: 905: use_dir_auth_admin_all_modules_and_services_loaded(Config) -> 906: Doc = <<"{ catC { command2 } }">>, 907: Ctx = #{user => jid:make_bare(<<"admin">>, <<"localhost">>)}, 908: {Ast, Ctx2} = check_directives(Config, Ctx, Doc), 909: Res = execute_ast(Config, Ctx2, Ast), 910: ?assertEqual(#{data => #{<<"catC">> => #{<<"command2">> => <<"command2">>}}}, Res). 911: 912: use_dir_auth_user_module_and_service_not_loaded(Config) -> 913: Doc = <<"{ catB { command2 } }">>, 914: Ctx = #{user => jid:make_bare(<<"user">>, <<"localhost">>)}, 915: {Ast, Ctx2} = check_directives(Config, Ctx, Doc), 916: #{errors := [Error]} = execute_ast(Config, Ctx2, Ast), 917: #{extensions := 918: #{code := deps_not_loaded, 919: not_loaded_modules := [<<"mod_x">>], 920: not_loaded_services := [<<"service_x">>] 921: }, 922: message := <<"Some of required modules or services are not loaded">>, 923: path := [<<"catB">>, <<"command2">>] 924: } = Error. 925: 926: use_dir_auth_admin_module_and_service_not_loaded(Config) -> 927: Doc = <<"{ catB { command2 } }">>, 928: Ctx = #{user => jid:make_bare(<<"admin">>, <<"localhost">>)}, 929: {Ast, Ctx2} = check_directives(Config, Ctx, Doc), 930: #{errors := [Error]} = execute_ast(Config, Ctx2, Ast), 931: #{extensions := 932: #{code := deps_not_loaded, 933: not_loaded_modules := [<<"mod_x">>], 934: not_loaded_services := [<<"service_x">>] 935: }, 936: message := <<"Some of required modules or services are not loaded">>, 937: path := [<<"catB">>, <<"command2">>] 938: } = Error. 939: 940: %% Helpers 941: 942: assert_code(Code, Data) -> 943: BinCode = atom_to_binary(Code), 944: ?assertMatch(#{<<"errors">> := [#{<<"extensions">> := #{<<"code">> := BinCode}}]}, Data). 945: 946: assert_no_permissions(ExpectedCode, Status, Data) -> 947: ?assertEqual({<<"401">>, <<"Unauthorized">>}, Status), 948: assert_code(ExpectedCode, Data). 949: 950: assert_access_granted(Status, Data) -> 951: ?assertEqual({<<"200">>, <<"OK">>}, Status), 952: % access was granted, no error was returned 953: ?assertNotMatch(#{<<"errors">> := _}, Data). 954: 955: assert_err_msg(Code, MsgContains, #{message := Msg} = ErrorMsg) -> 956: ?assertMatch(#{extensions := #{code := Code}}, ErrorMsg), 957: ?assertNotEqual(nomatch, binary:match(Msg, MsgContains)). 958: 959: make_error(Phase, Term) -> 960: #{phase => Phase, error_term => Term}. 961: 962: make_error(Path, Phase, Term) -> 963: #{path => Path, phase => Phase, error_term => Term}. 964: 965: check_directives(Config, Ctx, Doc) -> 966: Ep = ?config(endpoint, Config), 967: {ok, Ast} = graphql:parse(Doc), 968: {ok, #{ast := Ast2}} = graphql:type_check(Ep, Ast), 969: ok = graphql:validate(Ast2), 970: Default = #{operation_name => undefined, params => #{}, authorized => true, 971: error_module => mongoose_graphql_errors}, 972: Ctx2 = maps:merge(Default, Ctx), 973: {mongoose_graphql_directive:process_directives(Ctx2, Ast2), Ctx2}. 974: 975: execute_ast(Config, Ctx, Ast) -> 976: Ep = ?config(endpoint, Config), 977: graphql:execute(Ep, Ctx, Ast). 978: 979: check_permissions(Config, Auth, Doc) -> 980: Ep = ?config(endpoint, Config), 981: Op = proplists:get_value(op, Config, undefined), 982: {ok, Ast} = graphql:parse(Doc), 983: {ok, #{ast := Ast2}} = graphql:type_check(Ep, Ast), 984: ok = graphql:validate(Ast2), 985: Ctx = #{operation_name => Op, authorized => Auth, params => #{}}, 986: #document{} = mongoose_graphql_directive:process_directives(Ctx, Ast2). 987: 988: check_domain_permissions(Config, Domain, Doc) -> 989: Ep = ?config(endpoint, Config), 990: Args = proplists:get_value(args, Config, #{}), 991: Op = proplists:get_value(op, Config, undefined), 992: {ok, Ast} = graphql:parse(Doc), 993: {ok, #{ast := Ast2, fun_env := FunEnv}} = graphql:type_check(Ep, Ast), 994: ok = graphql:validate(Ast2), 995: Coerced = graphql:type_check_params(Ep, FunEnv, Op, Args), 996: Admin = jid:make_bare(<<"admin">>, Domain), 997: Ctx = #{operation_name => Op, authorized => true, authorized_as => domain_admin, 998: admin => Admin, params => Coerced}, 999: #document{} = mongoose_graphql_directive:process_directives(Ctx, Ast2). 1000: 1001: request(Doc, Authorized) -> 1002: request(undefined, Doc, Authorized). 1003: 1004: request(Op, Doc, Authorized) -> 1005: #{document => Doc, 1006: operation_name => Op, 1007: vars => #{}, 1008: authorized => Authorized, 1009: ctx => #{}}. 1010: 1011: example_split_schema_data(Config) -> 1012: Pattern = filename:join([proplists:get_value(data_dir, Config), 1013: "split_schema", "*.gql"]), 1014: Mapping = 1015: #{objects => 1016: #{'Query' => mongoose_graphql_default_resolver, 1017: 'Mutation' => mongoose_graphql_default_resolver, 1018: default => mongoose_graphql_default_resolver}}, 1019: {Mapping, Pattern}. 1020: 1021: example_schema_protected_data(Config) -> 1022: Pattern = filename:join([proplists:get_value(data_dir, Config), "protected_schema.gql"]), 1023: Mapping = 1024: #{objects => 1025: #{'UserQuery' => mongoose_graphql_default_resolver, 1026: 'UserMutation' => mongoose_graphql_default_resolver, 1027: default => mongoose_graphql_default_resolver}}, 1028: {Mapping, Pattern}. 1029: 1030: example_schema_data(Config) -> 1031: Pattern = filename:join([proplists:get_value(data_dir, Config), "schema.gql"]), 1032: Mapping = 1033: #{objects => 1034: #{'UserQuery' => mongoose_graphql_default_resolver, 1035: 'UserMutation' => mongoose_graphql_default_resolver, 1036: default => mongoose_graphql_default_resolver}}, 1037: {Mapping, Pattern}. 1038: 1039: example_permissions_schema_data(Config) -> 1040: Pattern = filename:join([proplists:get_value(data_dir, Config), "permissions_schema.gql"]), 1041: Mapping = 1042: #{objects => 1043: #{'UserQuery' => mongoose_graphql_default_resolver, 1044: 'UserMutation' => mongoose_graphql_default_resolver, 1045: default => mongoose_graphql_default_resolver}, 1046: enums => #{default => mongoose_graphql_default_resolver}, 1047: scalars => #{default => mongoose_graphql_scalar}, 1048: interfaces => #{default => mongoose_graphql_default_resolver}, 1049: unions => #{default => mongoose_graphql_default_resolver}}, 1050: {Mapping, Pattern}. 1051: 1052: example_directives_schema_data(Config) -> 1053: Pattern = filename:join([proplists:get_value(data_dir, Config), "directives_schema.gql"]), 1054: Mapping = 1055: #{objects => 1056: #{'UserQuery' => mongoose_graphql_default_resolver, 1057: 'UserMutation' => mongoose_graphql_default_resolver, 1058: default => mongoose_graphql_default_resolver}, 1059: enums => #{default => mongoose_graphql_default_resolver}, 1060: scalars => #{default => mongoose_graphql_scalar}, 1061: interfaces => #{default => mongoose_graphql_default_resolver}, 1062: unions => #{default => mongoose_graphql_default_resolver}}, 1063: {Mapping, Pattern}. 1064: 1065: example_listener_schema_data(Config) -> 1066: Pattern = filename:join([proplists:get_value(data_dir, Config), "listener_schema.gql"]), 1067: Mapping = 1068: #{objects => 1069: #{'UserQuery' => mongoose_graphql_default_resolver, 1070: 'UserMutation' => mongoose_graphql_default_resolver, 1071: default => mongoose_graphql_default_resolver}, 1072: enums => #{default => mongoose_graphql_default_resolver}}, 1073: {Mapping, Pattern}. 1074: 1075: -spec init_ep_listener(integer(), atom(), listener_opts(), [{atom(), term()}]) -> 1076: [{atom(), term()}]. 1077: init_ep_listener(Port, EpName, ListenerOpts, Config) -> 1078: Pid = spawn(fun() -> 1079: Name = list_to_atom("gql_listener_" ++ atom_to_list(EpName)), 1080: ok = start_listener(Name, Port, ListenerOpts), 1081: {Mapping, Pattern} = example_listener_schema_data(Config), 1082: {ok, _} = mongoose_graphql:create_endpoint(EpName, Mapping, [Pattern]), 1083: receive 1084: stop -> 1085: ok 1086: end 1087: end), 1088: [{test_process, Pid}, {endpoint_addr, "http://localhost:" ++ integer_to_list(Port)} | Config]. 1089: 1090: -spec start_listener(atom(), integer(), listener_opts()) -> ok. 1091: start_listener(Ref, Port, Opts) -> 1092: Dispatch = cowboy_router:compile([ 1093: {'_', [{"/graphql", mongoose_graphql_handler, Opts}]} 1094: ]), 1095: {ok, _} = cowboy:start_clear(Ref, 1096: [{port, Port}], 1097: #{env => #{dispatch => Dispatch}}), 1098: ok. 1099: 1100: -spec execute(binary(), map(), undefined | {binary(), binary()}) -> {{binary(), binary()}, map()}. 1101: execute(EpAddr, Body, undefined) -> 1102: post_request(EpAddr, [], Body); 1103: execute(EpAddr, Body, {Username, Password}) -> 1104: Creds = base64:encode(<<Username/binary, ":", Password/binary>>), 1105: Headers = [{<<"Authorization">>, <<"Basic ", Creds/binary>>}], 1106: post_request(EpAddr, Headers, Body). 1107: 1108: post_request(EpAddr, HeadersIn, Body) when is_binary(Body) -> 1109: {ok, Client} = fusco:start(EpAddr, []), 1110: Headers = [{<<"Content-Type">>, <<"application/json">>}, 1111: {<<"Request-Id">>, random_request_id()} | HeadersIn], 1112: {ok, {ResStatus, _, ResBody, _, _}} = Res = 1113: fusco:request(Client, <<"/graphql">>, <<"POST">>, Headers, Body, 5000), 1114: fusco:disconnect(Client), 1115: ct:log("~p", [Res]), 1116: {ResStatus, jiffy:decode(ResBody, [return_maps])}; 1117: post_request(Ep, HeadersIn, Body) -> 1118: post_request(Ep, HeadersIn, jiffy:encode(Body)). 1119: 1120: random_request_id() -> 1121: base16:encode(crypto:strong_rand_bytes(8)). 1122: 1123: meck_domain_api(Config) -> 1124: Hosts = [<<"test-domain.com">>, <<"localhost">>], 1125: % mongoose_domain_api 1126: meck:new(mongoose_domain_api, [no_link]), 1127: meck:expect(mongoose_domain_api, get_host_type, 1128: fun (Host) -> 1129: case lists:member(Host, Hosts) of 1130: true -> {ok, Host}; 1131: false -> {error, not_found} 1132: end 1133: end), 1134: meck:expect(mongoose_domain_api, get_subdomain_info, fun (_) -> {error, not_found} end), 1135: [{hosts, Hosts} | Config]. 1136: 1137: unmeck_domain_api(_Config) -> 1138: meck:unload(mongoose_domain_api). 1139: 1140: meck_module_and_service_checking(Config) -> 1141: LoadedModules = #{<<"test-domain.com">> => [mod_a, mod_d], 1142: <<"localhost">> => [mod_a, mod_b, mod_c]}, 1143: LoadedServices = [service_a, service_b], 1144: % gen_mod 1145: meck:new(gen_mod, [no_link]), 1146: meck:expect(gen_mod, is_loaded, 1147: fun (Domain, M) -> lists:member(M, maps:get(Domain, LoadedModules, [])) end), 1148: % mongoose_service 1149: meck:new(mongoose_service, [no_link]), 1150: meck:expect(mongoose_service, is_loaded, fun (M) -> lists:member(M, LoadedServices) end), 1151: [{loaded_services, LoadedServices}, 1152: {loaded_modules, LoadedModules} | Config]. 1153: 1154: unmeck_module_and_service_checking(_Config) -> 1155: meck:unload(gen_mod), 1156: meck:unload(mongoose_service).