FF-172: Screen for pans in destination using swagger_validator callback (#206)

This commit is contained in:
Alexey 2020-04-14 12:07:24 +03:00 committed by GitHub
parent e8a3bad7ed
commit 0d7150a39a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 121 additions and 26 deletions

2
Jenkinsfile vendored
View File

@ -41,7 +41,7 @@ build('fistful-server', 'docker-host', finalHook) {
} }
runStage('dialyze') { runStage('dialyze') {
withWsCache("_build/default/rebar3_21.3.8.7_plt") { withWsCache("_build/default/rebar3_22.3.1_plt") {
sh 'make wc_dialyze' sh 'make wc_dialyze'
} }
} }

View File

@ -20,7 +20,7 @@ BASE_IMAGE_NAME := service-erlang
BASE_IMAGE_TAG := da0ab769f01b650b389d18fc85e7418e727cbe96 BASE_IMAGE_TAG := da0ab769f01b650b389d18fc85e7418e727cbe96
# Build image tag to be used # Build image tag to be used
BUILD_IMAGE_TAG := 4536c31941b9c27c134e8daf0fd18848809219c9 BUILD_IMAGE_TAG := 442c2c274c1d8e484e5213089906a4271641d95e
REGISTRY := dr2.rbkmoney.com REGISTRY := dr2.rbkmoney.com

View File

@ -112,6 +112,11 @@ start_app(wapi = AppName) ->
decryption_key_paths => [ decryption_key_paths => [
"/opt/wapi/config/jwk.json" "/opt/wapi/config/jwk.json"
] ]
}},
{swagger_handler_opts, #{
validation_opts => #{
custom_validator => wapi_swagger_validator
}
}} }}
]), #{}}; ]), #{}};

View File

