Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Umojo-2: authenticating outbound call using Caller-ID #1

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v1.51
v2.00.10
2 changes: 2 additions & 0 deletions ecallmgr/src/ecallmgr_call_events.erl
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,7 @@ create_event(EventName, ApplicationName, Props) ->
-spec create_event_props/3 :: (binary(), 'undefined' | ne_binary(), proplist()) -> proplist().
create_event_props(EventName, ApplicationName, Props) ->
CCVs = ecallmgr_util:custom_channel_vars(Props),
CustomSipHeaders = ecallmgr_util:custom_sip_headers(Props),
{Mega,Sec,Micro} = erlang:now(),
Timestamp = wh_util:to_binary(((Mega * 1000000 + Sec) * 1000000 + Micro)),
props:filter_undefined(
Expand Down Expand Up @@ -392,6 +393,7 @@ create_event_props(EventName, ApplicationName, Props) ->
,{<<"Fax-Bad-Rows">>, props:get_value(<<"variable_fax_bad_rows">>, Props)}
,{<<"Fax-Transfer-Rate">>, props:get_value(<<"variable_fax_transfer_rate">>, Props)}
,{<<"Custom-Channel-Vars">>, wh_json:from_list(CCVs)}
,{<<"Custom-SIP-Headers">>, wh_json:from_list(CustomSipHeaders)}
%% this sucks, its leaky but I dont see a better way around it since we need the raw application
%% name in call_control... (see note in call_control on start_link for why we need to use AMQP
%% to communicate to it)
Expand Down
12 changes: 11 additions & 1 deletion ecallmgr/src/ecallmgr_util.erl
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
-export([send_cmd/4]).
-export([get_expires/1]).
-export([get_interface_properties/1, get_interface_properties/2]).
-export([get_sip_to/1, get_sip_from/1, get_sip_request/1, get_orig_ip/1, custom_channel_vars/1]).
-export([get_sip_to/1, get_sip_from/1, get_sip_request/1, get_orig_ip/1]).
-export([custom_channel_vars/1, custom_sip_headers/1]).
-export([eventstr_to_proplist/1, varstr_to_proplist/1, get_setting/1, get_setting/2]).
-export([is_node_up/1, is_node_up/2]).
-export([fs_log/3, put_callid/1]).
Expand Down Expand Up @@ -208,6 +209,15 @@ custom_channel_vars(Prop) ->
(_, Acc) -> Acc
end, [], Prop).

-spec custom_sip_headers/1 :: (proplist()) -> proplist().
custom_sip_headers(Prop) ->
lists:foldl(fun({<<"variable_sip_h_", Header/binary>>, V}, Acc) -> [{Header, wh_util:to_binary(mochiweb_util:unquote(V))} | Acc];
({<<"variable_sip_rh_", Header/binary>>, V}, Acc) -> [{Header, wh_util:to_binary(mochiweb_util:unquote(V))} | Acc];
({<<"variable_sip_ph_", Header/binary>>, V}, Acc) -> [{Header, wh_util:to_binary(mochiweb_util:unquote(V))} | Acc];
({<<"variable_sip_bye_h_", Header/binary>>, V}, Acc) -> [{Header, wh_util:to_binary(mochiweb_util:unquote(V))} | Acc];
(_, Acc) -> Acc
end, [], Prop).

%% convert a raw FS string of headers to a proplist
%% "Event-Name: NAME\nEvent-Timestamp: 1234\n" -> [{<<"Event-Name">>, <<"NAME">>}, {<<"Event-Timestamp">>, <<"1234">>}]
-spec eventstr_to_proplist/1 :: (ne_binary() | nonempty_string()) -> proplist().
Expand Down
3 changes: 2 additions & 1 deletion lib/whistle-1.0.0/src/api/wapi_call.erl
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@
%% Call Events
-define(CALL_EVENT_HEADERS, [<<"Call-ID">>]).
-define(OPTIONAL_CALL_EVENT_HEADERS, [<<"Application-Name">>, <<"Application-Response">>
,<<"Custom-Channel-Vars">>, <<"Timestamp">>, <<"Channel-State">>
,<<"Custom-Channel-Vars">>, <<"Custom-SIP-Headers">>
,<<"Timestamp">>, <<"Channel-State">>
,<<"Call-Direction">>, <<"Transfer-History">>
,<<"Other-Leg-Direction">>, <<"Other-Leg-Caller-ID-Name">>
,<<"Other-Leg-Caller-ID-Number">>, <<"Other-Leg-Destination-Number">>
Expand Down
22 changes: 22 additions & 0 deletions speech_ivr.README
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
==================================== SETUP ====================================

1) Ensure that cb_speech_ivr is in the "autoload_modules" list in the crossbar
system_config document.

crossbar system_config document:
http://host.example.com:5984/_utils/document.html?system_config/crossbar


2) Ensure that the speech_ivr system_config document exists. If it does not,
create a new document in the system_config database and seed it with the
following JSON:
{
"_id": "speech_ivr",
"default": {
"sip_uri_host": "someserver.example.com;transport=tcp",
"bridge_timeout": 3
}
}

