Separate privdoc and payres api to wapi-psidss (#8)

This commit is contained in:
Anton Belyaev 2018-07-16 17:30:38 +03:00 committed by Andrew Mayorov
parent 7c7ad60e1b
commit bfca5fafd2
12 changed files with 55 additions and 333 deletions

View File

@ -100,8 +100,6 @@ SWAG_SERVER_APP_TARGET := $(APP_PATH)/$(SWAG_SERVER_PREFIX)
SWAG_SERVER_APP_PATH := $(APP_PATH)/$(SWAG_SERVER_PREFIX)
SWAG_SERVER_APP_TARGET_WALLET := $(SWAG_SERVER_APP_PATH)_wallet/rebar.config
SWAG_SERVER_APP_TARGET_PAYRES := $(SWAG_SERVER_APP_PATH)_payres/rebar.config
SWAG_SERVER_APP_TARGET_PRIVDOC := $(SWAG_SERVER_APP_PATH)_privdoc/rebar.config
$(SWAG_SERVER_APP_PATH)_%/rebar.config: $(SWAGGER_SCHEME_API_PATH)/$*
$(SWAGGER_CODEGEN) generate \
@ -111,9 +109,9 @@ $(SWAG_SERVER_APP_PATH)_%/rebar.config: $(SWAGGER_SCHEME_API_PATH)/$*
--additional-properties \
packageName=$(SWAG_SERVER_PREFIX)_$*
swag_server.generate: $(SWAG_SERVER_APP_TARGET_WALLET) $(SWAG_SERVER_APP_TARGET_PAYRES) $(SWAG_SERVER_APP_TARGET_PRIVDOC)
swag_server.generate: $(SWAG_SERVER_APP_TARGET_WALLET)
swag_server.distclean: swag_server.distclean_wallet swag_server.distclean_payres swag_server.distclean_privdoc
swag_server.distclean: swag_server.distclean_wallet
swag_server.distclean_%:
rm -rf $(SWAG_SERVER_APP_PATH)_$*
@ -127,8 +125,6 @@ SWAG_CLIENT_APP_TARGET := $(APP_PATH)/$(SWAG_CLIENT_PREFIX)
SWAG_CLIENT_APP_PATH := $(APP_PATH)/$(SWAG_CLIENT_PREFIX)
SWAG_CLIENT_APP_TARGET_WALLET := $(SWAG_CLIENT_APP_PATH)_wallet/rebar.config
SWAG_CLIENT_APP_TARGET_PAYRES := $(SWAG_CLIENT_APP_PATH)_payres/rebar.config
SWAG_CLIENT_APP_TARGET_PRIVDOC := $(SWAG_CLIENT_APP_PATH)_privdoc/rebar.config
$(SWAG_CLIENT_APP_PATH)_%/rebar.config: $(SWAGGER_SCHEME_API_PATH)/$*
$(SWAGGER_CODEGEN) generate \
@ -138,9 +134,9 @@ $(SWAG_CLIENT_APP_PATH)_%/rebar.config: $(SWAGGER_SCHEME_API_PATH)/$*
--additional-properties \
packageName=$(SWAG_CLIENT_PREFIX)_$*
swag_client.generate: $(SWAG_CLIENT_APP_TARGET_WALLET) $(SWAG_CLIENT_APP_TARGET_PAYRES) $(SWAG_CLIENT_APP_TARGET_PRIVDOC)
swag_client.generate: $(SWAG_CLIENT_APP_TARGET_WALLET)
swag_client.distclean: swag_client.distclean_wallet swag_client.distclean_payres swag_client.distclean_privdoc
swag_client.distclean: swag_client.distclean_wallet
swag_client.distclean_%:
rm -rf $(SWAG_CLIENT_APP_PATH)_$*

View File

@ -10,6 +10,8 @@
stdlib,
woody,
erl_health,
lager,
scoper,
fistful,
ff_withdraw,
wapi

View File

@ -1,77 +0,0 @@
[
{elvis, [
{config, [
#{
dirs => [
"apps/*/src",
"apps/*/test"
],
filter => "*.erl",
ignore => ["_thrift.erl$", "src/swag_server*", "src/swag_client*", "_SUITE.erl$"],
rules => [
{elvis_style, line_length, #{limit => 120, skip_comments => false}},
{elvis_style, no_tabs},
{elvis_style, no_trailing_whitespace},
{elvis_style, macro_module_names},
{elvis_style, operator_spaces, #{rules => [{right, ","}, {right, "++"}, {left, "++"}]}},
{elvis_style, nesting_level, #{level => 4}},
{elvis_style, god_modules, #{limit => 25}},
{elvis_style, no_if_expression},
{elvis_style, invalid_dynamic_call, #{ignore => [wapi_swagger_server]}},
{elvis_style, used_ignored_variable},
{elvis_style, no_behavior_info},
{elvis_style, module_naming_convention, #{regex => "^[a-z]([a-z0-9]*_?)*(_SUITE)?$"}},
{elvis_style, function_naming_convention, #{regex => "^[a-z]([a-z0-9]*_?)*$"}},
{elvis_style, state_record_and_type},
{elvis_style, no_spec_with_records},
{elvis_style, dont_repeat_yourself, #{
min_complexity => 30,
ignore => [
wapi_tests_SUITE
]
}},
{elvis_style, no_debug_call, #{}}
]
},
#{
dirs => ["."],
filter => "Makefile",
ruleset => makefiles
},
#{
dirs => ["."],
filter => "elvis.config",
ruleset => elvis_config
},
#{
dirs => ["apps", "apps/*"],
filter => "rebar.config",
ignore => ["swag_server/*", "swag_client/*"],
rules => [
{elvis_style, line_length, #{limit => 120, skip_comments => false}},
{elvis_style, no_tabs},
{elvis_style, no_trailing_whitespace}
]
},
#{
dirs => ["."],
filter => "rebar.config",
rules => [
{elvis_style, line_length, #{limit => 120, skip_comments => false}},
{elvis_style, no_tabs},
{elvis_style, no_trailing_whitespace}
]
},
#{
dirs => ["apps/*/src"],
filter => "*.app.src",
ignore => ["src/swag_server*", "src/swag_client*"],
rules => [
{elvis_style, line_length, #{limit => 120, skip_comments => false}},
{elvis_style, no_tabs},
{elvis_style, no_trailing_whitespace}
]
}
]}
]}
].

View File

@ -7,15 +7,12 @@
kernel,
stdlib,
public_key,
lager,
%% lager_logstash_formatter,
genlib,
woody,
scoper,
erl_health,
dmsl,
identdocstore_proto,
swag_server_wallet,
swag_server_payres,
swag_server_privdoc,
jose,
cowboy_cors,
cowboy_access_log,
@ -23,9 +20,7 @@
base64url,
snowflake,
woody_user_identity,
payproc_errors,
erl_health,
identdocstore_proto
payproc_errors
]},
{env, []}
]}.

View File

@ -1,145 +0,0 @@
-module(wapi_payres_handler).
-include_lib("dmsl/include/dmsl_cds_thrift.hrl").
-behaviour(swag_server_payres_logic_handler).
-behaviour(wapi_handler).
%% swag_server_payres_logic_handler callbacks
-export([authorize_api_key/3]).
-export([handle_request/4]).
%% wapi_handler callbacks
-export([process_request/4]).
%% Types
-type req_data() :: wapi_handler:req_data().
-type handler_context() :: wapi_handler:context().
-type request_result() :: wapi_handler:request_result().
-type operation_id() :: swag_server_payres:operation_id().
-type api_key() :: swag_server_payres:api_key().
-type request_context() :: swag_server_payres:request_context().
-type handler_opts() :: swag_server_payres:handler_opts().
%% API
-spec authorize_api_key(operation_id(), api_key(), handler_opts()) ->
false | {true, wapi_auth:context()}.
authorize_api_key(OperationID, ApiKey, Opts) ->
ok = scoper:add_meta(#{api => payres, operation_id => OperationID}),
wapi_auth:authorize_api_key(OperationID, ApiKey, Opts).
-spec handle_request(operation_id(), req_data(), request_context(), handler_opts()) ->
request_result().
handle_request(OperationID, Req, SwagContext, Opts) ->
wapi_handler:handle_request(OperationID, Req, SwagContext, ?MODULE, Opts).
-spec process_request(operation_id(), req_data(), handler_context(), handler_opts()) ->
request_result().
process_request('StoreBankCard', Req, Context, _Opts) ->
{CardData, AuthData} = process_card_data(Req, Context),
wapi_handler_utils:reply_ok(201, maps:merge(to_swag(CardData), to_swag(AuthData)));
process_request('GetBankCard', #{'token' := Token}, _Context, _Opts) ->
case decode_token(Token) of
{ok, Data} ->
wapi_handler_utils:reply_ok(200, Data);
{error, badarg} ->
wapi_handler_utils:reply_ok(404)
end.
%% Internal functions
process_card_data(#{'BankCard' := Data}, Context) ->
put_card_data_to_cds(to_thrift(card_data, Data), to_thrift(session_data, Data), Context).
put_card_data_to_cds(CardData, SessionData, Context) ->
Call = {cds_storage, 'PutCardData', [CardData, SessionData]},
case service_call(Call, Context) of
{ok, #'PutCardDataResult'{session_id = SessionID, bank_card = BankCard}} ->
{{bank_card, BankCard}, {auth_data, SessionID}};
{exception, Exception} ->
case Exception of
#'InvalidCardData'{} ->
wapi_handler:throw_result(wapi_handler_utils:reply_ok(400,
wapi_handler_utils:get_error_msg(<<"Card data is invalid">>)
));
#'KeyringLocked'{} ->
% TODO
% It's better for the cds to signal woody-level unavailability when the
% keyring is locked, isn't it? It could always mention keyring lock as a
% reason in a woody error definition.
wapi_handler:throw_result(wapi_handler_utils:reply_error(503))
end
end.
to_thrift(card_data, Data) ->
{Month, Year} = parse_exp_date(genlib_map:get(<<"expDate">>, Data)),
CardNumber = genlib:to_binary(genlib_map:get(<<"cardNumber">>, Data)),
#'CardData'{
pan = CardNumber,
exp_date = #'ExpDate'{
month = Month,
year = Year
},
cardholder_name = genlib_map:get(<<"cardHolder">>, Data, undefined),
cvv = genlib_map:get(<<"cvv">>, Data, undefined)
};
to_thrift(session_data, Data) ->
#'SessionData'{
auth_data = {card_security_code, #'CardSecurityCode'{
value = maps:get(<<"cvv">>, Data, <<>>)
}}
}.
to_swag({Spec, Data}) when is_atom(Spec) ->
to_swag(Spec, Data).
to_swag(bank_card, #domain_BankCard{
'token' = Token,
'payment_system' = PaymentSystem,
'bin' = Bin,
'masked_pan' = MaskedPan
}) ->
BankCardData = genlib_map:compact(#{
<<"token">> => Token,
<<"paymentSystem">> => genlib:to_binary(PaymentSystem),
<<"bin">> => Bin,
<<"maskedPan">> => MaskedPan,
<<"lastDigits">> => wapi_utils:get_last_pan_digits(MaskedPan)
}),
maps:with(
[<<"token">>, <<"paymentSystem">>, <<"bin">>, <<"lastDigits">>],
BankCardData#{<<"token">> => encode_token(BankCardData)}
);
to_swag(auth_data, PaymentSessionID) ->
#{<<"authData">> => genlib:to_binary(PaymentSessionID)}.
encode_token(TokenData) ->
wapi_utils:map_to_base64url(TokenData).
decode_token(Token) ->
try wapi_utils:base64url_to_map(Token) of
Data = #{<<"token">> := _} ->
{ok, maps:with([<<"token">>, <<"paymentSystem">>, <<"bin">>, <<"lastDigits">>],
Data#{<<"token">> => Token})
};
_ ->
{error, badarg}
catch
error:badarg ->
{error, badarg}
end.
parse_exp_date(ExpDate) when is_binary(ExpDate) ->
[Month, Year0] = binary:split(ExpDate, <<"/">>),
Year = case genlib:to_int(Year0) of
Y when Y < 100 ->
2000 + Y;
Y ->
Y
end,
{genlib:to_int(Month), Year}.
service_call({ServiceName, Function, Args}, #{woody_context := WoodyContext}) ->
wapi_woody_client:call_service(ServiceName, Function, Args, WoodyContext).

View File

@ -1,94 +1,27 @@
-module(wapi_privdoc_handler).
%% TODO
%% switch to wapi_privdoc_handler when wapi becomes a whole service.
%% Note
%% It's a bit dirty to call cds directly from a non-pcidss service
%% even though we get only presentaton data from cds here, not actual card data.
-module(wapi_privdoc_backend).
-include_lib("identdocstore_proto/include/identdocstore_identity_document_storage_thrift.hrl").
-behaviour(swag_server_privdoc_logic_handler).
-behaviour(wapi_handler).
%% swag_server_privdoc_logic_handler callbacks
-export([authorize_api_key/3]).
-export([handle_request/4]).
%% wapi_handler callbacks
-export([process_request/4]).
%% helper
%% TODO move it somewhere else
-export([get_proof/2]).
%% Types
-type req_data() :: wapi_handler:req_data().
-type handler_context() :: wapi_handler:context().
-type request_result() :: wapi_handler:request_result().
-type operation_id() :: swag_server_privdoc:operation_id().
-type api_key() :: swag_server_privdoc:api_key().
-type request_context() :: swag_server_privdoc:request_context().
-type handler_opts() :: swag_server_privdoc:handler_opts().
%% API
-spec authorize_api_key(operation_id(), api_key(), handler_opts()) ->
false | {true, wapi_auth:context()}.
authorize_api_key(OperationID, ApiKey, Opts) ->
ok = scoper:add_meta(#{api => privdoc, operation_id => OperationID}),
wapi_auth:authorize_api_key(OperationID, ApiKey, Opts).
-spec handle_request(operation_id(), req_data(), request_context(), handler_opts()) ->
request_result().
handle_request(OperationID, Params, SwagContext, Opts) ->
wapi_handler:handle_request(OperationID, Params, SwagContext, ?MODULE, Opts).
-spec process_request(operation_id(), req_data(), handler_context(), handler_opts()) ->
request_result().
process_request('StorePrivateDocument', #{'PrivateDocument' := Params}, Context, _Opts) ->
wapi_handler_utils:reply_ok(201, process_doc_data(Params, Context)).
process_doc_data(Params, Context) ->
{ok, Token} = put_doc_data_to_cds(to_thrift(doc_data, Params), Context),
to_swag(doc, {Params, Token}).
-spec get_proof(binary(), handler_context()) -> map().
get_proof(Token, Context) ->
{ok, DocData} = service_call({identdoc_storage, 'Get', [Token]}, Context),
to_swag(doc_data, {DocData, Token}).
to_thrift(doc_data, Params = #{<<"type">> := <<"RUSDomesticPassportData">>}) ->
{russian_domestic_passport, #'identdocstore_RussianDomesticPassport'{
series = maps:get(<<"series">>, Params),
number = maps:get(<<"number">>, Params),
issuer = maps:get(<<"issuer">>, Params),
issuer_code = maps:get(<<"issuerCode">>, Params),
issued_at = maps:get(<<"issuedAt">>, Params),
family_name = maps:get(<<"familyName">>, Params),
first_name = maps:get(<<"firstName">>, Params),
patronymic = maps:get(<<"patronymic">>, Params, undefined),
birth_date = maps:get(<<"birthDate">>, Params),
birth_place = maps:get(<<"birthPlace">>, Params)
}};
to_thrift(doc_data, Params = #{<<"type">> := <<"RUSRetireeInsuranceCertificateData">>}) ->
{russian_retiree_insurance_certificate, #'identdocstore_RussianRetireeInsuranceCertificate'{
number = maps:get(<<"number">>, Params)
}}.
to_swag(doc, {Params, Token}) ->
Doc = to_swag(raw_doc, {Params, Token}),
Doc#{<<"token">> => wapi_utils:map_to_base64url(Doc)};
to_swag(raw_doc, {Params = #{<<"type">> := <<"RUSDomesticPassportData">>}, Token}) ->
#{
<<"type">> => <<"RUSDomesticPassport">>,
<<"token">> => Token,
<<"seriesMasked">> => mask(pass_series, Params),
<<"numberMasked">> => mask(pass_number, Params),
<<"fullnameMasked">> => mask(pass_fullname, Params)
};
to_swag(raw_doc, {Params = #{<<"type">> := <<"RUSRetireeInsuranceCertificateData">>}, Token}) ->
#{
<<"type">> => <<"RUSRetireeInsuranceCertificate">>,
<<"token">> => Token,
<<"numberMasked">> => mask(retiree_insurance_cert_number, Params)
};
to_swag(doc_data, {{russian_domestic_passport, D}, Token}) ->
to_swag(doc, {
#{
@ -108,10 +41,24 @@ to_swag(doc_data, {{russian_retiree_insurance_certificate, D}, Token}) ->
<<"number">> => D#'identdocstore_RussianRetireeInsuranceCertificate'.number
},
Token
}).
put_doc_data_to_cds(IdentityDoc, Context) ->
service_call({identdoc_storage, 'Put', [IdentityDoc]}, Context).
});
to_swag(doc, {Params, Token}) ->
Doc = to_swag(raw_doc, {Params, Token}),
Doc#{<<"token">> => wapi_utils:map_to_base64url(Doc)};
to_swag(raw_doc, {Params = #{<<"type">> := <<"RUSDomesticPassportData">>}, Token}) ->
#{
<<"type">> => <<"RUSDomesticPassport">>,
<<"token">> => Token,
<<"seriesMasked">> => mask(pass_series, Params),
<<"numberMasked">> => mask(pass_number, Params),
<<"fullnameMasked">> => mask(pass_fullname, Params)
};
to_swag(raw_doc, {Params = #{<<"type">> := <<"RUSRetireeInsuranceCertificateData">>}, Token}) ->
#{
<<"type">> => <<"RUSRetireeInsuranceCertificate">>,
<<"token">> => Token,
<<"numberMasked">> => mask(retiree_insurance_cert_number, Params)
}.
service_call({ServiceName, Function, Args}, #{woody_context := WoodyContext}) ->
wapi_woody_client:call_service(ServiceName, Function, Args, WoodyContext).

View File

@ -48,7 +48,5 @@ get_authorizer_child_spec(jwt, Options) ->
get_logic_handler_info() ->
{#{
wallet => wapi_wallet_handler,
payres => wapi_payres_handler,
privdoc => wapi_privdoc_handler
wallet => wapi_wallet_handler
}, []}.

View File

@ -23,16 +23,14 @@ child_spec({HealthRoutes, LogicHandlers}) ->
get_socket_transport() ->
{ok, IP} = inet:parse_address(genlib_app:env(?APP, ip, ?DEFAULT_IP_ADDR)),
Port = genlib_app:env(?APP, port, ?DEFAULT_PORT),
NumAcceptors = genlib_app:env(?APP, acceptors_poolsize, ?DEFAULT_ACCEPTORS_POOLSIZE),
{ranch_tcp, [{ip, IP}, {port, Port}, {num_acceptors, NumAcceptors}]}.
AcceptorsPool = genlib_app:env(?APP, acceptors_poolsize, ?DEFAULT_ACCEPTORS_POOLSIZE),
{ranch_tcp, [{ip, IP}, {port, Port}, {num_acceptors, AcceptorsPool}]}.
get_cowboy_config(HealthRoutes, LogicHandlers) ->
Dispatch =
cowboy_router:compile(squash_routes(
HealthRoutes ++
swag_server_wallet_router:get_paths(maps:get(wallet, LogicHandlers)) ++
swag_server_payres_router:get_paths(maps:get(payres, LogicHandlers)) ++
swag_server_privdoc_router:get_paths(maps:get(privdoc, LogicHandlers))
swag_server_wallet_router:get_paths(maps:get(wallet, LogicHandlers))
)),
[
{env, [

View File

@ -156,7 +156,7 @@ create_identity_challenge(IdentityId, Params, Context) ->
ChallengeId = next_id('identity-challenge'),
do(fun() ->
_ = check_resource(identity, IdentityId, Context),
ok = unwrap(identity, ff_identity_machine:start_challenge(IdentityId,
ok = unwrap(ff_identity_machine:start_challenge(IdentityId,
maps:merge(#{id => ChallengeId}, from_swag(identity_challenge_params, Params)
))),
unwrap(get_identity_challenge(IdentityId, ChallengeId, Context))
@ -164,11 +164,12 @@ create_identity_challenge(IdentityId, Params, Context) ->
-spec get_identity_challenge(id(), id(), ctx()) -> result(map(),
{identity, notfound} |
{identity, unauthorized}
{identity, unauthorized} |
{challenge, notfound}
).
get_identity_challenge(IdentityId, ChallengeId, Context) ->
do(fun() ->
Challenge = unwrap(ff_identity:challenge(
Challenge = unwrap(challenge, ff_identity:challenge(
ChallengeId, ff_identity_machine:identity(get_state(identity, IdentityId, Context))
)),
Proofs = enrich_proofs(ff_identity_challenge:proofs(Challenge), Context),
@ -403,7 +404,7 @@ enrich_proofs(Proofs, Context) ->
[enrich_proof(P, Context) || P <- Proofs].
enrich_proof({_, Token}, Context) ->
wapi_privdoc_handler:get_proof(Token, Context).
wapi_privdoc_backend:get_proof(Token, Context).
get_state(Resource, Id, Context) ->
State = unwrap(Resource, do_get_state(Resource, Id)),

View File

@ -144,7 +144,8 @@ process_request('GetIdentityChallenge', #{
case wapi_wallet_ff_backend:get_identity_challenge(IdentityId, ChallengeId, Context) of
{ok, Challenge} -> wapi_handler_utils:reply_ok(200, Challenge);
{error, {identity, notfound}} -> wapi_handler_utils:reply_ok(404);
{error, {identity, unauthorized}} -> wapi_handler_utils:reply_ok(404)
{error, {identity, unauthorized}} -> wapi_handler_utils:reply_ok(404);
{error, {challenge, notfound}} -> wapi_handler_utils:reply_ok(404)
end;
process_request('PollIdentityChallengeEvents', Params, Context, _Opts) ->
case wapi_wallet_ff_backend:get_identity_challenge_events(Params, Context) of
@ -297,9 +298,9 @@ process_request('GetWithdrawalEvents', #{
end;
%% Residences
process_request('GetResidence', #{'residence' := Residence}, Context, _Opts) ->
case wapi_wallet_ff_backend:get_residence(Residence, Context) of
{ok, Currency} -> wapi_handler_utils:reply_ok(200, Currency);
process_request('GetResidence', #{'residence' := ResidenceId}, Context, _Opts) ->
case wapi_wallet_ff_backend:get_residence(ResidenceId, Context) of
{ok, Residence} -> wapi_handler_utils:reply_ok(200, Residence);
{error, notfound} -> wapi_handler_utils:reply_ok(404)
end;

View File

@ -4,7 +4,7 @@
#{
dirs => ["apps/*/src"],
filter => "*.erl",
ignore => ["_thrift.erl$"],
ignore => ["_thrift.erl$", "src/swag_server*", "src/swag_client*"],
rules => [
{elvis_style, line_length, #{limit => 120, skip_comments => false}},
{elvis_style, no_tabs},
@ -56,6 +56,7 @@
},
#{
dirs => ["apps", "apps/*"],
ignore => ["swag_server*", "swag_client*"],
filter => "rebar.config",
rules => [
{elvis_style, line_length, #{limit => 120, skip_comments => false}},
@ -75,6 +76,7 @@
#{
dirs => ["apps/*/src"],
filter => "*.app.src",
ignore => ["src/swag_server*", "src/swag_client*"],
rules => [
{elvis_style, line_length, #{limit => 120, skip_comments => false}},
{elvis_style, no_tabs},

View File

@ -58,6 +58,10 @@
{<<"jose">>,{pkg,<<"jose">>,<<"1.7.9">>},0},
{<<"jsx">>,{pkg,<<"jsx">>,<<"2.8.2">>},0},
{<<"lager">>,{pkg,<<"lager">>,<<"3.6.1">>},0},
{<<"lager_logstash_formatter">>,
{git,"git@github.com:rbkmoney/lager_logstash_formatter.git",
{ref,"24527c15c47749866f2d427b333fa1333a46b8af"}},
0},
{<<"machinery">>,
{git,"git@github.com:rbkmoney/machinery.git",
{ref,"eb1beed9a287d8b6ab8c68b782b2143ef574c99d"}},