@ -28,7 +28,8 @@ init([]) ->
{LogicHandlers, LogicHandlerSpecs} = get_logic_handler_info(), {LogicHandlers, LogicHandlerSpecs} = get_logic_handler_info(),
HealthCheck = enable_health_logging(genlib_app:env(wapi, health_check, #{})), HealthCheck = enable_health_logging(genlib_app:env(wapi, health_check, #{})),
HealthRoutes = [{'_', [erl_health_handle:get_route(HealthCheck)]}], HealthRoutes = [{'_', [erl_health_handle:get_route(HealthCheck)]}],
SwaggerSpec = wapi_swagger_server:child_spec(HealthRoutes, LogicHandlers), SwaggerHandlerOpts = genlib_app:env(wapi, swagger_handler_opts, #{}),
SwaggerSpec = wapi_swagger_server:child_spec(HealthRoutes, LogicHandlers, SwaggerHandlerOpts),
{ok, { {ok, {
{one_for_all, 0, 1}, {one_for_all, 0, 1},
[LechiffreSpec] ++ [LechiffreSpec] ++

View File

@ -1,6 +1,6 @@
-module(wapi_swagger_server). -module(wapi_swagger_server).
-export([child_spec/2]). -export([child_spec/3]).
-export_type([logic_handler/0]). -export_type([logic_handler/0]).
-export_type([logic_handlers/0]). -export_type([logic_handlers/0]).
@ -8,16 +8,18 @@
-type logic_handler() :: swag_server_wallet:logic_handler(_). -type logic_handler() :: swag_server_wallet:logic_handler(_).
-type logic_handlers() :: #{atom() => logic_handler()}. -type logic_handlers() :: #{atom() => logic_handler()}.
-type swagger_handler_opts() :: swag_server_wallet_router:swagger_handler_opts().
-define(APP, wapi). -define(APP, wapi).
-define(DEFAULT_ACCEPTORS_POOLSIZE, 100). -define(DEFAULT_ACCEPTORS_POOLSIZE, 100).
-define(DEFAULT_IP_ADDR, "::"). -define(DEFAULT_IP_ADDR, "::").
-define(DEFAULT_PORT, 8080). -define(DEFAULT_PORT, 8080).
-spec child_spec(cowboy_router:routes(), logic_handlers()) -> -spec child_spec(cowboy_router:routes(), logic_handlers(), swagger_handler_opts()) ->
supervisor:child_spec(). supervisor:child_spec().
child_spec(HealthRoutes, LogicHandlers) -> child_spec(HealthRoutes, LogicHandlers, SwaggerHandlerOpts) ->
{Transport, TransportOpts} = get_socket_transport(), {Transport, TransportOpts} = get_socket_transport(),
CowboyOpts = get_cowboy_config(HealthRoutes, LogicHandlers), CowboyOpts = get_cowboy_config(HealthRoutes, LogicHandlers, SwaggerHandlerOpts),
ranch:child_spec(?MODULE, Transport, TransportOpts, cowboy_clear, CowboyOpts). ranch:child_spec(?MODULE, Transport, TransportOpts, cowboy_clear, CowboyOpts).
get_socket_transport() -> get_socket_transport() ->
@ -26,11 +28,14 @@ get_socket_transport() ->
AcceptorsPool = genlib_app:env(?APP, acceptors_poolsize, ?DEFAULT_ACCEPTORS_POOLSIZE), AcceptorsPool = genlib_app:env(?APP, acceptors_poolsize, ?DEFAULT_ACCEPTORS_POOLSIZE),
{ranch_tcp, #{socket_opts => [{ip, IP}, {port, Port}], num_acceptors => AcceptorsPool}}. {ranch_tcp, #{socket_opts => [{ip, IP}, {port, Port}], num_acceptors => AcceptorsPool}}.
get_cowboy_config(HealthRoutes, LogicHandlers) -> get_cowboy_config(HealthRoutes, LogicHandlers, SwaggerHandlerOpts) ->
Dispatch = Dispatch =
cowboy_router:compile(squash_routes( cowboy_router:compile(squash_routes(
HealthRoutes ++ HealthRoutes ++
swag_server_wallet_router:get_paths(maps:get(wallet, LogicHandlers)) swag_server_wallet_router:get_paths(
maps:get(wallet, LogicHandlers),
SwaggerHandlerOpts
)
)), )),
CowboyOpts = #{ CowboyOpts = #{
env => #{ env => #{

View File

@ -0,0 +1,80 @@
-module(wapi_swagger_validator).
-type param_rule() :: swag_server_wallet_param_validator:param_rule().
-type schema_rule() :: swag_server_wallet_schema_validator:schema_rule().
-type value() :: swag_server_wallet:value().
-type param_context() :: swag_server_wallet_param_validator:context().
-type schema_context() :: swag_server_wallet_schema_validator:context().
-type validate_param_result() ::
ok | {ok, term()} | pass | error | {error, Error :: term()}.
-type validate_schema_result() ::
jesse_state:state() | pass | no_return().
-behaviour(swag_server_wallet_custom_validator).
-export([validate_param/3]).
-export([validate_schema/4]).
-spec validate_param(param_rule(), value(), param_context()) ->
validate_param_result().
validate_param(_Rule, _Value, _Meta) ->
pass.
-spec validate_schema(schema_rule(), value(), schema_context(), jesse_state:state()) ->
validate_schema_result().
validate_schema(
{<<"type">>, <<"string">>},
Value,
#{
operation_id := 'CreateDestination',
definition_name := 'Destination',
% current_path := [<<"name">>], % check all fields
msg_type := request
},
JesseState
) when is_binary(Value) ->
case check_destination_name(Value) of
ok ->
pass; % pass back to the built-in validator
error ->
jesse_error:handle_data_invalid(wrong_format, Value, JesseState)
end;
validate_schema(_Rule, _Value, _Meta, _JesseState) ->
pass.
check_destination_name(Name) ->
case re:run(Name, <<"\\d{12,19}">>, [{capture, all, binary}, global]) of
nomatch -> ok;
{match, Captured} -> check_luhn(Captured)
end.
check_luhn([]) ->
ok;
check_luhn([Captured | Rest]) ->
case lists:any(fun do_check_luhn/1, Captured) of
true -> error;
false -> check_luhn(Rest)
end.
do_check_luhn(String) ->
do_check_luhn(String, 0).
do_check_luhn(<<CheckSum>>, Sum) ->
case Sum * 9 rem 10 of
M when M =:= CheckSum - $0 ->
true;
_M ->
false
end;
do_check_luhn(<<N, Rest/binary>>, Sum) when byte_size(Rest) rem 2 =:= 1 ->
case (N - $0) * 2 of
M when M >= 10 ->
do_check_luhn(Rest, Sum + M div 10 + M rem 10);
M ->
do_check_luhn(Rest, Sum + M)
end;
do_check_luhn(<<N, Rest/binary>>, Sum) ->
do_check_luhn(Rest, Sum + N - $0).

View File

@ -357,7 +357,6 @@ create_destination(Params = #{<<"identity">> := IdenityId}, Context) ->
CreateFun = fun(ID, EntityCtx) -> CreateFun = fun(ID, EntityCtx) ->
_ = check_resource(identity, IdenityId, Context), _ = check_resource(identity, IdenityId, Context),
DestinationParams = from_swag(destination_params, Params), DestinationParams = from_swag(destination_params, Params),
_ = check_destination_params(DestinationParams),
Resource = unwrap(construct_resource(maps:get(resource, DestinationParams))), Resource = unwrap(construct_resource(maps:get(resource, DestinationParams))),
ff_destination:create( ff_destination:create(
DestinationParams#{id => ID, resource => Resource}, DestinationParams#{id => ID, resource => Resource},
@ -841,13 +840,6 @@ when Type =:= <<"CryptoWalletDestinationResource">> ->
currency => from_swag(crypto_wallet_currency, Resource) currency => from_swag(crypto_wallet_currency, Resource)
})}}}. })}}}.
%%@TODO delete as soon as a more permanent solution is in place
check_destination_params(#{name := Name}) ->
case re:run(Name, <<"\\d{12,19}">>, [{capture, none}]) of
nomatch -> ok;
match -> throw({illegal_pattern, name})
end.
encode_resource_bank_card(BankCard, AuthData) -> encode_resource_bank_card(BankCard, AuthData) ->
EncodedBankCard = encode_bank_card(BankCard), EncodedBankCard = encode_bank_card(BankCard),
{bank_card, EncodedBankCard#{auth_data => {session, #{session_id => AuthData}}}}. {bank_card, EncodedBankCard#{auth_data => {session, #{session_id => AuthData}}}}.

View File

@ -272,12 +272,6 @@ process_request('CreateDestination', #{'Destination' := Params}, Context, Opts)
wapi_handler_utils:logic_error(external_id_conflict, {ID, ExternalID}); wapi_handler_utils:logic_error(external_id_conflict, {ID, ExternalID});
{error, invalid} -> {error, invalid} ->
wapi_handler_utils:reply_ok(422, wapi_handler_utils:get_error_msg(<<"Invalid currency">>)); wapi_handler_utils:reply_ok(422, wapi_handler_utils:get_error_msg(<<"Invalid currency">>));
{error, {illegal_pattern, Name}} ->
wapi_handler_utils:reply_error(400, #{
<<"errorType">> => <<"SchemaViolated">>,
<<"name">> => Name,
<<"description">> => <<"Illegal pattern found in parameter">>
});
{error, {invalid_resource_token, Type}} -> {error, {invalid_resource_token, Type}} ->
wapi_handler_utils:reply_error(400, #{ wapi_handler_utils:reply_error(400, #{
<<"errorType">> => <<"InvalidResourceToken">>, <<"errorType">> => <<"InvalidResourceToken">>,

View File

@ -173,11 +173,25 @@ create_destination_failed_test(C) ->
{error, {400, #{<<"errorType">> := <<"InvalidResourceToken">>}}} {error, {400, #{<<"errorType">> := <<"InvalidResourceToken">>}}}
= create_destination(IdentityID, Resource0, C), = create_destination(IdentityID, Resource0, C),
%% %%
DestinationName = <<"4242424242424242">>, DestinationName0 = <<"abc4242424242424242">>,
CardToken = store_bank_card(C), CardToken = store_bank_card(C),
Resource1 = make_bank_card_resource(CardToken), Resource1 = make_bank_card_resource(CardToken),
{error, {400, #{<<"errorType">> := <<"SchemaViolated">>}}} {error, {response_validation_failed, _,
= create_destination(DestinationName, IdentityID, Resource1, C). #{
<<"errorType">> := <<"schema_violated">>,
<<"name">> := <<"Destination">>
}
}} = create_destination(DestinationName0, IdentityID, Resource1, C),
DestinationName1 = <<"abc1231241241241244">>,
IdentityID1 = <<"4242424242424242">>,
{error, {response_validation_failed, _,
#{
<<"errorType">> := <<"schema_violated">>,
<<"name">> := <<"Destination">>
}
}} = create_destination(DestinationName1, IdentityID1, Resource1, C),
DestinationName2 = <<"1231241241241244">>,
{ok, _} = create_destination(DestinationName2, IdentityID, Resource1, C).
-spec withdrawal_to_bank_card_test(config()) -> test_return(). -spec withdrawal_to_bank_card_test(config()) -> test_return().

View File

@ -45,6 +45,10 @@
{git,"git@github.com:rbkmoney/dmt_core.git", {git,"git@github.com:rbkmoney/dmt_core.git",
{ref,"8ac78cb1c94abdcdda6675dd7519893626567573"}}, {ref,"8ac78cb1c94abdcdda6675dd7519893626567573"}},
1}, 1},
{<<"email_validator">>,
{git,"https://github.com/rbkmoney/email_validator.git",
{ref,"be90c6ebd34d29fa9390136469b99d8a68ad4996"}},
0},
{<<"erl_health">>, {<<"erl_health">>,
{git,"https://github.com/rbkmoney/erlang-health.git", {git,"https://github.com/rbkmoney/erlang-health.git",
{ref,"c190cb8de0359b933a27cd20ddc74180c0e5f5c4"}}, {ref,"c190cb8de0359b933a27cd20ddc74180c0e5f5c4"}},