speech_ivr system_config document:
http://host.example.org:5984/_utils/document.html?system_config/speech_ivr
94 changes: 94 additions & 0 deletions whistle_apps/apps/callflow/src/module/cf_speech_ivr.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
%%%-------------------------------------------------------------------
%%% @copyright (C) 2011-2012, Umojo
%%% @doc
%%%
%%% @end
%%% @contributors
%%% Jon Blanton
%%%-------------------------------------------------------------------
-module(cf_speech_ivr).

-include("../callflow.hrl").

-export([handle/2]).

-define(CONFIG_CAT, <<"speech_ivr">>).
%%--------------------------------------------------------------------
%% @public
%% @doc
%% Entry point for this module, attempts to call the speech IVR as defined
%% Returns continue if fails to connect or stop when successfull.
%% @end
%%--------------------------------------------------------------------
-spec handle/2 :: (wh_json:json_object(), whapps_call:call()) -> 'ok'.
handle(Data, Call) ->
case bridge_to_speech_ivr(Data, Call) of
{ok, HangupData} ->
%% success - branch/transfer to correct child
CustomSipHeaders = wh_json:get_value(<<"Custom-SIP-Headers">>, HangupData),
Instructions = {wh_json:get_ne_value(<<"X-Branch">>, CustomSipHeaders)
,wh_json:get_ne_value(<<"X-Transfer">>, CustomSipHeaders)
},

case Instructions of
{undefined, undefined} ->
lager:debug("no instructions found"),
cf_exe:continue(Call);
{undefined, CallflowId} ->
lager:debug("transfer instruction found"),
case couch_mgr:open_doc(whapps_call:account_db(Call), CallflowId) of
{ok, Doc} ->
lager:debug("branching to callflow ~s", [CallflowId]),
Flow = wh_json:get_value(<<"flow">>, Doc, wh_json:new()),
cf_exe:branch(Flow, Call);
{error, R} ->
lager:debug("could not branch to callflow ~s, ~p", [CallflowId, R]),
cf_exe:continue(Call)
end;
{BranchKey, _} ->
lager:debug("branch instruction found"),
cf_exe:attempt(BranchKey, Call)
end;
{_, _} ->
%% failure - attempt to fallback to non-speech enabled ivr
case wh_json:get_ne_value(<<"menu_id">>, Data, undefined) of
undefined ->
cf_exe:continue(Call);
MenuId ->
%% possibly say "due to high call volume, speech is currently disabled"
cf_menu:handle(wh_json:set_value(<<"id">>, MenuId, wh_json:new()), Call)
end
end.

%%--------------------------------------------------------------------
%% @private
%% @doc
%% Attempts to bridge to the the speech IVR
%% @end
%%--------------------------------------------------------------------
-spec bridge_to_speech_ivr(wh_json:json_object(), whapps_call:call()) -> 'ok'.
bridge_to_speech_ivr(Data, Call) ->
SpeechIvrEndpoint = build_speech_ivr_endpoint(whapps_call:request_user(Call)
,whapps_call:account_id(Call)
,wh_json:get_value(<<"id">>, Data)
,<<"PLACEHOLDER">>
),
Timeout = whapps_config:get(?CONFIG_CAT, <<"bridge_timeout">>),
whapps_call_command:b_bridge([SpeechIvrEndpoint], Timeout, <<"simultaneous">>, true, Call).

