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