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