%%--------------------------------------------------------------------
%% @private
%% @doc
%% Builds the speech IVR endpoint
%% @end
%%--------------------------------------------------------------------
build_speech_ivr_endpoint(Destination, AccountId, IvrId, CacheId) ->
Route = wh_util:to_binary([<<"sip:">>, Destination, <<"@">>, whapps_config:get(?CONFIG_CAT, <<"sip_uri_host">>)]),
SipHeaders = wh_json:from_list([{<<"X-Account-Id">>, AccountId}
,{<<"X-IVR-Id">>, IvrId}
,{<<"X-Cache-Id">>, CacheId}
]),
wh_json:from_list([{<<"Invite-Format">>, <<"route">>}
,{<<"Route">>, Route}
,{<<"SIP-Headers">>, SipHeaders}
]).
196 changes: 196 additions & 0 deletions whistle_apps/apps/crossbar/src/modules/cb_speech_ivr.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
%%%-------------------------------------------------------------------
%%% @copyright (C) 2012, Umojo
%%% @doc
%%% Speech IVR module
%%%
%%% Handle client requests for Speech IVR callflow fragments
%%%
%%% @end
%%% @contributors
%%% Jon Blanton
%%%-------------------------------------------------------------------
-module(cb_speech_ivr).

-export([init/0
,allowed_methods/0, allowed_methods/1
,resource_exists/0, resource_exists/1
,validate/1, validate/2
]).

-include_lib("crossbar/include/crossbar.hrl").

-define(MOD_CONFIG_CAT, <<"speech_ivr">>).
-define(CB_LIST, <<"speech_ivr/crossbar_listing">>).
-define(DIRECTORY_LOOKUP, <<"speech_ivr/directory_lookup">>).
-define(EXPAND_KEYS, [<<"menu_id">>, <<"directory_id">>]).

%%%===================================================================
%%% API
%%%===================================================================
init() ->
_ = crossbar_bindings:bind(<<"v1_resource.allowed_methods.speech_ivr">>, ?MODULE, allowed_methods),
_ = crossbar_bindings:bind(<<"v1_resource.resource_exists.speech_ivr">>, ?MODULE, resource_exists),
_ = crossbar_bindings:bind(<<"v1_resource.validate.speech_ivr">>, ?MODULE, validate),
crossbar_bindings:bind(<<"v1_resource.finish_request.*.speech_ivr">>, ?MODULE, reconcile_services).

%%--------------------------------------------------------------------
%% @public
%% @doc
%% This function determines the verbs that are appropriate for the
%% given Nouns. IE: '/accounts/' can only accept GET and PUT
%%
%% Failure here returns 405
%% @end
%%--------------------------------------------------------------------
-spec allowed_methods/0 :: () -> http_methods().
allowed_methods() ->
['GET'].

-spec allowed_methods/1 :: (path_token()) -> http_methods().
allowed_methods(_) ->
['GET'].

%%--------------------------------------------------------------------
%% @public
%% @doc
%% This function determines if the provided list of Nouns are valid.
%%
%% Failure here returns 404
%% @end
%%--------------------------------------------------------------------
-spec resource_exists/0 :: () -> 'true'.
resource_exists() -> true.

-spec resource_exists/1 :: (path_token()) -> 'true'.
resource_exists(_) -> true.

