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