Skip to content

Commit

Permalink
more crossbar bindings (2600hz#6277)
Browse files Browse the repository at this point in the history
add support for creating Bearer Kazoo Tokens

* Sometimes JWT Tokens can be excessive in size. For long running
integrations, we need to provide Bearer Tokens that can be scoped and
invalidated at any time.

* this is not a replacement for user auth

* modules should be allowed to fetch the request data to avoid
normalizing the payload.

* it may confuse log readers when there are UTF characters in the
payload

* some SSO integrations require the username as-is this allows to skip
username normalization by setting 'normalize_username' to false in
crossbar context
  • Loading branch information
lazedo authored and jamesaimonetti committed Jan 28, 2020
1 parent 6d763df commit 6a73856
Show file tree
Hide file tree
Showing 10 changed files with 97 additions and 27 deletions.
31 changes: 21 additions & 10 deletions applications/crossbar/src/api_util.erl
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ maybe_get_req_data(Context, Req, ?VERSION_1) ->
?MODULE:stop(Req, Context1);
maybe_get_req_data(Context, Req0, _Version) ->
{QS, Req1} = get_query_string_data(Req0),
get_req_data(Context, Req1, get_content_type(Req1), QS).
get_req_data_binding(Context, Req1, get_content_type(Req1), QS).

-spec get_query_string_data(cowboy_req:req()) ->
{kz_json:object(), cowboy_req:req()}.
Expand All @@ -208,18 +208,29 @@ get_content_type(Req) ->
{Main, Sub, _Opts} -> <<Main/binary, "/", Sub/binary>>
end.

-spec get_req_data_binding(cb_context:context(), cowboy_req:req(), cowboy_content_type(), kz_json:object()) ->
{cb_context:context(), cowboy_req:req()} |
stop_return().
get_req_data_binding(Context, Req, CT, QS) ->
[{Mod, Params} | _] = cb_context:req_nouns(Context),
Method = kz_term:to_lower_binary(cb_context:method(Context)),
Event = create_event_name(Context, [<<"request_data">>, Method, Mod]),
Payload = [{Req, Context, CT, QS} | Params],
try crossbar_bindings:map(Event, Payload) of
[{'ok', Ctx}] -> {Ctx, Req};
[{'error', Error}] -> {'stop', Req, cb_context:add_system_error(Error, Context)};
_Else -> get_req_data(Context, Req, CT, QS)
catch
_:_:_ -> get_req_data(Context, Req, CT, QS)
end.

-spec get_req_data(cb_context:context(), cowboy_req:req(), cowboy_content_type(), kz_json:object()) ->
{cb_context:context(), cowboy_req:req()} |
stop_return().
get_req_data(Context, Req0, 'undefined', QS) ->
lager:debug("undefined content type when getting req data, assuming application/json"),
case get_request_body(Req0) of
{'ok', Body, Req1} ->
Ctx = cb_context:set_req_header(Context, <<"content-type">>, ?DEFAULT_CONTENT_TYPE),
try_json(Body, QS, Ctx, Req1);
{'error', 'max_size', Req1} ->
handle_max_filesize_exceeded(Context, Req1)
end;
Ctx = cb_context:set_req_header(Context, <<"content-type">>, ?DEFAULT_CONTENT_TYPE),
get_req_data(Ctx, Req0, <<"application/json">>, QS);
get_req_data(Context, Req, <<"multipart/form-data">>, QS) ->
lager:debug("multipart/form-data content type when getting req data"),
maybe_extract_multipart(cb_context:set_query_string(Context, QS), Req, QS);
Expand Down Expand Up @@ -571,7 +582,7 @@ handle_max_filesize_exceeded(Context, Req1) ->
decode_json_body(ReqBody, Req) ->
try kz_json:unsafe_decode(ReqBody) of
JObj ->
lager:debug("request has a json payload: ~s", [ReqBody]),
lager:debug("request has a json payload: ~ts", [ReqBody]),
{normalize_envelope_keys(JObj), Req}
catch
'throw':{'invalid_json',{'error',{ErrLine, ErrMsg}}, _JSON} ->
Expand Down Expand Up @@ -926,7 +937,7 @@ set_auth_context(Context, Token, TokenType) ->

-spec get_authorization_token_type(kz_term:ne_binary()) -> {kz_term:ne_binary(), atom()}.
get_authorization_token_type(<<"Basic ", Token/binary>>) -> {Token, 'basic'};
get_authorization_token_type(<<"Bearer ", Token/binary>>) -> {Token, 'oauth'};
get_authorization_token_type(<<"Bearer ", Token/binary>>) -> {Token, 'bearer'};
get_authorization_token_type(Token) -> {Token, 'unknown'}.

%%------------------------------------------------------------------------------
Expand Down
4 changes: 2 additions & 2 deletions applications/crossbar/src/cb_context.erl
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ is_account_admin(AuthAccountId, AuthUserId) ->
'false'
end.

-spec auth_token_type(context()) -> 'x-auth-token' | 'basic' | 'oauth' | 'unknown'.
-spec auth_token_type(context()) -> 'x-auth-token' | 'basic' | 'bearer' | 'unknown'.
auth_token_type(#cb_context{auth_token_type=AuthTokenType}) -> AuthTokenType.

-spec auth_token(context()) -> kz_term:api_ne_binary().
Expand Down Expand Up @@ -555,7 +555,7 @@ set_device_id(#cb_context{}=Context, DeviceId) ->
set_reseller_id(#cb_context{}=Context, AcctId) ->
Context#cb_context{reseller_id=AcctId}.

-spec set_auth_token_type(context(), 'x-auth-token' | 'basic' | 'oauth' | 'unknown') -> context().
-spec set_auth_token_type(context(), 'x-auth-token' | 'basic' | 'bearer' | 'unknown') -> context().
set_auth_token_type(#cb_context{}=Context, AuthTokenType) ->
Context#cb_context{auth_token_type=AuthTokenType}.

Expand Down
5 changes: 3 additions & 2 deletions applications/crossbar/src/crossbar.hrl
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@

-define(CB_ACCOUNT_TOKEN_RESTRICTIONS, <<"token_restrictions">>).

-define(DEFAULT_ACCEPT_HEADER, [{{<<"*">>, <<"*">>, []}, 1000, []}]).

-define(CONTENT_PROVIDED, [{'to_json', ?JSON_CONTENT_TYPES}
,{'to_csv', ?CSV_CONTENT_TYPES}
]).
Expand All @@ -51,7 +53,6 @@
,?HTTP_PATCH
,?HTTP_OPTIONS
]).

-define(QUICKCALL_PATH_TOKEN, <<"quickcall">>).
-define(DEVICES_QCALL_NOUNS(DeviceId, Number)
,[{<<"quickcall">>, [Number]}
Expand Down Expand Up @@ -151,7 +152,7 @@
,charsets_provided = [<<"iso-8859-1">>] :: kz_term:ne_binaries() %% all charsets provided
,encodings_provided = [<<"gzip;q=1.0">>,<<"identity;q=0.5">>] :: kz_term:ne_binaries() %% gzip and identity
,auth_token = 'undefined' :: kz_term:api_ne_binary()
,auth_token_type = 'x-auth-token' :: 'x-auth-token' | 'basic' | 'oauth' | 'unknown'
,auth_token_type = 'x-auth-token' :: 'x-auth-token' | 'basic' | 'bearer' | 'unknown'
,auth_account_id :: kz_term:api_ne_binary()
,auth_doc :: kz_term:api_object()
,req_verb = ?HTTP_GET :: http_method() % see ?ALLOWED_METHODS
Expand Down
15 changes: 8 additions & 7 deletions applications/crossbar/src/crossbar_doc.erl
Original file line number Diff line number Diff line change
Expand Up @@ -1226,9 +1226,9 @@ add_pvt_auth(_JObj, Updates, Context) ->
add_pvt_alphanum_name(JObj, Updates, Context) ->
add_pvt_alphanum_name(JObj, Updates, Context, kz_json:get_value(<<"name">>, JObj), kz_doc:type(JObj)).

-spec add_pvt_alphanum_name(kz_json:object(), kz_json:json_proplist(), cb_context:context(), kz_term:api_binary(), kz_term:ne_binary()) ->
-spec add_pvt_alphanum_name(kz_json:object(), kz_json:json_proplist(), cb_context:context(), kz_term:api_binary(), kz_term:api_ne_binary()) ->
kz_json:json_proplist().
add_pvt_alphanum_name(JObj, Updates, _Context, 'undefined', <<"user">>) ->
add_pvt_alphanum_name(JObj, Updates, Context, 'undefined', <<"user">>) ->
Name = case {kz_json:get_ne_binary_value(<<"first_name">>, JObj)
,kz_json:get_ne_binary_value(<<"last_name">>, JObj)
}
Expand All @@ -1238,15 +1238,16 @@ add_pvt_alphanum_name(JObj, Updates, _Context, 'undefined', <<"user">>) ->
{FirstName, 'undefined'} -> FirstName;
{FirstName, LastName} -> <<FirstName/binary, LastName/binary>>
end,
[{<<"pvt_alphanum_name">>, cb_modules_util:normalize_alphanum_name(Name)}
| Updates
];
add_pvt_alphanum_name(JObj, Updates, Context, Name, 'undefined');
add_pvt_alphanum_name(_JObj, Updates, _Context, 'undefined', _Type) ->
Updates;
add_pvt_alphanum_name(_JObj, Updates, _Context, Name, _Type) ->
add_pvt_alphanum_name(_JObj, Updates, _Context, Name, _Type)
when is_binary(Name) ->
[{<<"pvt_alphanum_name">>, cb_modules_util:normalize_alphanum_name(Name)}
| Updates
].
];
add_pvt_alphanum_name(_JObj, Updates, _Context, _Name, _Type) ->
Updates.

%%------------------------------------------------------------------------------
%% @doc
Expand Down
1 change: 1 addition & 0 deletions applications/crossbar/src/crossbar_types.hrl
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
-type couch_schema() :: [{couch_doc_path(), validator_rules()}].

-type cb_cowboy_payload() :: {cowboy_req:req(), cb_context:context()}.
-type cb_cowboy_req_data() :: {cowboy_req:req(), cb_context:context(), cowboy_content_type(), kz_json:object()}.

-define(CSV_CONTENT_TYPES, [{<<"application">>, <<"octet-stream">>, '*'}
,{<<"text">>, <<"csv">>, '*'}
Expand Down
4 changes: 3 additions & 1 deletion applications/crossbar/src/modules/cb_token_auth.erl
Original file line number Diff line number Diff line change
Expand Up @@ -155,14 +155,16 @@ early_authenticate(Context) ->
{'true', cb_context:context()}.
early_authenticate(Context, 'x-auth-token') ->
early_authenticate_token(Context, cb_context:auth_token(Context));
early_authenticate(Context, 'bearer') ->
early_authenticate_token(Context, kz_auth_bearer:fetch(cb_context:auth_token(Context)));
early_authenticate(_Context, _TokenType) -> 'false'.

-spec early_authenticate_token(cb_context:context(), kz_term:api_binary()) ->
boolean() |
{'true', cb_context:context()}.
early_authenticate_token(Context, AuthToken) when is_binary(AuthToken) ->
validate_auth_token(Context, AuthToken);
early_authenticate_token(_Context, 'undefined') -> 'true'.
early_authenticate_token(_Context, 'undefined') -> 'false'.

-spec check_auth_token(cb_context:context(), kz_term:api_binary(), boolean()) ->
boolean() |
Expand Down
6 changes: 4 additions & 2 deletions applications/crossbar/src/modules/cb_users.erl
Original file line number Diff line number Diff line change
Expand Up @@ -522,12 +522,14 @@ updates_if_validation_passed(UserId, Context) ->
-spec normalize_username(kz_term:api_binary(), cb_context:context()) -> cb_context:context().
normalize_username(_UserId, Context) ->
JObj = cb_context:req_data(Context),
Normalize = cb_context:fetch(Context, 'normalize_username', true),
case kzd_users:username(JObj) of
'undefined' -> Context;
Username ->
Username when Normalize ->
NormalizedUsername = kz_term:to_lower_binary(Username),
lager:debug("normalized '~s' to '~s'", [Username, NormalizedUsername]),
cb_context:set_req_data(Context, kzd_users:set_username(JObj, NormalizedUsername))
cb_context:set_req_data(Context, kzd_users:set_username(JObj, NormalizedUsername));
_ -> Context
end.

%%------------------------------------------------------------------------------
Expand Down
48 changes: 48 additions & 0 deletions core/kazoo_auth/src/kz_auth_bearer.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
%%%-----------------------------------------------------------------------------
%%% @copyright (C) 2012-2020, 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(kz_auth_bearer).

%%==============================================================================
%% API functions
%%==============================================================================

-export([create/1
,fetch/1
]).

-include("kazoo_auth.hrl").

-spec create(kz_term:proplist()) ->
{'ok', kz_term:ne_binary()} |
{'error', 'algorithm_not_supported'} |
kz_datamgr:data_error().
create(Claims) ->
case kz_auth:create_token(Claims) of
{'ok', Token} -> save_token(Token);
Else -> Else
end.

-spec fetch(kz_term:ne_binary()) -> kz_term:api_ne_binary().
fetch(Bearer) ->
case kz_datamgr:open_cache_doc(?KZ_AUTH_DB, Bearer) of
{'ok', JObj} -> kz_json:get_ne_binary_value(<<"Token">>, JObj);
Else -> Else
end.

-spec save_token(binary()) ->
{'ok', kz_term:ne_binary()} |
kz_datamgr:data_error().
save_token(Token) ->
JObj = kz_json:from_list([{<<"Token">>, Token}]),
Doc = kz_doc:update_pvt_parameters(JObj, ?KZ_AUTH_DB, [{'type', <<"bearer-token">>}]),
case kz_datamgr:save_doc(?KZ_AUTH_DB, Doc) of
{'ok', Created} -> {'ok', kz_doc:id(Created)};
Else -> Else
end.
8 changes: 6 additions & 2 deletions core/kazoo_auth/src/kz_auth_keys.erl
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

-export([get_public_key_from_cert/1]).
-export([get_public_key_from_private_key/1]).
-export([gen_private_key/0]).
-export([gen_private_key/0, gen_private_key/1]).
-export([get_public_key/2]).
-export([get_private_key_from_file/1]).
-export([from_pem/1, to_pem/1]).
Expand Down Expand Up @@ -373,7 +373,11 @@ save_private_key(JObj, Key) ->

-spec gen_private_key() -> {'ok', public_key:rsa_private_key()}.
gen_private_key() ->
Key = public_key:generate_key({'rsa', ?RSA_KEY_SIZE, ?RSA_KEY_SIZE + 1}),
gen_private_key(?RSA_KEY_SIZE).

-spec gen_private_key(integer()) -> {'ok', public_key:rsa_private_key()}.
gen_private_key(Size) ->
Key = public_key:generate_key({'rsa', Size, Size + 1}),
{'ok', Key}.

%% @equiv reset_private_key(kz_auth_apps:get_auth_app(<<"kazoo">>))
Expand Down
2 changes: 1 addition & 1 deletion core/kazoo_stdlib/include/kazoo_json.hrl
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
-type keys() :: [key(),...].

%% Denotes a flatten version of JSON proplist, `[{full_path, value}]'.
-type flat_proplist() :: [{keys(), flat_json_term()},...].
-type flat_proplist() :: [{keys(), flat_json_term()}].

%% Denotes array in JSON.
-type json_array() :: json_terms() | [].
Expand Down

0 comments on commit 6a73856

Please sign in to comment.