%%--------------------------------------------------------------------
%% @public
%% @doc
%% This function determines if the parameters and content are correct
%% for this request
%%
%% Failure here returns 400
%% @end
%%--------------------------------------------------------------------
-spec validate/1 :: (#cb_context{}) -> #cb_context{}.
validate(#cb_context{req_verb = <<"get">>}=Context) ->
load_speech_ivr_summary(Context).

-spec validate/2 :: (#cb_context{}, path_token()) -> #cb_context{}.
validate(#cb_context{req_verb = <<"get">>}=Context, DocId) ->
load_speech_ivr(DocId, Context).

%%%===================================================================
%%% Internal functions
%%%===================================================================

%%--------------------------------------------------------------------
%% @private
%% @doc
%% Attempt to load list of speech IVRs
%% @end
%%--------------------------------------------------------------------
-spec load_speech_ivr_summary/1 :: (#cb_context{}) -> #cb_context{}.
load_speech_ivr_summary(Context) ->
crossbar_doc:load_view(?CB_LIST, [], Context, fun normalize_view_results_key/2).

%%--------------------------------------------------------------------
%% @private
%% @doc
%% Load a speech IVR callfow fragment from the database, ids resolved
%% @end
%%--------------------------------------------------------------------
-spec load_speech_ivr/2 :: (ne_binary(), #cb_context{}) -> #cb_context{}.
load_speech_ivr(DocId, Context) ->
case crossbar_doc:load_view(?CB_LIST, [{<<"key">>, DocId}], Context, fun normalize_view_results_value/2) of
#cb_context{resp_status=success, resp_data=[[Data, CallflowId] | _]}=Context1 ->
Data1 = wh_json:set_value(<<"callflow_id">>, CallflowId, Data),
lager:debug("expanding speech ivr data"),
expand_speech_ivr_data(Context1#cb_context{resp_data=Data1});
%TODO: Expand directory here
Else ->
lager:debug("not expanding speech ivr data"),
Else
end.

%%--------------------------------------------------------------------
%% @private
%% @doc
%% Normalizes the resuts of a view using keys
%% @end
%%--------------------------------------------------------------------
-spec expand_speech_ivr_data/1 :: (#cb_context{}) -> #cb_context{}.
expand_speech_ivr_data(#cb_context{resp_data=Data}=Context) ->
case collect_ids(Data) of
[_|_]=Ids ->
lager:debug("expanding 1 or more IDs"),
inject_docs(Ids, Context);
[] ->
lager:debug("no IDs found"),
Context
end.

%%--------------------------------------------------------------------
%% @private
%% @doc
%% Collects the value, specifically id, of particular fields
%% @end
%%--------------------------------------------------------------------
-spec collect_ids/1 :: (wh_json:json_object()) -> [ne_binary(), ...] | [].
collect_ids(Data) ->
wh_json:foldl(fun(Key, Value, Acc) ->
case lists:member(Key, ?EXPAND_KEYS) of
true -> [Value | Acc];
false -> Acc
end
end
,[]
,Data
).

%%--------------------------------------------------------------------
%% @private
%% @doc
%% Injects documents into the response data of a given context
%% @end
%%--------------------------------------------------------------------
-spec inject_docs/2 :: ([ne_binary(), ...], #cb_context{}) -> #cb_context{}.
inject_docs(Ids, #cb_context{db_name=Db, resp_data=Data}=Context) ->
case couch_mgr:all_docs(Db, [{<<"keys">>, Ids}, {<<"include_docs">>, <<"true">>}]) of
{ok, Results} ->
lager:debug("got doc(s), injecting now"),
Data1 = lists:foldl(fun(Doc, Acc) ->
wh_json:set_value(wh_json:get_value(<<"pvt_type">>, Doc)
,crossbar_doc:public_fields(Doc)
,Acc
)
end
,Data
,[wh_json:get_value(<<"doc">>, Result) || Result <- Results]
),
Context#cb_context{resp_data=Data1};
_Else ->
lager:debug("failed inject doc(s)"),
Context
end.

%%--------------------------------------------------------------------
%% @private
%% @doc
%% Normalizes the resuts of a view using keys
%% @end
%%--------------------------------------------------------------------
-spec normalize_view_results_key/2 :: (wh_json:json_object(), wh_json:json_objects()) -> wh_json:json_objects().
normalize_view_results_key(JObj, Acc) ->
[wh_json:get_value(<<"key">>, JObj)|Acc].

%%--------------------------------------------------------------------
%% @private
%% @doc
%% Normalizes the resuts of a view using keys
%% @end
%%--------------------------------------------------------------------
-spec normalize_view_results_value/2 :: (wh_json:json_object(), wh_json:json_objects()) -> wh_json:json_objects().
normalize_view_results_value(JObj, Acc) ->
[wh_json:get_value(<<"value">>, JObj)|Acc].
5 changes: 4 additions & 1 deletion whistle_apps/apps/stepswitch/src/stepswitch_util.erl
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,10 @@ evaluate_number(Number, Resrcs) ->
evaluate_flags(F1, Resrcs) ->
[Resrc
|| #resrc{flags=F2}=Resrc <- Resrcs,
lists:all(fun(Flag) -> lists:member(Flag, F2) end, F1)
lists:all(fun(Flag) ->
wh_util:is_empty(Flag)
orelse lists:member(Flag, F2)
end, F1)
].

%%--------------------------------------------------------------------
Expand Down