From 4526c80032ebe0ea7eacea2a57d989255a2763b8 Mon Sep 17 00:00:00 2001 From: Harenson Henao Date: Tue, 5 Nov 2019 21:22:53 +0000 Subject: [PATCH] Do not honor privacy settings for emergency calls (#6139) If a user dials to an emergency number and the device, user, account, or carrier has Caller-ID Privacy enabled and/or the user uses *67 feature code to perform the emergency call, disregard the privacy settings and use the configured caller-id for emergency calls. --- applications/callflow/src/cf_route_req.erl | 38 ++++++-- .../callflow/test/cf_route_req_tests.erl | 54 ++++++++++++ .../stepswitch/src/stepswitch_bridge.erl | 28 +++++- .../test/stepswitch_bridge_tests.erl | 86 +++++++++++++++++++ .../include/kapi_offnet_resource.hrl | 1 + 5 files changed, 198 insertions(+), 9 deletions(-) create mode 100644 applications/callflow/test/cf_route_req_tests.erl create mode 100644 applications/stepswitch/test/stepswitch_bridge_tests.erl diff --git a/applications/callflow/src/cf_route_req.erl b/applications/callflow/src/cf_route_req.erl index 42036a54080..e61a7355630 100644 --- a/applications/callflow/src/cf_route_req.erl +++ b/applications/callflow/src/cf_route_req.erl @@ -15,6 +15,11 @@ ,allow_no_match/1 ]). +-ifdef(TEST). +-export([avoid_privacy_if_emergency_call/2 + ]). +-endif. + -include("callflow.hrl"). -define(DEFAULT_METAFLOWS(AccountId) @@ -247,20 +252,23 @@ pre_park_action(Call) -> -spec update_call(kz_json:object(), boolean(), kz_term:ne_binary(), kapps_call:call()) -> kapps_call:call(). update_call(Flow, NoMatch, ControllerQ, Call) -> - Props = [{'cf_flow_id', kz_doc:id(Flow)} - ,{'cf_flow_name', kz_json:get_ne_binary_value(<<"name">>, Flow, kapps_call:request_user(Call))} - ,{'cf_flow', kz_json:get_value(<<"flow">>, Flow)} - ,{'cf_capture_group', kz_json:get_ne_value(<<"capture_group">>, Flow)} - ,{'cf_capture_groups', kz_json:get_value(<<"capture_groups">>, Flow, kz_json:new())} + FlowId = kz_doc:id(Flow), + CaptureGroup = kz_json:get_ne_value(<<"capture_group">>, Flow), + NewFlow = avoid_privacy_if_emergency_call(CaptureGroup, Flow), + Props = [{'cf_flow_id', FlowId} + ,{'cf_flow_name', kz_json:get_ne_binary_value(<<"name">>, NewFlow, kapps_call:request_user(Call))} + ,{'cf_flow', kz_json:get_value(<<"flow">>, NewFlow)} + ,{'cf_capture_group', CaptureGroup} + ,{'cf_capture_groups', kz_json:get_value(<<"capture_groups">>, NewFlow, kz_json:new())} ,{'cf_no_match', NoMatch} - ,{'cf_metaflow', kz_json:get_value(<<"metaflows">>, Flow, ?DEFAULT_METAFLOWS(kapps_call:account_id(Call)))} + ,{'cf_metaflow', kz_json:get_value(<<"metaflows">>, NewFlow, ?DEFAULT_METAFLOWS(kapps_call:account_id(Call)))} ], Updaters = [{fun kapps_call:kvs_store_proplist/2, Props} ,{fun kapps_call:set_controller_queue/2, ControllerQ} ,{fun kapps_call:set_application_name/2, ?APP_NAME} ,{fun kapps_call:set_application_version/2, ?APP_VERSION} - ,{fun kapps_call:insert_custom_channel_var/3, <<"CallFlow-ID">>, kz_doc:id(Flow)} + ,{fun kapps_call:insert_custom_channel_var/3, <<"CallFlow-ID">>, FlowId} ], kapps_call:exec(Updaters, Call). @@ -319,3 +327,19 @@ extract_sip_username(Contact) -> {'match', [Match]} -> Match; _ -> 'undefined' end. + +%%------------------------------------------------------------------------------ +%% @doc If dialing an emergency number (e.g. 911) using the `*67' feature code, then don't honor privacy settings. +%% @end +%%------------------------------------------------------------------------------ +-spec avoid_privacy_if_emergency_call(kz_term:api_binary(), kzd_callflows:doc()) -> kzd_callflows:doc(). +avoid_privacy_if_emergency_call('undefined', Flow) -> + Flow; +avoid_privacy_if_emergency_call(CaptureGroup, Flow) -> + case knm_converters:classify(CaptureGroup) of + <<"emergency">> -> + lager:info("emergency call ongoing, ignoring privacy settings"), + kz_json:set_value([<<"flow">>, <<"data">>, <<"mode">>], <<"none">>, Flow); + _ -> + Flow + end. diff --git a/applications/callflow/test/cf_route_req_tests.erl b/applications/callflow/test/cf_route_req_tests.erl new file mode 100644 index 00000000000..85ae6f0ca54 --- /dev/null +++ b/applications/callflow/test/cf_route_req_tests.erl @@ -0,0 +1,54 @@ +%%%----------------------------------------------------------------------------- +%%% @copyright (C) 2011-2019, 2600Hz +%%% @doc +%%% This Source Code Form is subject to the terms of the Mozilla Public +%%% License, v. 2.0. If a copy of the MPL was not distributed with this +%%% file, You can obtain one at https://mozilla.org/MPL/2.0/. +%%% +%%% @end +%%%----------------------------------------------------------------------------- +-module(cf_route_req_tests). + +-include_lib("eunit/include/eunit.hrl"). + +avoid_privacy_if_emergency_call_test_() -> + EmergencyFlow = kz_json:set_value(<<"capture_group">>, <<"911">>, star_67_callflow()), + NormalFlow = star_67_callflow([<<"+14151234567">>]), + + ModeNoneFlow = kz_json:set_value([<<"flow">>, <<"data">>, <<"mode">>], <<"none">>, EmergencyFlow), + + [{"When dialing emergency number with *67 feature code, ignore privacy settings" + ,check_expected(ModeNoneFlow, EmergencyFlow) + } + ,{"When dialing normal number with *67 feature code, allow privacy settings" + ,check_expected(NormalFlow, NormalFlow) + } + ]. + +check_expected(Expected, Callflow) -> + Resp = cf_route_req:avoid_privacy_if_emergency_call( + kz_json:get_ne_value(<<"capture_group">>, Callflow) + ,Callflow + ), + ?_assert(kz_json:are_equal(Expected, Resp)). + +star_67_callflow() -> + star_67_callflow([]). + +star_67_callflow(Numbers) -> + Flow = kz_json:from_list_recursive([{<<"children">>, []} + ,{<<"data">> + ,[{<<"action">>, <<"full">>} + ,{<<"endpoint_strategy">>, <<"overwrite">>} + ,{<<"mode">>, <<"full">>} + ] + } + ,{<<"module">>, <<"privacy">>} + ]), + Routines = [{fun kzd_callflows:set_featurecode_name/2, <<"privacy[mode=full]">>} + ,{fun kzd_callflows:set_featurecode_number/2, <<"67">>} + ,{fun kzd_callflows:set_flow/2, Flow} + ,{fun kzd_callflows:set_patterns/2, [<<"^\\*67([0-9]*)", $$/integer>>]} + ,{fun kzd_callflows:set_numbers/2, Numbers} + ], + kz_json:exec_first(Routines, kzd_callflows:new()). diff --git a/applications/stepswitch/src/stepswitch_bridge.erl b/applications/stepswitch/src/stepswitch_bridge.erl index 0d0990e34da..2347c21b82d 100644 --- a/applications/stepswitch/src/stepswitch_bridge.erl +++ b/applications/stepswitch/src/stepswitch_bridge.erl @@ -27,6 +27,12 @@ ,code_change/3 ]). +-ifdef(TEST). +-export[avoid_privacy_if_emergency_call/2 + ,contains_emergency_endpoints/1 + ]. +-endif. + -include("stepswitch.hrl"). -include_lib("kazoo_amqp/include/kapi_offnet_resource.hrl"). -include_lib("kazoo_numbers/include/knm_phone_number.hrl"). @@ -422,8 +428,9 @@ build_bridge(#state{endpoints=Endpoints ,{<<"Require-Fail-On-Single-Reject">>, null} ], + NewEndpoints = avoid_privacy_if_emergency_call(IsEmergency, Endpoints), CCVs = kz_json:set_values(AddCCVs ++ RemoveCCVs, ReqCCVs), - FmtEndpoints = stepswitch_util:format_endpoints(Endpoints, Name, Number, OffnetReq), + FmtEndpoints = stepswitch_util:format_endpoints(NewEndpoints, Name, Number, OffnetReq), Realm = kzd_accounts:fetch_realm(AccountId), @@ -578,7 +585,7 @@ is_emergency_endpoint('true') -> 'true'; is_emergency_endpoint('false') -> 'false'; is_emergency_endpoint(Endpoint) -> - is_emergency_endpoint(kz_json:is_true([<<"Custom-Channel-Vars">>, <<"Emergency-Resource">>], Endpoint)). + is_emergency_endpoint(kz_json:is_true([?KEY_CCVS, ?KEY_EMERGENCY_RESOURCE], Endpoint)). -spec bridge_timeout(kapi_offnet_resource:req()) -> kz_term:proplist(). bridge_timeout(OffnetReq) -> @@ -691,3 +698,20 @@ send_deny_emergency_response(OffnetReq, ControlQ) -> get_event_type(CallEvt) -> {Cat, Name} = kz_util:get_event_type(CallEvt), {Cat, Name, kz_call_event:call_id(CallEvt)}. + + +%%------------------------------------------------------------------------------ +%% @doc If it is an emergency call (i.e 911 call) don't honor `privacy' settings. +%% @end +%%------------------------------------------------------------------------------ +-spec avoid_privacy_if_emergency_call(boolean(), stepswitch_resources:endpoints()) -> stepswitch_resources:endpoints(). +avoid_privacy_if_emergency_call('true', Endpoints) -> + lager:info("emergency call ongoing, ignoring privacy settings"), + Values = [{?KEY_PRIVACY_HIDE_NAME, 'false'} + ,{?KEY_PRIVACY_HIDE_NUMBER, 'false'} + ,{?KEY_PRIVACY_METHOD, <<"none">>} + ], + F = fun(E, Acc) -> [kz_json:set_values(Values, E) | Acc] end, + lists:foldl(F, [], Endpoints); +avoid_privacy_if_emergency_call('false', Endpoints) -> + Endpoints. diff --git a/applications/stepswitch/test/stepswitch_bridge_tests.erl b/applications/stepswitch/test/stepswitch_bridge_tests.erl new file mode 100644 index 00000000000..0c89380522b --- /dev/null +++ b/applications/stepswitch/test/stepswitch_bridge_tests.erl @@ -0,0 +1,86 @@ +%%%----------------------------------------------------------------------------- +%%% @copyright (C) 2011-2019, 2600Hz +%%% @doc +%%% This Source Code Form is subject to the terms of the Mozilla Public +%%% License, v. 2.0. If a copy of the MPL was not distributed with this +%%% file, You can obtain one at https://mozilla.org/MPL/2.0/. +%%% +%%% @end +%%%----------------------------------------------------------------------------- +-module(stepswitch_bridge_tests). + +-include_lib("eunit/include/eunit.hrl"). + +avoid_privacy_if_emergency_call_test_() -> + EmergencyPrivacy = emergency_endpoint_with_privacy(), + EmergencyNotPrivacy = emergency_endpoint_without_privacy(), + NotEmergencyPrivacy = not_emergency_endpoint_with_privacy(), + NotEmergencyNotPrivacy = not_emergency_endpoint_without_privacy(), + + [{"If emergency endpoint and privacy enabled, don't enforce privacy" + ,check_expected(EmergencyNotPrivacy, [EmergencyPrivacy]) + } + ,{"If emergency endpoint and privacy disabled, let it be" + ,check_expected(EmergencyNotPrivacy, [EmergencyNotPrivacy]) + } + ,{"If NOT emergency endpoint and privacy enabled, let it be" + ,check_expected(NotEmergencyPrivacy, [NotEmergencyPrivacy]) + } + ,{"If NOT emergency endpoint and privacy disabled, let it be" + ,check_expected(NotEmergencyNotPrivacy, [NotEmergencyNotPrivacy]) + } + ]. + +check_expected(Expected, Endpoints) -> + Resp = stepswitch_bridge:avoid_privacy_if_emergency_call( + stepswitch_bridge:contains_emergency_endpoints(Endpoints) + ,Endpoints + ), + ?_assert(kz_json:are_equal(Expected, hd(Resp))). + +emergency_endpoint_with_privacy() -> + endpoint(<<"true">>, <<"kazoo">>, 'true', 'true'). + +emergency_endpoint_without_privacy() -> + endpoint(<<"true">>, <<"none">>, 'false', 'false'). + +not_emergency_endpoint_with_privacy() -> + endpoint(<<"false">>, <<"kazoo">>, 'true', 'true'). + +not_emergency_endpoint_without_privacy() -> + endpoint(<<"false">>, <<"none">>, 'false', 'false'). + +endpoint(IsEmergency, PrivacyMethod, PrivacyHideName, PrivacyHideNumber) -> + kz_json:from_list_recursive( + [{<<"Route">>, <<"sip:911@testing.zswitch.net">>} + ,{<<"Outbound-Callee-ID-Name">>, <<"911">>} + ,{<<"Outbound-Callee-ID-Number">>, <<"911">>} + ,{<<"To-DID">>, <<"911">>} + ,{<<"Invite-Format">>, <<"route">>} + ,{<<"Caller-ID-Type">>, <<"external">>} + ,{<<"Bypass-Media">>, false} + ,{<<"Auth-User">>, <<"user_endpoint">>} + ,{<<"Auth-Password">>, <<"password_endpoint">>} + ,{<<"Endpoint-Type">>, <<"sip">>} + ,{<<"Endpoint-Progress-Timeout">>, <<"8">>} + ,{<<"Custom-Channel-Vars">> + ,[{<<"Emergency-Resource">>, IsEmergency} + ,{<<"Matched-Number">>, <<"911">>} + ,{<<"Resource-Type">>, <<"offnet-termination">>} + ,{<<"Format-From-URI">>, 'false'} + ,{<<"Original-Number">>, <<"911">>} + ,{<<"DID-Classifier">>, <<"emergency">>} + ,{<<"E164-Destination">>, <<"911">>} + ,{<<"Resource-ID">>, <<"eb79c3e4a17518e83307482bf3f2e40b-emergency">>} + ,{<<"Global-Resource">>, 'true'} + ] + } + ,{<<"Outbound-Caller-ID-Number">>, <<"+14151234567">>} + ,{<<"Outbound-Caller-ID-Name">>, <<"Emergency Caller ID Test">>} + ,{<<"Privacy-Method">>, PrivacyMethod} + ,{<<"Privacy-Hide-Name">>, PrivacyHideName} + ,{<<"Privacy-Hide-Number">>, PrivacyHideNumber} + ,{<<"Weight">>, 50} + ,{<<"Name">>, <<"E911 Testing Outbound Carrier - emergency">>} + ] + ). diff --git a/core/kazoo_amqp/include/kapi_offnet_resource.hrl b/core/kazoo_amqp/include/kapi_offnet_resource.hrl index 5671b9e6262..4b3ef059e6e 100644 --- a/core/kazoo_amqp/include/kapi_offnet_resource.hrl +++ b/core/kazoo_amqp/include/kapi_offnet_resource.hrl @@ -57,6 +57,7 @@ -define(KEY_PRIVACY_METHOD, <<"Privacy-Method">>). -define(KEY_PRIVACY_HIDE_NAME, <<"Privacy-Hide-Name">>). -define(KEY_PRIVACY_HIDE_NUMBER, <<"Privacy-Hide-Number">>). +-define(KEY_EMERGENCY_RESOURCE, <<"Emergency-Resource">>). -define(RESOURCE_TYPE_AUDIO, <<"audio">>). -define(RESOURCE_TYPE_ORIGINATE, <<"originate">>). -define(RESOURCE_TYPE_SMS, <<"sms">>).