1: %%============================================================================== 2: %% Copyright 2012 Erlang Solutions Ltd. 3: %% 4: %% Test the mod_vcard* modules. 5: %% mod_vcard uses mnesia 6: %% mod_vcard_ldap uses ldap 7: %% mod_vcard_rdbms uses rdbms 8: %% 9: %% They share many comonalities but sometimes behave differently or have 10: %% some extra or missing features. They also need different config depending 11: %% on which vhost they're running on. 12: %% 13: %% ----- 14: %% 15: %% Licensed under the Apache License, Version 2.0 (the "License"); 16: %% you may not use this file except in compliance with the License. 17: %% You may obtain a copy of the License at 18: %% 19: %% http://www.apache.org/licenses/LICENSE-2.0 20: %% 21: %% Unless required by applicable law or agreed to in writing, software 22: %% distributed under the License is distributed on an "AS IS" BASIS, 23: %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 24: %% See the License for the specific language governing permissions and 25: %% limitations under the License. 26: %%============================================================================== 27: 28: -module(vcard_SUITE). 29: -compile([export_all, nowarn_export_all]). 30: 31: -include_lib("common_test/include/ct.hrl"). 32: -include_lib("escalus/include/escalus_xmlns.hrl"). 33: -include_lib("escalus/include/escalus.hrl"). 34: -include_lib("exml/include/exml.hrl"). 35: 36: %% Element CData 37: -define(EL(Element, Name), exml_query:path(Element, [{element, Name}])). 38: -define(EL_CD(Element, Name), exml_query:path(Element, [{element, Name}, cdata])). 39: 40: -define(PHOTO_BIN, <<130, 192, 33, 159, 204, 86, 12, 63, 132, 164>>). 41: -define(PHOTO_BASE_64, <<"gsAhn8xWDD+EpA==">>). %% jlib:encode_base64(?PHOTO_BIN) 42: 43: -import(distributed_helper, [mim/0, 44: require_rpc_nodes/1, 45: subhost_pattern/1, 46: rpc/4]). 47: -import(ldap_helper, [get_ldap_base/1, 48: call_ldap/3]). 49: -import(domain_helper, [host_type/0, 50: host_types/0, 51: domain/0]). 52: 53: %%-------------------------------------------------------------------- 54: %% Suite configuration 55: %%-------------------------------------------------------------------- 56: 57: all() -> 58: [{group, rw}, 59: {group, ro_full}, 60: {group, ro_limited}, 61: {group, params_limited_infinity}, 62: {group, ro_no}, 63: {group, ldap_only} 64: ]. 65: 66: groups() -> 67: %% setting test data before tests is proving awkward so might as well use the 68: %% data set in the update tests to test the rest. 69: [{rw, [sequence], rw_tests()}, 70: {ro_full, [], ro_full_search_tests()}, 71: {ro_limited, [], ro_limited_search_tests()}, 72: {params_limited_infinity, [], rw_tests()}, 73: {ro_no, [sequence], ro_no_search_tests()}, 74: {ldap_only, [], ldap_only_tests()}]. 75: 76: rw_tests() -> 77: [ 78: update_own_card, 79: cant_update_own_card_with_invalid_field, 80: can_update_own_card_with_emoji_in_nickname, 81: can_update_own_card_with_unicode_in_address 82: ]. 83: 84: ro_full_search_tests() -> 85: [retrieve_own_card, 86: user_doesnt_exist, 87: update_other_card, 88: retrieve_others_card, 89: vcard_service_discovery, 90: server_vcard, 91: directory_service_vcard, 92: request_search_fields, 93: search_open, 94: search_empty, 95: search_some, 96: search_some_many_fields, 97: search_wildcard, 98: search_rsm_count, 99: search_rsm_forward, 100: search_rsm_backward, 101: search_rsm_pages, 102: search_errors 103: ]. 104: 105: ro_limited_search_tests() -> 106: [search_open_limited, 107: search_some_limited, 108: search_in_service_discovery]. 109: 110: ro_no_search_tests() -> 111: [search_not_allowed, 112: search_not_in_service_discovery]. 113: 114: ldap_only_tests() -> 115: [search_some_many_fields_with_or_operator, 116: user_doesnt_exist, 117: return_photo_inserted_as_binary_by_3rd_party_service]. 118: 119: suite() -> 120: require_rpc_nodes([mim]) ++ escalus:suite(). 121: 122: %%-------------------------------------------------------------------- 123: %% Init & teardown 124: %%-------------------------------------------------------------------- 125: 126: init_per_suite(Config) -> 127: NewConfig = escalus:init_per_suite(Config), 128: NewConfig1 = vcard_config(NewConfig), 129: NewConfig2 = prepare_vcard_module(NewConfig1), 130: AliceAndBob = escalus_users:get_users([alice, bob]), 131: Spec = escalus_users:get_userspec(Config, alice), 132: Addr = proplists:get_value(host, Spec, <<"localhost">>), 133: SecDomain = secondary_domain(), 134: BisUsers = [{aliceb, [{username, <<"aliceb">>}, 135: {server, SecDomain}, 136: {host, Addr}, 137: {password, <<"makota">>}]}, 138: {bobb, [{username, <<"bobb">>}, 139: {server, SecDomain}, 140: {host, Addr}, 141: {password, <<"makrolika">>}]}], 142: NewUsers = AliceAndBob ++ BisUsers, 143: escalus:create_users([{escalus_users, NewUsers} | NewConfig2], NewUsers). 144: 145: vcard_config(Config) -> 146: Domain = domain(), 147: [{all_vcards, get_all_vcards()}, 148: {server_vcards, get_server_vcards()}, 149: {directory_jid, <<"vjud.", Domain/binary>>} 150: ] ++ Config. 151: 152: end_per_suite(Config) -> 153: restore_vcard_module(Config), 154: Who = ?config(escalus_users, Config), 155: NewConfig = escalus:delete_users(Config, Who), 156: escalus:end_per_suite(NewConfig). 157: 158: init_per_group(Group, Config) when Group == rw; Group == params_limited_infinity -> 159: restart_vcard_mod(Config, Group), 160: case vcard_helper:is_vcard_ldap() of 161: true -> 162: {skip, ldap_vcard_is_readonly}; 163: _ -> 164: Config 165: end; 166: init_per_group(ldap_only, Config) -> 167: VCardConfig = ?config(mod_vcard_opts, Config), 168: case maps:get(backend, VCardConfig) of 169: ldap -> 170: Config1 = restart_and_prepare_vcard(ldap_only, Config), 171: insert_alice_photo(Config1); 172: _ -> 173: {skip, "this group is only for ldap vCard backend"} 174: end; 175: init_per_group(GroupName, Config) -> 176: restart_and_prepare_vcard(GroupName, Config). 177: 178: end_per_group(_, Config) -> 179: stop_vcard_mod(Config), 180: Config. 181: 182: init_per_testcase(CaseName, Config) -> 183: escalus:init_per_testcase(CaseName, Config). 184: 185: end_per_testcase(CaseName, Config) -> 186: escalus:end_per_testcase(CaseName, Config). 187: 188: restart_and_prepare_vcard(GroupName, Config) -> 189: restart_vcard_mod(Config, GroupName), 190: prepare_vcards(Config). 191: 192: %%-------------------------------------------------------------------- 193: %% XEP-0054: vcard-temp Test cases 194: %%-------------------------------------------------------------------- 195: 196: update_own_card(Config) -> 197: escalus:story( 198: Config, [{alice, 1}], 199: fun(Client1) -> 200: %% set some initial value different from the actual test data 201: %% so we know it really got updated and wasn't just old data 202: Client1Fields = [{<<"FN">>, <<"Old name">>}], 203: Client1SetResultStanza 204: = escalus:send_and_wait(Client1, 205: escalus_stanza:vcard_update(Client1Fields)), 206: escalus:assert(is_iq_result, Client1SetResultStanza), 207: Client1GetResultStanza 208: = escalus:send_and_wait(Client1, escalus_stanza:vcard_request()), 209: <<"Old name">> 210: = stanza_get_vcard_field_cdata(Client1GetResultStanza, <<"FN">>) 211: end). 212: 213: cant_update_own_card_with_invalid_field(Config) -> 214: escalus:story( 215: Config, [{alice, 1}], 216: fun(Client1) -> 217: %% One of "Non-character code points". Banned in stringrep but still valid UTF. 218: Client1Fields = [{<<"FN">>, <<243,191,191,190>>}], 219: Client1SetResultStanza 220: = escalus:send_and_wait(Client1, 221: escalus_stanza:vcard_update(Client1Fields)), 222: escalus:assert(is_iq_error, Client1SetResultStanza) 223: end). 224: 225: can_update_own_card_with_emoji_in_nickname(Config) -> 226: escalus:story( 227: Config, [{alice, 1}], 228: fun(Client1) -> 229: NickWithEmoji = <<"Jan 😊"/utf8>>, 230: Client1Fields = [{<<"NICKNAME">>, NickWithEmoji}], 231: Client1SetResultStanza 232: = escalus:send_and_wait(Client1, 233: escalus_stanza:vcard_update(Client1Fields)), 234: escalus:assert(is_iq_result, Client1SetResultStanza), 235: Client1GetResultStanza 236: = escalus:send_and_wait(Client1, escalus_stanza:vcard_request()), 237: NickWithEmoji = stanza_get_vcard_field_cdata(Client1GetResultStanza, <<"NICKNAME">>) 238: end). 239: 240: can_update_own_card_with_unicode_in_address(Config) -> 241: escalus:story( 242: Config, [{alice, 1}], 243: fun(Client1) -> 244: Locality = get_utf8_city(), 245: Client1Fields = [{<<"ADDR">>, [{<<"LOCALITY">>, get_utf8_city()}]}], 246: Client1SetResultStanza 247: = escalus:send_and_wait(Client1, 248: escalus_stanza:vcard_update(Client1Fields)), 249: escalus:assert(is_iq_result, Client1SetResultStanza), 250: Client1GetResultStanza 251: = escalus:send_and_wait(Client1, escalus_stanza:vcard_request()), 252: Addr = stanza_get_vcard_field(Client1GetResultStanza, <<"ADDR">>), 253: Locality = ?EL_CD(Addr, <<"LOCALITY">>) 254: end). 255: 256: retrieve_own_card(Config) -> 257: escalus:story( 258: Config, [{alice, 1}], 259: fun(Client) -> 260: Res = escalus:send_and_wait(Client, 261: escalus_stanza:vcard_request()), 262: JID = escalus_utils:jid_to_lower(escalus_client:short_jid(Client)), 263: ClientVCardTups = get_user_vcard(JID, Config), 264: check_vcard(ClientVCardTups, Res) 265: end). 266: 267: 268: 269: %% If no vCard exists, the server MUST return a stanza error 270: %% (which SHOULD be <item-not-found/>) or an IQ-result 271: %% containing an empty <vCard/> element. 272: %% We return <item-not-found/> 273: user_doesnt_exist(Config) -> 274: escalus:story( 275: Config, [{alice, 1}], 276: fun(Client) -> 277: Domain = domain(), 278: BadJID = <<"nonexistent@", Domain/binary>>, 279: Res = escalus:send_and_wait(Client, 280: escalus_stanza:vcard_request(BadJID)), 281: case 282: escalus_pred:is_error(<<"cancel">>, 283: <<"item-not-found">>, 284: Res) of 285: true -> 286: ok; 287: _ -> 288: [] = Res#xmlel.children, 289: ct:comment("empty result instead of error") 290: end 291: end). 292: 293: update_other_card(Config) -> 294: escalus:story( 295: Config, [{alice, 1}, {bob, 1}], 296: fun(Client, OtherClient) -> 297: JID = escalus_utils:jid_to_lower(escalus_client:short_jid(Client)), 298: Fields = [{<<"FN">>, <<"New name">>}], 299: Res = escalus:send_and_wait(OtherClient, 300: escalus_stanza:vcard_update(JID, Fields)), 301: 302: %% auth forbidden is also allowed 303: escalus:assert(is_error, [<<"cancel">>, 304: <<"not-allowed">>], Res), 305: 306: %% check that nothing was changed 307: Res2 = escalus:send_and_wait(Client, 308: escalus_stanza:vcard_request()), 309: ClientVCardTups = get_user_vcard(JID, Config), 310: check_vcard(ClientVCardTups, Res2) 311: end). 312: 313: retrieve_others_card(Config) -> 314: escalus:story( 315: Config, [{alice, 1}, {bob, 1}], 316: fun(Client, OtherClient) -> 317: OtherJID = escalus_utils:jid_to_lower(escalus_client:short_jid(OtherClient)), 318: Res = escalus:send_and_wait(Client, 319: escalus_stanza:vcard_request(OtherJID)), 320: OtherClientVCardTups = get_user_vcard(OtherJID, Config), 321: check_vcard(OtherClientVCardTups, Res), 322: 323: %% In accordance with XMPP Core [5], a compliant server MUST 324: %% respond on behalf of the requestor and not forward the IQ to 325: %% the requestee's connected resource. 326: 327: Res2 = (catch escalus:wait_for_stanza(OtherClient)), 328: escalus:assert(stanza_timeout, Res2) 329: end). 330: 331: server_vcard(Config) -> 332: escalus:story( 333: Config, [{alice, 1}], 334: fun(Client) -> 335: Domain = domain(), 336: Res = escalus:send_and_wait(Client, 337: escalus_stanza:vcard_request(Domain)), 338: ServerVCardTups = get_server_vcard(Domain, Config), 339: check_vcard(ServerVCardTups, Res) 340: end). 341: 342: directory_service_vcard(Config) -> 343: escalus:story( 344: Config, [{alice, 1}], 345: fun(Client) -> 346: DirJID = ?config(directory_jid, Config), 347: Res = escalus:send_and_wait(Client, 348: escalus_stanza:vcard_request(DirJID)), 349: DirVCardTups = get_server_vcard(DirJID, Config), 350: check_vcard(DirVCardTups, Res) 351: end). 352: 353: vcard_service_discovery(Config) -> 354: escalus:story( 355: Config, [{alice, 1}], 356: fun(Client) -> 357: Domain = domain(), 358: Res = escalus:send_and_wait(Client, 359: escalus_stanza:disco_info(Domain)), 360: escalus:assert(is_iq_result, Res), 361: escalus:assert(has_feature, [<<"vcard-temp">>], Res) 362: end). 363: 364: %%-------------------------------------------------------------------- 365: %% XEP-0055 jabber:iq:search User Directory service Test cases 366: %% 367: %%-------------------------------------------------------------------- 368: 369: %% all.search.domain 370: 371: request_search_fields(Config) -> 372: escalus:story( 373: Config, [{alice, 1}], 374: fun(Client) -> 375: DirJID = ?config(directory_jid, Config), 376: Res = escalus:send_and_wait(Client, 377: escalus_stanza:search_fields_iq(DirJID)), 378: escalus:assert(is_iq_result, Res), 379: Result = ?EL(Res, <<"query">>), 380: XData = ?EL(Result, <<"x">>), 381: #xmlel{ children = XChildren } = XData, 382: FieldTups = field_tuples(XChildren), 383: true = lists:member({<<"text-single">>, 384: get_user_search_field(), <<"User">>}, 385: FieldTups), 386: true = lists:member({<<"text-single">>, 387: get_full_name_search_field(), 388: <<"Full Name">>}, 389: FieldTups) 390: end). 391: 392: search_open(Config) -> 393: escalus:story( 394: Config, [{alice, 1}], 395: fun(Client) -> 396: DirJID = ?config(directory_jid, Config), 397: Fields = [#xmlel{ name = <<"field">> }], 398: Res = escalus:send_and_wait(Client, 399: escalus_stanza:search_iq(DirJID, Fields)), 400: escalus:assert(is_iq_result, Res), 401: 402: %% Basically test that the right values exist 403: %% and map to the right column headings 404: ItemTups = search_result_item_tuples(Res), 405: ExpectedItemTups = [], 406: list_unordered_key_match(ExpectedItemTups, ItemTups) 407: end). 408: 409: search_empty(Config) -> 410: escalus:story( 411: Config, [{alice, 1}], 412: fun(Client) -> 413: DirJID = ?config(directory_jid, Config), 414: Fields = [{<<"fn">>, <<"nobody">>}], 415: Res = escalus:send_and_wait(Client, 416: escalus_stanza:search_iq(DirJID, 417: escalus_stanza:search_fields(Fields))), 418: escalus:assert(is_iq_result, Res), 419: [] = search_result_item_tuples(Res) 420: end). 421: 422: search_some(Config) -> 423: escalus:story( 424: Config, [{bob, 1}], 425: fun(Client) -> 426: DirJID = ?config(directory_jid, Config), 427: MoscowRUBin = get_utf8_city(), 428: Fields = [{get_locality_search_field(), MoscowRUBin}], 429: Res = escalus:send_and_wait(Client, 430: escalus_stanza:search_iq(DirJID, 431: escalus_stanza:search_fields(Fields))), 432: escalus:assert(is_iq_result, Res), 433: 434: %% Basically test that the right values exist 435: %% and map to the right column headings 436: Domain = domain(), 437: AliceJID = <<"alice@", Domain/binary>>, 438: 439: %% Extra check 440: _Result = escalus:send_and_wait(Client, escalus_stanza:vcard_request(AliceJID)), 441: 442: [{AliceJID, ItemTups}] = search_result_item_tuples(Res), 443: {_, _, <<"City">>, MoscowRUBin} = lists:keyfind(<<"City">>, 3, ItemTups) 444: end). 445: 446: search_wildcard(Config) -> 447: escalus:story( 448: Config, [{bob, 1}], 449: fun(Client) -> 450: Domain = ct:get_config({hosts, mim, secondary_domain}), 451: DirJID = <<"vjud.", Domain/binary>>, 452: Fields = [{get_full_name_search_field(), 453: <<"Doe*">>}], 454: Res = escalus:send_and_wait(Client, 455: escalus_stanza:search_iq(DirJID, 456: escalus_stanza:search_fields(Fields))), 457: escalus:assert(is_iq_result, Res), 458: 459: ItemTups = search_result_item_tuples(Res), 460: ExpectedItemTups = get_search_results(Config, 461: [<<"bobb@", Domain/binary>>, 462: <<"aliceb@", Domain/binary>>]), 463: case vcard_helper:is_vcard_ldap() of 464: true -> 465: 3 = length(ItemTups); 466: _ -> 467: list_unordered_key_match2(ExpectedItemTups, ItemTups) 468: end 469: end). 470: 471: search_rsm_pages(Config) -> 472: escalus:story( 473: Config, [{bob, 1}], 474: fun(Client) -> 475: Domain = ct:get_config({hosts, mim, secondary_domain}), 476: DirJID = <<"vjud.", Domain/binary>>, 477: Fields = [{get_full_name_search_field(), 478: <<"Doe*">>}], 479: Iq1 = escalus_stanza:search_iq( 480: DirJID, 481: escalus_stanza:search_fields(Fields) 482: ), 483: Iq2 = append_to_query( 484: Iq1, 485: #xmlel{name = <<"set">>, 486: attrs = [{<<"xmlns">>, ?NS_RSM}], 487: children = 488: [#xmlel{name = "max", 489: children = 490: [#xmlcdata{content = <<"1">>}] 491: } 492: ]} 493: ), 494: 495: Res1 = escalus:send_and_wait(Client, Iq2), 496: escalus:assert(is_iq_result, Res1), 497: 498: RSMCount1 = get_rsm_count(Res1), 499: case vcard_helper:is_vcard_ldap() of 500: true -> 501: <<"3">> = RSMCount1; 502: false -> 503: <<"2">> = RSMCount1 504: end, 505: ItemTups1 = search_result_item_tuples(Res1), 506: 507: Iq3 = append_to_query( 508: Iq1, 509: #xmlel{name = <<"set">>, 510: attrs = [{<<"xmlns">>, ?NS_RSM}], 511: children = 512: [#xmlel{name = "max", 513: children = 514: [#xmlcdata{content = <<"1">>}] 515: }, 516: #xmlel{name = "index", 517: children = 518: [#xmlcdata{content = <<"1">>}] 519: } 520: ]} 521: ), 522: 523: Res2 = escalus:send_and_wait(Client, Iq3), 524: escalus:assert(is_iq_result, Res2), 525: 526: RSMCount2 = get_rsm_count(Res2), 527: case vcard_helper:is_vcard_ldap() of 528: true -> 529: <<"3">> = RSMCount2; 530: false -> 531: <<"2">> = RSMCount2 532: end, 533: ItemTups2 = search_result_item_tuples(Res2), 534: SecDomain = secondary_domain(), 535: ExpectedItemTups = get_search_results( 536: Config, 537: [<<"bobb@", SecDomain/binary>>, 538: <<"aliceb@", SecDomain/binary>>]), 539: case vcard_helper:is_vcard_ldap() of 540: true -> 541: ignore; 542: _ -> 543: list_unordered_key_match2(ExpectedItemTups, 544: ItemTups1 ++ ItemTups2) 545: end 546: end). 547: 548: search_rsm_forward(Config) -> 549: escalus:story( 550: Config, [{bob, 1}], 551: fun(Client) -> 552: Domain = ct:get_config({hosts, mim, secondary_domain}), 553: DirJID = <<"vjud.", Domain/binary>>, 554: Fields = [{get_full_name_search_field(), 555: <<"Doe*">>}], 556: Iq1 = escalus_stanza:search_iq( 557: DirJID, 558: escalus_stanza:search_fields(Fields) 559: ), 560: Iq2 = append_to_query( 561: Iq1, 562: #xmlel{name = <<"set">>, 563: attrs = [{<<"xmlns">>, ?NS_RSM}], 564: children = 565: [#xmlel{name = "max", 566: children = 567: [#xmlcdata{content = <<"1">>}] 568: } 569: ]} 570: ), 571: 572: Res1 = escalus:send_and_wait(Client, Iq2), 573: escalus:assert(is_iq_result, Res1), 574: 575: RSMCount1 = get_rsm_count(Res1), 576: case vcard_helper:is_vcard_ldap() of 577: true -> 578: <<"3">> = RSMCount1; 579: false -> 580: <<"2">> = RSMCount1 581: end, 582: RSMLast1 = get_rsm_last(Res1), 583: ItemTups1 = search_result_item_tuples(Res1), 584: 585: Iq3 = append_to_query( 586: Iq1, 587: #xmlel{name = <<"set">>, 588: attrs = [{<<"xmlns">>, ?NS_RSM}], 589: children = 590: [#xmlel{name = "max", 591: children = 592: [#xmlcdata{content = <<"1">>}] 593: }, 594: #xmlel{name = "after", 595: children = 596: [#xmlcdata{content = RSMLast1}] 597: } 598: ]} 599: ), 600: 601: Res2 = escalus:send_and_wait(Client, Iq3), 602: escalus:assert(is_iq_result, Res2), 603: 604: RSMCount2 = get_rsm_count(Res2), 605: case vcard_helper:is_vcard_ldap() of 606: true -> 607: <<"3">> = RSMCount2; 608: false -> 609: <<"2">> = RSMCount2 610: end, 611: RSMLast2 = get_rsm_last(Res2), 612: ItemTups2 = search_result_item_tuples(Res2), 613: SecDomain = secondary_domain(), 614: ExpectedItemTups = get_search_results( 615: Config, 616: [<<"bobb@", SecDomain/binary>>, 617: <<"aliceb@", SecDomain/binary>>]), 618: case vcard_helper:is_vcard_ldap() of 619: true -> 620: ignore; 621: _ -> 622: list_unordered_key_match2(ExpectedItemTups, 623: ItemTups1 ++ ItemTups2) 624: end, 625: 626: Iq4 = append_to_query( 627: Iq1, 628: #xmlel{name = <<"set">>, 629: attrs = [{<<"xmlns">>, ?NS_RSM}], 630: children = 631: [#xmlel{name = "max", 632: children = 633: [#xmlcdata{content = <<"1">>}] 634: }, 635: #xmlel{name = "after", 636: children = 637: [#xmlcdata{content = RSMLast2}] 638: } 639: ]} 640: ), 641: 642: Res3 = escalus:send_and_wait(Client, Iq4), 643: escalus:assert(is_iq_result, Res3), 644: 645: RSMCount2 = get_rsm_count(Res3), 646: case vcard_helper:is_vcard_ldap() of 647: true -> 648: <<"3">> = RSMCount2, 649: [_] = search_result_item_tuples(Res3); 650: false -> 651: <<"2">> = RSMCount2, 652: [] = search_result_item_tuples(Res3) 653: end 654: end). 655: 656: search_rsm_backward(Config) -> 657: escalus:story( 658: Config, [{bob, 1}], 659: fun(Client) -> 660: Domain = ct:get_config({hosts, mim, secondary_domain}), 661: DirJID = <<"vjud.", Domain/binary>>, 662: Fields = [{get_full_name_search_field(), 663: <<"Doe*">>}], 664: Iq1 = escalus_stanza:search_iq( 665: DirJID, 666: escalus_stanza:search_fields(Fields) 667: ), 668: Iq2 = append_to_query( 669: Iq1, 670: #xmlel{name = <<"set">>, 671: attrs = [{<<"xmlns">>, ?NS_RSM}], 672: children = 673: [#xmlel{name = "max", 674: children = 675: [#xmlcdata{content = <<"1">>}] 676: }, 677: #xmlel{name = "before"} 678: ]} 679: ), 680: 681: Res1 = escalus:send_and_wait(Client, Iq2), 682: escalus:assert(is_iq_result, Res1), 683: 684: RSMCount1 = get_rsm_count(Res1), 685: case vcard_helper:is_vcard_ldap() of 686: true -> 687: <<"3">> = RSMCount1; 688: false -> 689: <<"2">> = RSMCount1 690: end, 691: RSMFirst1 = get_rsm_first(Res1), 692: ItemTups1 = search_result_item_tuples(Res1), 693: 694: Iq3 = append_to_query( 695: Iq1, 696: #xmlel{name = <<"set">>, 697: attrs = [{<<"xmlns">>, ?NS_RSM}], 698: children = 699: [#xmlel{name = "max", 700: children = 701: [#xmlcdata{content = <<"1">>}] 702: }, 703: #xmlel{name = "before", 704: children = 705: [#xmlcdata{content = RSMFirst1}] 706: } 707: ]} 708: ), 709: 710: Res2 = escalus:send_and_wait(Client, Iq3), 711: escalus:assert(is_iq_result, Res2), 712: 713: RSMCount2 = get_rsm_count(Res2), 714: case vcard_helper:is_vcard_ldap() of 715: true -> 716: <<"3">> = RSMCount2; 717: false -> 718: <<"2">> = RSMCount2 719: end, 720: RSMFirst2 = get_rsm_first(Res2), 721: ItemTups2 = search_result_item_tuples(Res2), 722: SecDomain = secondary_domain(), 723: ExpectedItemTups = get_search_results( 724: Config, 725: [<<"bobb@", SecDomain/binary>>, 726: <<"aliceb@", SecDomain/binary>>]), 727: case vcard_helper:is_vcard_ldap() of 728: true -> 729: ignore; 730: _ -> 731: list_unordered_key_match2(ExpectedItemTups, 732: ItemTups1 ++ ItemTups2) 733: end, 734: 735: Iq4 = append_to_query( 736: Iq1, 737: #xmlel{name = <<"set">>, 738: attrs = [{<<"xmlns">>, ?NS_RSM}], 739: children = 740: [#xmlel{name = "max", 741: children = 742: [#xmlcdata{content = <<"1">>}] 743: }, 744: #xmlel{name = "before", 745: children = 746: [#xmlcdata{content = RSMFirst2}] 747: } 748: ]} 749: ), 750: 751: Res3 = escalus:send_and_wait(Client, Iq4), 752: escalus:assert(is_iq_result, Res3), 753: 754: RSMCount2 = get_rsm_count(Res3), 755: case vcard_helper:is_vcard_ldap() of 756: true -> 757: <<"3">> = RSMCount2, 758: [_] = search_result_item_tuples(Res3); 759: false -> 760: <<"2">> = RSMCount2, 761: [] = search_result_item_tuples(Res3) 762: end 763: end). 764: 765: search_rsm_count(Config) -> 766: escalus:story( 767: Config, [{bob, 1}], 768: fun(Client) -> 769: Domain = ct:get_config({hosts, mim, secondary_domain}), 770: DirJID = <<"vjud.", Domain/binary>>, 771: Fields = [{get_full_name_search_field(), 772: <<"Doe*">>}], 773: Iq1 = escalus_stanza:search_iq( 774: DirJID, 775: escalus_stanza:search_fields(Fields) 776: ), 777: Iq2 = append_to_query( 778: Iq1, 779: #xmlel{name = <<"set">>, 780: attrs = [{<<"xmlns">>, ?NS_RSM}], 781: children = 782: [#xmlel{name = "max", 783: children = 784: [#xmlcdata{content = <<"0">>}] 785: } 786: ]} 787: ), 788: 789: Res = escalus:send_and_wait(Client, Iq2), 790: escalus:assert(is_iq_result, Res), 791: 792: ItemTups = search_result_item_tuples(Res), 793: 0 = length(ItemTups), 794: 795: RSMCount = get_rsm_count(Res), 796: case vcard_helper:is_vcard_ldap() of 797: true -> 798: <<"3">> = RSMCount; 799: false -> 800: <<"2">> = RSMCount 801: end 802: end). 803: 804: search_errors(Config) -> 805: escalus:story( 806: Config, [{alice, 1}], 807: fun(Client) -> 808: DirJID = ?config(directory_jid, Config), 809: Fields = [#xmlel{ name = <<"field">> }], 810: Req = escalus_stanza:search_iq(DirJID, Fields), 811: 812: escalus:assert(is_error, [<<"modify">>, <<"bad-request">>], 813: escalus:send_and_wait(Client, form_helper:remove_form_types(Req))), 814: escalus:assert(is_error, [<<"modify">>, <<"bad-request">>], 815: escalus:send_and_wait(Client, form_helper:remove_form_ns(Req))), 816: escalus:assert(is_error, [<<"modify">>, <<"bad-request">>], 817: escalus:send_and_wait(Client, form_helper:remove_forms(Req))) 818: end). 819: 820: get_rsm_count(El) -> 821: exml_query:path(El, [{element, <<"query">>}, 822: {element, <<"set">>}, 823: {element, <<"count">>}, 824: cdata]). 825: 826: get_rsm_first(El) -> 827: exml_query:path(El, [{element, <<"query">>}, 828: {element, <<"set">>}, 829: {element, <<"first">>}, 830: cdata]). 831: 832: get_rsm_last(El) -> 833: exml_query:path(El, [{element, <<"query">>}, 834: {element, <<"set">>}, 835: {element, <<"last">>}, 836: cdata]). 837: 838: append_to_query(#xmlel{name = <<"iq">>, 839: children = IqChildren} = Iq, 840: NewChild) -> 841: Iq#xmlel{ 842: children = 843: lists:map( 844: fun(#xmlel{ 845: name = <<"query">>, 846: children = QueryChildren 847: } = Query) -> 848: Query#xmlel{children = QueryChildren ++ [NewChild]}; 849: (El) -> 850: El 851: end, IqChildren)}. 852: 853: search_some_many_fields(Config) -> 854: escalus:story( 855: Config, [{alice, 1}], 856: fun(Client) -> 857: {Domain, ItemTups} = search_vcard_by_many_fields(Client), 858: SomeJID = <<"aliceb@", Domain/binary>>, 859: [{SomeJID, _JIDsFields}] = ItemTups 860: %{_Start, _Length} = binary:match(SomeJID, <<"@", Server/binary>>) 861: end). 862: 863: search_vcard_by_many_fields(Client) -> 864: Domain = ct:get_config({hosts, mim, secondary_domain}), 865: DirJID = <<"vjud.", Domain/binary>>, 866: 867: Fields = [{get_first_name_search_field(), <<"Alice">>}, 868: {get_last_name_search_field(), <<"Doe">>}], 869: Stanza = escalus_stanza:search_iq(DirJID, 870: escalus_stanza:search_fields(Fields)), 871: Res = escalus:send_and_wait(Client, Stanza), 872: escalus:assert(is_iq_result, Res), 873: ItemTups = search_result_item_tuples(Res), 874: %% exactly one result returned and its JID domain is correct 875: {Domain, ItemTups}. 876: 877: search_some_many_fields_with_or_operator(Config) -> 878: escalus:story(Config, [{alice, 1}], fun(Client) -> 879: {_Domain, ItemTups} = search_vcard_by_many_fields(Client), 880: 3 = length(ItemTups) 881: end). 882: 883: return_photo_inserted_as_binary_by_3rd_party_service(Config) -> 884: %% The PHOTO was inserted by script and not by modifing vCard by the client 885: escalus:story(Config, [{alice, 1}], fun(Alice) -> 886: Res = escalus:send_and_wait(Alice, 887: escalus_stanza:vcard_request()), 888: JID = escalus_utils:jid_to_lower(escalus_client:short_jid(Alice)), 889: 890: Photo = exml_query:path(Res, [{element, <<"vCard">>}, 891: {element, <<"PHOTO">>}, 892: {element, <<"BINVAL">>}, 893: cdata]), 894: 895: ?PHOTO_BASE_64 = Photo, 896: 897: DirJID = ?config(directory_jid, Config), 898: Fields = [{<<"mail">>, <<"alice@mail.example.com">>}], 899: Res2 = escalus:send_and_wait(Alice, 900: escalus_stanza:search_iq(DirJID, 901: escalus_stanza:search_fields(Fields))), 902: [{JID, ItemTups}] = search_result_item_tuples(Res2), 903: 904: {_, <<"PHOTO">>, _, ?PHOTO_BASE_64} = lists:keyfind(<<"PHOTO">>, 2, ItemTups) 905: 906: end). 907: 908: %%------------------------------------ 909: %% @limited.search.domain 910: 911: search_open_limited(Config) -> 912: escalus:story( 913: Config, [{alice, 1}], 914: fun(Client) -> 915: Domain = domain(), 916: DirJID = <<"directory.", Domain/binary>>, 917: Fields = [null], 918: Res = escalus:send_and_wait(Client, 919: escalus_stanza:search_iq(DirJID, 920: escalus_stanza:search_fields(Fields))), 921: escalus:assert(is_iq_result, Res), 922: [] = search_result_item_tuples(Res) 923: end). 924: 925: search_some_limited(Config) -> 926: escalus:story( 927: Config, [{alice, 1}], 928: fun(Client) -> 929: Domain = ct:get_config({hosts, mim, secondary_domain}), 930: DirJID = <<"directory.", Domain/binary>>, 931: Fields = [{get_last_name_search_field(), <<"Doe">>}], 932: Res = escalus:send_and_wait(Client, 933: escalus_stanza:search_iq(DirJID, 934: escalus_stanza:search_fields(Fields))), 935: escalus:assert(is_iq_result, Res), 936: ItemTups = search_result_item_tuples(Res), 937: %% exactly one result returned and its JID domain is correct 938: [{SomeJID, _JIDsFields}] = ItemTups, 939: true = lists:member(SomeJID, [<<"aliceb@", Domain/binary>>, 940: <<"bobb@", Domain/binary>>]) 941: end). 942: 943: 944: %% disco#items to limited.search.domain says directory.limited.search.domain exists 945: %% disco#info to directory.limited.search.domain says it has feature jabber:iq:search 946: %% and an <identity category='directory' type='user'/> 947: %% http://xmpp.org/extensions/xep-0030.html#registrar-reg-identity 948: search_in_service_discovery(Config) -> 949: escalus:story( 950: Config, [{alice, 1}], 951: fun(Client) -> 952: Domain = domain(), 953: DirJID = <<"directory.", Domain/binary>>, 954: 955: %% Item 956: ItemsRes = escalus:send_and_wait(Client, 957: escalus_stanza:disco_items(Domain)), 958: escalus:assert(is_iq_result, ItemsRes), 959: escalus:assert(has_item, [DirJID], ItemsRes), 960: 961: %% Feature 962: InfoRes = escalus:send_and_wait(Client, 963: escalus_stanza:disco_info(DirJID)), 964: escalus:assert(is_iq_result, InfoRes), 965: escalus:assert(has_feature, [?NS_SEARCH], InfoRes), 966: 967: %% Identity 968: escalus:assert(has_identity, [<<"directory">>, 969: <<"user">>], InfoRes) 970: end). 971: 972: %%------------------------------------ 973: %% @no.search.domain 974: 975: search_not_allowed(Config) -> 976: escalus:story( 977: Config, [{alice, 1}], 978: fun(Client) -> 979: DirJID = ?config(directory_jid, Config), 980: Fields = [null], 981: Res = escalus:send_and_wait(Client, 982: escalus_stanza:search_iq(DirJID, 983: escalus_stanza:search_fields(Fields))), 984: escalus:assert(fun(Packet) -> 985: escalus_pred:is_error(<<"cancel">>, <<"service-unavailable">>, Packet) 986: orelse 987: %% A case for dynamic domains 988: escalus_pred:is_error(<<"cancel">>, <<"remote-server-not-found">>, Packet) 989: end, [], Res) 990: end). 991: 992: %% disco#items to no.search.domain doesn't say vjud.no.search.domain exists 993: search_not_in_service_discovery(Config) -> 994: escalus:story( 995: Config, [{alice, 1}], 996: fun(Client) -> 997: DirJID = ?config(directory_jid, Config), 998: %% Item 999: ItemsRes = escalus:send_and_wait(Client, 1000: escalus_stanza:disco_items(domain())), 1001: escalus:assert(is_iq_result, ItemsRes), 1002: escalus:assert(has_no_such_item, [DirJID], ItemsRes) 1003: end). 1004: 1005: %%-------------------------------------------------------------------- 1006: %% Helper functions 1007: %%-------------------------------------------------------------------- 1008: 1009: expected_search_results(Key, Config) -> 1010: {_, ExpectedResults} = 1011: lists:keyfind(expected_results, 1, 1012: escalus_config:get_config(search_data, Config)), 1013: lists:keyfind(Key, 1, ExpectedResults). 1014: 1015: prepare_vcards(Config) -> 1016: AllVCards = ?config(all_vcards, Config), 1017: ModVcardBackend = get_backend(Config), 1018: lists:foreach( 1019: fun({JID, Fields}) -> 1020: case binary:match(JID, <<"@">>) of 1021: nomatch -> 1022: ok; 1023: _ -> 1024: prepare_vcard(ModVcardBackend, JID, Fields) 1025: end 1026: end, AllVCards), 1027: Config. 1028: 1029: get_backend(Config) -> 1030: maps:get(backend, ?config(mod_vcard_opts, Config), mnesia). 1031: 1032: prepare_vcard(ldap, JID, Fields) -> 1033: [User, Server] = binary:split(JID, <<"@">>), 1034: Base = get_ldap_base(Server), 1035: VCardMap = [{<<"NICKNAME">>, <<"%u">>, []}, 1036: {<<"FN">>, <<"%s">>, [<<"displayName">>]}, 1037: {<<"FAMILY">>, <<"%s">>, [<<"sn">>]}, 1038: {<<"GIVEN">>, <<"%s">>, [<<"givenName">>]}, 1039: {<<"MIDDLE">>, <<"%s">>, [<<"initials">>]}, 1040: {<<"ORGNAME">>, <<"%s">>, [<<"o">>]}, 1041: {<<"ORGUNIT">>, <<"%s">>, [<<"ou">>]}, 1042: 1043: %{<<"CTRY">>, <<"%s">>, [<<"c">>]}, 1044: {<<"LOCALITY">>, <<"%s">>, [<<"l">>]}, 1045: {<<"STREET">>, <<"%s">>, [<<"street">>]}, 1046: {<<"REGION">>, <<"%s">>, [<<"st">>]}, 1047: 1048: {<<"PCODE">>, <<"%s">>, [<<"postalCode">>]}, 1049: {<<"TITLE">>, <<"%s">>, [<<"title">>]}, 1050: {<<"URL">>, <<"%s">>, [<<"labeleduri">>]}, 1051: {<<"DESC">>, <<"%s">>, [<<"description">>]}, 1052: {<<"TEL">>, <<"%s">>, [<<"telephoneNumber">>]}, 1053: {<<"NUMBER">>, <<"%s">>, [<<"telephoneNumber">>]}, 1054: 1055: {<<"EMAIL">>, <<"%s">>, [<<"mail">>]}, 1056: {<<"USERID">>, <<"%s">>, [<<"mail">>]}, 1057: %% {<<"BDAY">>, <<"%s">>, [<<"birthDay">>]}, %OpenLDAP doesn't sport it by default 1058: {<<"ROLE">>, <<"%s">>, [<<"employeeType">>]}, 1059: {<<"PHOTO">>, <<"%s">>, [<<"jpegPhoto">>]} 1060: ], 1061: Fun = fun(Field, Val) -> 1062: case vcard_field_to_ldap(VCardMap, Field) of 1063: undefined -> 1064: undefined; 1065: LdapField -> 1066: eldap:mod_replace(binary_to_list(LdapField), [binary_to_list(Val)]) 1067: end 1068: end, 1069: Modificators = convert_vcard_fields(Fields, [], Fun), 1070: Dn = <<"cn=", User/binary, ",", Base/binary>>, 1071: ok = call_ldap(Server, modify, [binary_to_list(Dn), Modificators]); 1072: prepare_vcard(_, JID, Fields) -> 1073: RJID = get_jid_record(JID), 1074: VCard = escalus_stanza:vcard_update(JID, Fields), 1075: vcard_rpc(RJID, VCard). 1076: 1077: insert_alice_photo(Config) -> 1078: User = <<"alice">>, 1079: Domain = domain(), 1080: Base = get_ldap_base(Domain), 1081: Photo = ?PHOTO_BIN, 1082: Modificators = [eldap:mod_replace("jpegPhoto", [binary_to_list(Photo)])], 1083: Dn = <<"cn=", User/binary, ",", Base/binary>>, 1084: ok = call_ldap(Domain, modify, [binary_to_list(Dn), Modificators]), 1085: Config. 1086: 1087: 1088: fields_to_ldap_modificators(_, [], Acc) -> 1089: Acc; 1090: fields_to_ldap_modificators(VcardMap, [{Field, Val} | Rest], Acc) when is_binary(Val) -> 1091: case vcard_field_to_ldap(VcardMap, Field) of 1092: undefined -> 1093: NewAcc = Acc; 1094: LdapField -> 1095: LdapModify = eldap:mod_replace(binary_to_list(LdapField), [binary_to_list(Val)]), 1096: NewAcc = [LdapModify | Acc] 1097: end, 1098: fields_to_ldap_modificators(VcardMap, Rest, NewAcc); 1099: fields_to_ldap_modificators(VcardMap, [{_, Children} | Rest], Acc) when is_list(Children) -> 1100: NewAcc = fields_to_ldap_modificators(VcardMap, Children, Acc), 1101: fields_to_ldap_modificators(VcardMap, Rest, NewAcc). 1102: 1103: vcard_field_to_ldap(Map, Field) -> 1104: case lists:keyfind(Field, 1, Map) of 1105: {Field, _, [LdapField]} -> 1106: LdapField; 1107: _ -> 1108: undefined 1109: end. 1110: 1111: delete_vcards(Config) -> 1112: AllVCards = escalus_config:get_ct({vcard, data, all_search, expected_vcards}), 1113: lists:foreach( 1114: fun({JID, _}) -> 1115: case binary:match(JID, <<"@">>) of 1116: nomatch -> 1117: ok; 1118: _ -> 1119: RJID = get_jid_record(JID), 1120: ok = vcard_rpc(RJID, 1121: escalus_stanza:vcard_update(JID, [])) 1122: end 1123: end, AllVCards), 1124: Config. 1125: 1126: get_jid_record(JID) -> 1127: [User, Server] = binary:split(JID, <<"@">>), 1128: jid:make_bare(User, Server). 1129: 1130: vcard_rpc(JID, Stanza) -> 1131: Res = rpc(mim(), ejabberd_router, route, [JID, JID, Stanza]), 1132: case Res of 1133: #{stanza := #{type := <<"set">>}} -> 1134: ok; 1135: _ -> 1136: %% Something is wrong with the IQ handler 1137: %% Let's print enough info 1138: ct:pal("jid=~p~n stanza=~p~n result=~p~n", [JID, Stanza, Res]), 1139: mongoose_helper:print_debug_info_for_module(mod_vcard), 1140: ct:fail(vcard_rpc_failed) 1141: end. 1142: 1143: restart_vcard_mod(Config, ro_limited) -> 1144: restart_mod(params_limited(Config)); 1145: restart_vcard_mod(Config, params_limited_infinity) -> 1146: restart_mod(params_limited_infinity(Config)); 1147: restart_vcard_mod(Config, ro_no) -> 1148: restart_mod(params_no(Config)); 1149: restart_vcard_mod(Config, ldap_only) -> 1150: restart_mod(params_ldap_only(Config)); 1151: restart_vcard_mod(Config, _GN) -> 1152: restart_mod(params_all(Config)). 1153: 1154: restart_mod(Params) -> 1155: [restart_mod(HostType, Params) || HostType <- host_types()], 1156: ok. 1157: 1158: restart_mod(HostType, Params) -> 1159: dynamic_modules:restart(HostType, mod_vcard, Params). 1160: 1161: prepare_vcard_module(Config) -> 1162: %% Keep the old config, so we can undo our changes, once finished testing 1163: Config1 = dynamic_modules:save_modules(host_types(), Config), 1164: %% Get a list of options, we can use as a prototype to start new modules 1165: VCardOpts = dynamic_modules:get_saved_config(host_type(), mod_vcard, Config1), 1166: [{mod_vcard_opts, VCardOpts} | Config1]. 1167: 1168: restore_vcard_module(Config) -> 1169: dynamic_modules:restore_modules(Config). 1170: 1171: stop_vcard_mod(_Config) -> 1172: [dynamic_modules:stop(HostType, mod_vcard) || HostType <- host_types()], 1173: ok. 1174: 1175: params_all(Config) -> 1176: add_backend_param(#{}, ?config(mod_vcard_opts, Config)). 1177: 1178: params_limited(Config) -> 1179: add_backend_param(#{matches => 1, 1180: host => subhost_pattern("directory.@HOST@")}, 1181: ?config(mod_vcard_opts, Config)). 1182: 1183: params_limited_infinity(Config) -> 1184: add_backend_param(#{matches => infinity, 1185: host => subhost_pattern("directory.@HOST@")}, 1186: ?config(mod_vcard_opts, Config)). 1187: 1188: params_no(Config) -> 1189: add_backend_param(#{search => false, 1190: host => subhost_pattern("vjud.@HOST@")}, 1191: ?config(mod_vcard_opts, Config)). 1192: 1193: params_ldap_only(Config) -> 1194: Reported = [{<<"Full Name">>, <<"FN">>}, 1195: {<<"Given Name">>, <<"FIRST">>}, 1196: {<<"Middle Name">>, <<"MIDDLE">>}, 1197: {<<"Family Name">>, <<"LAST">>}, 1198: {<<"Nickname">>, <<"NICK">>}, 1199: {<<"Birthday">>, <<"BDAY">>}, 1200: {<<"Country">>, <<"CTRY">>}, 1201: {<<"City">>, <<"LOCALITY">>}, 1202: {<<"Email">>, <<"EMAIL">>}, 1203: {<<"Organization Name">>, <<"ORGNAME">>}, 1204: {<<"Organization Unit">>, <<"ORGUNIT">>}, 1205: {<<"Photo">>, <<"PHOTO">>}], 1206: add_backend_param(#{ldap => #{search_operator => 'or', 1207: binary_search_fields => [<<"PHOTO">>], 1208: search_reported => Reported}}, 1209: ?config(mod_vcard_opts, Config)). 1210: 1211: add_backend_param(Opts = #{ldap := LDAPOpts}, 1212: CurrentVCardConfig = #{backend := ldap, ldap := CurrentLDAPOpts}) -> 1213: NewLDAPOpts = maps:merge(CurrentLDAPOpts, LDAPOpts), 1214: maps:merge(CurrentVCardConfig, Opts#{ldap => NewLDAPOpts}); 1215: add_backend_param(Opts, CurrentVCardConfig) -> 1216: maps:merge(CurrentVCardConfig, Opts). 1217: 1218: %%---------------------- 1219: %% xmlel shortcuts 1220: stanza_get_vcard_field(Stanza, FieldName) -> 1221: VCard = ?EL(Stanza, <<"vCard">>), 1222: ?EL(VCard, FieldName). 1223: 1224: stanza_get_vcard_field_cdata(Stanza, FieldName) -> 1225: VCard = ?EL(Stanza, <<"vCard">>), 1226: ?EL_CD(VCard, FieldName). 1227: 1228: %%--------------------- 1229: %% test helpers 1230: 1231: %% 1232: %% -> [{Type, Var, Label}] 1233: %% 1234: field_tuples([]) -> 1235: []; 1236: field_tuples([#xmlel{name = <<"field">>, 1237: attrs=Attrs, 1238: children=_Children} = El| Rest]) -> 1239: {<<"type">>, Type} = lists:keyfind(<<"type">>, 1, Attrs), 1240: {<<"var">>, Var} = lists:keyfind(<<"var">>, 1, Attrs), 1241: {<<"label">>, Label} = lists:keyfind(<<"label">>, 1, Attrs), 1242: case ?EL_CD(El, <<"value">>) of 1243: undefined -> 1244: [{Type, Var, Label}|field_tuples(Rest)]; 1245: ValCData -> 1246: [{Type, Var, Label, ValCData}|field_tuples(Rest)] 1247: end; 1248: field_tuples([_SomeOtherEl|Rest]) -> 1249: field_tuples(Rest). 1250: 1251: 1252: %% 1253: %% -> [{Type, Var, Label, ValueCData}] 1254: %% 1255: %% This is naiive and expensive LOL! 1256: item_field_tuples(_, []) -> 1257: []; 1258: item_field_tuples(ReportedFieldTups, 1259: [#xmlel{name = <<"field">>, 1260: attrs=Attrs, 1261: children=_Children} = El| Rest]) -> 1262: {<<"var">>, Var} = lists:keyfind(<<"var">>, 1, Attrs), 1263: {Type, Var, Label} = lists:keyfind(Var, 2, ReportedFieldTups), 1264: [{Type, Var, Label, ?EL_CD(El, <<"value">>)} 1265: | item_field_tuples(ReportedFieldTups, Rest)]; 1266: 1267: item_field_tuples(ReportedFieldTups, [_SomeOtherEl|Rest]) -> 1268: item_field_tuples(ReportedFieldTups, Rest). 1269: 1270: 1271: %% 1272: %% -> [{JID, [ItemFieldTups]}] 1273: %% 1274: %% Finds the JID and maps fields to their labels and types 1275: %% 1276: item_tuples(_, []) -> 1277: []; 1278: item_tuples(ReportedFieldTups, [#xmlel{name = <<"item">>, 1279: children = Children} | Rest]) -> 1280: ItemFieldTups = item_field_tuples(ReportedFieldTups, Children), 1281: {_, _, _, JID} = lists:keyfind(<<"jid">>, 2, ItemFieldTups), 1282: [{JID, ItemFieldTups}|item_tuples(ReportedFieldTups, Rest)]; 1283: item_tuples(ReportedFieldTypes, [_SomeOtherChild | Rest]) -> 1284: item_tuples(ReportedFieldTypes, Rest). 1285: 1286: 1287: %% This tests that at least the values in the ExpectedVCardTups are in the 1288: %% VCardUnderTest. 1289: %% Any extra values in the vcard are ignored by this function and should be 1290: %% checked or rejected elsewhere. 1291: %% crash means fail, return means success. 1292: check_vcard(ExpectedVCardTups, Stanza) -> 1293: escalus_pred:is_iq(<<"result">>, Stanza), 1294: VCardUnderTest = ?EL(Stanza, <<"vCard">>), 1295: check_xml_element(ExpectedVCardTups, VCardUnderTest). 1296: 1297: 1298: check_xml_element([], _ElUnderTest) -> 1299: ok; %% just return true to be consistent with other clauses. 1300: check_xml_element([{ExpdFieldName, ExpdChildren}|Rest], ElUnderTest) 1301: when is_list(ExpdChildren) -> 1302: check_xml_element(ExpdChildren, ?EL(ElUnderTest, ExpdFieldName)), 1303: check_xml_element(Rest, ElUnderTest); 1304: check_xml_element([{ExpdFieldName, ExpdCData}|Rest], ElUnderTest) -> 1305: case ?EL_CD(ElUnderTest, ExpdFieldName) of 1306: ExpdCData -> 1307: check_xml_element(Rest, ElUnderTest); 1308: Else -> 1309: ct:fail("Expected ~p got ~p~n", [ExpdCData, Else]) 1310: end. 1311: 1312: %% Checks that the elements of two lists with matching keys are equal 1313: %% while the order of the elements does not matter. 1314: %% Returning means success. Crashing via ct:fail means failure. 1315: %% Prints the lists in the ct:fail Result term. 1316: list_unordered_key_match(Expected, Actual) -> 1317: case length(Actual) of 1318: ActualLength when ActualLength == length(Expected) -> 1319: list_unordered_key_match2(Expected, Actual); 1320: ActualLength -> 1321: ct:fail("Expected size ~p, actual size ~p~nExpected: ~p~nActual: ~p", 1322: [length(Expected), ActualLength, Expected, Actual]) 1323: end. 1324: 1325: list_unordered_key_match2([], _) -> 1326: ok; 1327: list_unordered_key_match2([{User, ExpectedTup} | _Rest], ActualTuples) -> 1328: 1329: case lists:keyfind(User, 1, ActualTuples) of 1330: {User, ReceivedTuple} -> 1331: verify_tuples(ReceivedTuple, ExpectedTup); 1332: _ -> 1333: ct:fail("can't find user ~p in received results: ~p", 1334: [User, ActualTuples]) 1335: 1336: end. 1337: 1338: verify_tuples(Received, Expected) -> 1339: Fun = fun({_, _, Name, Value} = ExpectedItem) -> 1340: case lists:keyfind(Name, 3, Received) of 1341: {_, _, Name, Value} -> 1342: true; 1343: _ -> 1344: ct:fail("can't find item ~p in received items:~p", [ExpectedItem, Received]) 1345: end 1346: end, 1347: lists:all(Fun, Expected). 1348: 1349: 1350: search_result_item_tuples(Stanza) -> 1351: Result = ?EL(Stanza, <<"query">>), 1352: XData = ?EL(Result, <<"x">>), 1353: #xmlel{ attrs = _XAttrs, 1354: children = XChildren } = XData, 1355: Reported = ?EL(XData, <<"reported">>), 1356: ReportedFieldTups = field_tuples(Reported#xmlel.children), 1357: _ItemTups = item_tuples(ReportedFieldTups, XChildren). 1358: 1359: get_all_vcards() -> 1360: Domain = domain(), 1361: SecDomain = secondary_domain(), 1362: [{<<"alice@", Domain/binary>>, 1363: [{<<"NICKNAME">>, <<"alice">>}, 1364: {<<"FN">>, <<"Wonderland, Alice">>}, 1365: {<<"TITLE">>, <<"Executive Director">>}, 1366: {<<"ROLE">>, <<"Patron Saint">>}, 1367: {<<"DESC">>, <<"active">>}, 1368: {<<"URL">>, <<"http://john.doe/">>}, 1369: %{<<"PHOTO">>, crypto:strong_rand_bytes(10)}, 1370: {<<"EMAIL">>, 1371: [{<<"USERID">>, <<"alice@mail.example.com">>}]}, 1372: {<<"N">>, 1373: [{<<"FAMILY">>, <<"Wonderland">>}, 1374: {<<"GIVEN">>, <<"Alice">>}, 1375: {<<"MIDDLE">>, <<"I.N.">>}]}, 1376: {<<"ADR">>, 1377: [{<<"STREET">>, <<"1899 Wynkoop Street">>}, 1378: {<<"LOCALITY">>, get_utf8_city()}, 1379: {<<"REGION">>, <<"CO">>}, 1380: {<<"PCODE">>, <<"91210">>} 1381: ] ++ maybe_add_ctry()}, 1382: {<<"TEL">>, 1383: [{<<"NUMBER">>, <<"+1 512 305 0280">>}]}, 1384: {<<"ORG">>, 1385: [{<<"ORGNAME">>, <<"The world">>}, 1386: {<<"ORGUNIT">>, <<"People">>}]} 1387: ] ++ maybe_add_bday() ++ maybe_add_jabberd_id(<<"alice@", Domain/binary>>)}, 1388: {<<"bob@", Domain/binary>>, 1389: [{<<"NICKNAME">>, <<"bob">>}, 1390: {<<"FN">>, <<"Doe, Bob">>}, 1391: {<<"ADR">>, [{<<"STREET">>, 1392: <<208, 146, 32, 208, 161, 208, 190, 208, 178, 1393: 208, 181, 209, 130, 209, 129, 208, 186, 208, 190, 208, 185, 32, 1394: 208, 160, 208, 190, 209, 129, 209, 129, 208, 184, 208, 184, 44, 1395: 32, 208, 180, 208, 190, 209, 128, 208, 190, 208, 179, 208, 176, 1396: 32, 209, 128, 208, 176, 208, 183, 208, 178, 208, 181, 209, 130, 1397: 208, 178, 208, 187, 209, 143, 208, 181, 209, 130, 209, 129, 209, 1398: 143, 32, 208, 178, 209, 139>>}]} 1399: ] ++ maybe_add_jabberd_id(<<"bob@", Domain/binary>>)}, 1400: {<<"bobb@", SecDomain/binary>>, 1401: [{<<"NICKNAME">>, <<"bobb">>}, 1402: {<<"FN">>, <<"Doe, Bob">>}, 1403: {<<"N">>, 1404: [{<<"FAMILY">>, <<"Doe">>}, 1405: {<<"GIVEN">>, <<"Bob">>}]} 1406: ] ++ maybe_add_jabberd_id(<<"bobb@", SecDomain/binary>>)}, 1407: {<<"aliceb@", SecDomain/binary>>, 1408: [{<<"NICKNAME">>, <<"aliceb">>}, 1409: {<<"FN">>, <<"Doe, Alice">>}, 1410: {<<"N">>, 1411: [{<<"FAMILY">>, <<"Doe">>}, 1412: {<<"GIVEN">>, <<"Alice">>}]} 1413: ] ++ maybe_add_jabberd_id(<<"aliceb@", SecDomain/binary>>)} 1414: ]. 1415: 1416: maybe_add_ctry() -> 1417: maybe_add([{<<"CTRY">>, <<"PL">>}]). 1418: 1419: maybe_add_bday() -> 1420: maybe_add([{<<"BDAY">>, <<"2011-08-06">>}]). 1421: 1422: maybe_add_jabberd_id(JabberId) -> 1423: maybe_add([{<<"JABBERID">>, JabberId}]). 1424: 1425: maybe_add(Elems) -> 1426: case vcard_helper:is_vcard_ldap() of 1427: true -> 1428: []; 1429: _ -> 1430: Elems 1431: end. 1432: 1433: get_server_vcards() -> 1434: [{domain(), 1435: [{<<"FN">>, <<"MongooseIM">>}, 1436: {<<"DESC">>, <<"MongooseIM XMPP Server\nCopyright (c) Erlang Solutions Ltd.">>}]}, 1437: {<<"vjud.", (domain())/binary>>, 1438: [{<<"FN">>, <<"MongooseIM/mod_vcard">>}, 1439: {<<"DESC">>, <<"MongooseIM vCard module\nCopyright (c) Erlang Solutions Ltd.">>}]}]. 1440: 1441: 1442: get_user_vcard(JID, Config) -> 1443: case lists:keyfind(JID, 1, ?config(all_vcards, Config)) of 1444: {JID, ClientVCardTups} -> 1445: ClientVCardTups; 1446: _ -> 1447: ct:fail({get_user_vcard_failed, JID}) 1448: end. 1449: 1450: get_server_vcard(ServerJid, Config) -> 1451: case lists:keyfind(ServerJid, 1, ?config(server_vcards, Config)) of 1452: {ServerJid, VCard} -> 1453: VCard; 1454: _ -> 1455: ct:fail({get_server_vcard_failed, ServerJid}) 1456: end. 1457: 1458: get_search_results(Config, Users) -> 1459: [{User, get_search_result(get_user_vcard(User, Config))} || User <- Users]. 1460: 1461: get_search_result(VCard) -> 1462: convert_vcard_fields(VCard, [], fun vcard_field_to_result/2). 1463: 1464: get_locality_search_field() -> 1465: get_search_field(<<"locality">>, <<"l">>). 1466: 1467: get_user_search_field() -> 1468: get_search_field(<<"user">>, <<"%u">>). 1469: 1470: get_full_name_search_field() -> 1471: get_search_field(<<"fn">>, <<"displayName">>). 1472: 1473: get_last_name_search_field() -> 1474: get_search_field(<<"last">>, <<"sn">>). 1475: 1476: get_first_name_search_field() -> 1477: get_search_field(<<"first">>, <<"givenName">>). 1478: 1479: get_search_field(Default, LDAP) -> 1480: case vcard_helper:is_vcard_ldap() of 1481: true -> 1482: LDAP; 1483: _ -> 1484: Default 1485: end. 1486: 1487: convert_vcard_fields([], Acc, _) -> Acc; 1488: convert_vcard_fields([{_Field, Children} | Rest], Acc, Fun) when is_list(Children) -> 1489: NewAcc = convert_vcard_fields(Children, Acc, Fun), 1490: convert_vcard_fields(Rest, NewAcc, Fun); 1491: convert_vcard_fields([{Field, Value} | Rest], Acc, Fun) -> 1492: NewAcc = case Fun(Field, Value) of 1493: undefined -> 1494: Acc; 1495: Result -> 1496: [Result | Acc] 1497: end, 1498: convert_vcard_fields(Rest, NewAcc, Fun). 1499: 1500: 1501: vcard_field_to_result(Field, Value) -> 1502: case vcard_result_mapping(Field) of 1503: undefined -> 1504: undefined; 1505: Short -> 1506: case lists:keyfind(Short, 2, reported_fields()) of 1507: {Type, Short, Long} -> 1508: {Type, Short, Long, Value}; 1509: _ -> 1510: undefined 1511: end 1512: end. 1513: 1514: reported_fields() -> 1515: [{<<"text-single">>, <<"fn">>, <<"Full Name">>}, 1516: {<<"text-single">>, <<"last">>, <<"Family Name">>}, 1517: {<<"text-single">>, <<"first">>, <<"Name">>}, 1518: {<<"text-single">>, <<"middle">>, <<"Middle Name">>}, 1519: {<<"text-single">>, <<"nick">>, <<"Nickname">>}, 1520: {<<"text-single">>, <<"bday">>, <<"Birthday">>}, 1521: {<<"text-single">>, <<"ctry">>, <<"Country">>}, 1522: {<<"text-single">>, <<"locality">>, <<"City">>}, 1523: {<<"text-single">>, <<"email">>, <<"Email">>}, 1524: {<<"text-single">>, <<"orgname">>, <<"Organization Name">>}, 1525: {<<"text-single">>, <<"orgunit">>, <<"Organization Unit">>} 1526: ] ++ maybe_add_jabberd_field(). 1527: 1528: maybe_add_jabberd_field() -> 1529: case vcard_helper:is_vcard_ldap() of 1530: true -> 1531: []; 1532: _ -> 1533: [{<<"jid-single">>, <<"jid">>, <<"Jabber ID">>}] 1534: end. 1535: 1536: 1537: vcard_result_mapping(<<"NICKNAME">>) -> <<"nick">>; 1538: vcard_result_mapping(<<"FN">>) -> <<"fn">>; 1539: vcard_result_mapping(<<"USERID">>) -> <<"email">>; 1540: vcard_result_mapping(<<"FAMILY">>) -> <<"last">>; 1541: vcard_result_mapping(<<"GIVEN">>) -> <<"first">>; 1542: vcard_result_mapping(<<"MIDDLE">>) -> <<"middle">>; 1543: vcard_result_mapping(<<"LOCALITY">>) -> <<"locality">>; 1544: vcard_result_mapping(<<"CTRY">>) -> <<"ctry">>; 1545: vcard_result_mapping(<<"ORGNAME">>) -> <<"orgname">>; 1546: vcard_result_mapping(<<"ORGUNIT">>) -> <<"orgunit">>; 1547: vcard_result_mapping(<<"JABBERID">>) -> <<"jid">>; 1548: vcard_result_mapping(<<"BDAY">>) -> <<"bday">>; 1549: vcard_result_mapping(_) -> undefined. 1550: 1551: get_utf8_city() -> 1552: %% This is the UTF-8 of Москва 1553: <<208, 156, 208, 190, 209, 129, 208, 186, 208, 178, 208, 176>>. 1554: 1555: secondary_domain() -> 1556: ct:get_config({hosts, mim, secondary_domain}).