diff --git a/apps/capi/src/capi_bouncer_context.erl b/apps/capi/src/capi_bouncer_context.erl index 8b1f8e6..c4559fb 100644 --- a/apps/capi/src/capi_bouncer_context.erl +++ b/apps/capi/src/capi_bouncer_context.erl @@ -1,7 +1,6 @@ -module(capi_bouncer_context). -include_lib("bouncer_proto/include/bouncer_context_v1_thrift.hrl"). - -include_lib("damsel/include/dmsl_payment_processing_thrift.hrl"). -include_lib("damsel/include/dmsl_webhooker_thrift.hrl"). -include_lib("reporter_proto/include/reporter_reports_thrift.hrl"). @@ -9,11 +8,12 @@ -type fragment() :: bouncer_client:context_fragment(). -type acc() :: bouncer_context_helpers:context_fragment(). - +-type payment_tool_context() :: bouncer_context_v1_thrift:'ContextPaymentTool'(). -type fragments() :: {acc(), _ExternalFragments :: #{_ID => fragment()}}. -export_type([fragment/0]). -export_type([acc/0]). +-export_type([payment_tool_context/0]). -export_type([fragments/0]). -type prototypes() :: [ @@ -44,9 +44,7 @@ }. -type prototype_payment_tool() :: #{ - invoice => entity_id(), - customer => entity_id(), - expiration => timestamp() + payment_tool_context => payment_tool_context() }. -type prototype_payproc() :: #{ @@ -87,7 +85,6 @@ -type payout() :: payouts_payout_manager_thrift:'Payout'(). -type entity_id() :: binary(). --type timestamp() :: binary(). -export_type([prototypes/0]). -export_type([prototype_operation/0]). @@ -141,13 +138,7 @@ build(operation, Params = #{id := OperationID}, Acc, _WoodyCtx) -> }; build(payment_tool, Params = #{}, Acc, _WoodyCtx) -> Acc#bctx_v1_ContextFragment{ - payment_tool = #bctx_v1_ContextPaymentTool{ - scope = #bctx_v1_AuthScope{ - invoice = maybe_entity(invoice, Params), - customer = maybe_entity(customer, Params) - }, - expiration = maps:get(expiration, Params, undefined) - } + payment_tool = maps:get(payment_tool_context, Params, undefined) }; build(payproc, Params = #{}, Acc, WoodyCtx) -> Acc#bctx_v1_ContextFragment{ diff --git a/apps/capi/src/capi_crypto.erl b/apps/capi/src/capi_crypto.erl index 2d17ed0..a3a0d15 100644 --- a/apps/capi/src/capi_crypto.erl +++ b/apps/capi/src/capi_crypto.erl @@ -1,6 +1,7 @@ -module(capi_crypto). -include_lib("damsel/include/dmsl_payment_tool_token_thrift.hrl"). +-include_lib("bouncer_proto/include/bouncer_context_v1_thrift.hrl"). -type token() :: binary(). -type token_data() :: #{ @@ -8,8 +9,7 @@ valid_until := deadline(), bouncer_data => bouncer_data() }. --type bouncer_data() :: term(). - +-type bouncer_data() :: capi_bouncer_context:payment_tool_context(). -type payment_tool() :: dmsl_domain_thrift:'PaymentTool'(). -type payment_tool_token() :: dmsl_payment_tool_token_thrift:'PaymentToolToken'(). -type payment_tool_token_payload() :: dmsl_payment_tool_token_thrift:'PaymentToolTokenPayload'(). @@ -22,11 +22,13 @@ -export([encode_token/1]). -export([decode_token/1]). +-define(THRIFT_TYPE, {struct, struct, {dmsl_payment_tool_token_thrift, 'PaymentToolToken'}}). +-define(BOUNCER_THRIFT_TYPE, {struct, struct, {bouncer_context_v1_thrift, 'ContextPaymentTool'}}). + -spec encode_token(token_data()) -> token(). encode_token(TokenData) -> PaymentToolToken = encode_payment_tool_token(TokenData), - ThriftType = {struct, struct, {dmsl_payment_tool_token_thrift, 'PaymentToolToken'}}, - {ok, EncodedToken} = lechiffre:encode(ThriftType, PaymentToolToken), + {ok, EncodedToken} = lechiffre:encode(?THRIFT_TYPE, PaymentToolToken), TokenVersion = token_version(), <>. @@ -47,8 +49,7 @@ token_version() -> <<"v2">>. decrypt_token(EncryptedPaymentToolToken) -> - ThriftType = {struct, struct, {dmsl_payment_tool_token_thrift, 'PaymentToolToken'}}, - case lechiffre:decode(ThriftType, EncryptedPaymentToolToken) of + case lechiffre:decode(?THRIFT_TYPE, EncryptedPaymentToolToken) of {ok, PaymentToolToken} -> Payload = PaymentToolToken#ptt_PaymentToolToken.payload, ValidUntil = PaymentToolToken#ptt_PaymentToolToken.valid_until, @@ -73,11 +74,15 @@ encode_payment_tool_token(TokenData) -> bouncer_data = encode_bouncer_data(BouncerContext) }. --spec encode_bouncer_data(bouncer_data()) -> binary() | undefined. +-spec encode_bouncer_data(bouncer_data() | undefined) -> binary() | undefined. encode_bouncer_data(undefined) -> undefined; encode_bouncer_data(BouncerData) -> - base64:encode(erlang:term_to_binary(BouncerData)). + Codec = thrift_strict_binary_codec:new(), + case thrift_strict_binary_codec:write(Codec, ?BOUNCER_THRIFT_TYPE, BouncerData) of + {ok, Codec1} -> + thrift_strict_binary_codec:close(Codec1) + end. -spec encode_deadline(deadline()) -> binary() | undefined. encode_deadline(undefined) -> @@ -107,14 +112,16 @@ encode_payment_tool_token_payload({mobile_commerce, MobileCommerce}) -> mobile_commerce = MobileCommerce }}. --spec decode_bouncer_data(binary()) -> bouncer_data() | undefined | no_return(). +-spec decode_bouncer_data(binary() | undefined) -> bouncer_data() | undefined | no_return(). decode_bouncer_data(undefined) -> undefined; decode_bouncer_data(Content) -> - try - erlang:binary_to_term(base64:decode(Content)) - catch - error:Error -> + Codec = thrift_strict_binary_codec:new(Content), + case thrift_strict_binary_codec:read(Codec, ?BOUNCER_THRIFT_TYPE) of + {ok, BouncerData, Codec1} -> + _ = thrift_strict_binary_codec:close(Codec1), + BouncerData; + Error -> erlang:error({malformed_token, Error}, [Content]) end. diff --git a/apps/capi/src/capi_handler_customers.erl b/apps/capi/src/capi_handler_customers.erl index a266e07..e74d9f9 100644 --- a/apps/capi/src/capi_handler_customers.erl +++ b/apps/capi/src/capi_handler_customers.erl @@ -115,28 +115,33 @@ prepare('CreateCustomerAccessToken' = OperationID, Req, Context) -> {ok, #{authorize => Authorize, process => Process}}; prepare('CreateBinding' = OperationID, Req, Context) -> CustomerID = maps:get(customerID, Req), + CustomerBindingParams = maps:get('CustomerBindingParams', Req), + PaymentToken = decode_payment_token(CustomerBindingParams), Authorize = fun() -> Prototypes = [ {operation, #{id => OperationID, customer => CustomerID}}, - {payproc, #{customer => CustomerID}} + {payproc, #{customer => CustomerID}}, + {payment_tool, prepare_payment_tool_prototype(PaymentToken)} ], {ok, capi_auth:authorize_operation(Prototypes, Context)} end, Process = fun() -> Result = try - CustomerBindingParams = maps:get('CustomerBindingParams', Req), + #{payment_tool := PaymentTool} = PaymentToken, {CustomerBindingID, RecPaymentToolID} = generate_binding_ids( OperationID, CustomerBindingParams, + PaymentTool, Context ), EncodedCustomerBindingParams = encode_customer_binding_params( CustomerBindingID, RecPaymentToolID, - CustomerBindingParams + CustomerBindingParams, + PaymentTool ), Call = {customer_management, 'StartBinding', {CustomerID, EncodedCustomerBindingParams}}, @@ -285,6 +290,15 @@ prepare(_OperationID, _Req, _Context) -> %% +prepare_payment_tool_prototype(#{bouncer_data := BouncerData}) -> + #{ + payment_tool_context => BouncerData + }; +prepare_payment_tool_prototype(_Other) -> + #{}. + +%% + get_customer_by_id(CustomerID, Context) -> EventRange = #payproc_EventRange{}, capi_handler_utils:service_call({customer_management, 'Get', {CustomerID, EventRange}}, Context). @@ -316,23 +330,17 @@ encode_customer_params(CustomerID, PartyID, Params) -> encode_customer_metadata(Meta) -> capi_json_marshalling:marshal(Meta). -generate_binding_ids(OperationID, CustomerBindingParams, Context = #{woody_context := WoodyContext}) -> +generate_binding_ids(OperationID, CustomerBindingParams, PaymentTool, Context = #{woody_context := WoodyContext}) -> ExternalID = maps:get(<<"externalID">>, CustomerBindingParams, undefined), UserID = capi_handler_utils:get_user_id(Context), PaymentResource = maps:get(<<"paymentResource">>, CustomerBindingParams), - PaymentToolToken = maps:get(<<"paymentToolToken">>, PaymentResource), - PaymentTool = capi_handler_decoder_party:decode_payment_tool(encode_payment_tool_token(PaymentToolToken)), - CustomerBindingParamsEncrypted = - maps:put( - <<"paymentResource">>, - maps:put( - <<"paymentTool">>, - PaymentTool, - maps:remove(<<"paymentToolToken">>, PaymentResource) - ), - CustomerBindingParams - ), + PaymentResourceWoToken = maps:remove(<<"paymentToolToken">>, PaymentResource), + CustomerBindingParamsEncrypted = CustomerBindingParams#{ + <<"paymentResource">> => PaymentResourceWoToken#{ + <<"paymentTool">> => capi_handler_decoder_party:decode_payment_tool(PaymentTool) + } + }, Identity = capi_bender:make_identity( {schema, capi_feature_schemas:customer_binding(), CustomerBindingParamsEncrypted} @@ -354,11 +362,9 @@ generate_binding_ids(OperationID, CustomerBindingParams, Context = #{woody_conte encode_customer_binding_params( CustomerBindingID, RecPaymentToolID, - #{<<"paymentResource">> := PaymentResource} + #{<<"paymentResource">> := PaymentResource}, + PaymentTool ) -> - PaymentToolToken = maps:get(<<"paymentToolToken">>, PaymentResource), - PaymentTool = encode_payment_tool_token(PaymentToolToken), - {ClientInfo, PaymentSession} = capi_handler_utils:unwrap_payment_session(maps:get(<<"paymentSession">>, PaymentResource)), @@ -372,27 +378,34 @@ encode_customer_binding_params( } }. -encode_payment_tool_token(Token) -> +decode_payment_token(#{<<"paymentResource">> := PaymentResource}) -> + decode_payment_token(PaymentResource); +decode_payment_token(#{<<"paymentToolToken">> := Token}) -> case capi_crypto:decode_token(Token) of - {ok, TokenData} -> - #{payment_tool := PaymentTool, valid_until := ValidUntil} = TokenData, + % TODO #ED-162 Проверка времени жизни будет в bouncer, тут её следует убрать вместе с тестами + {ok, #{valid_until := ValidUntil} = TokenData} -> case capi_utils:deadline_is_reached(ValidUntil) of true -> logger:warning("Payment tool token expired: ~p", [capi_utils:deadline_to_binary(ValidUntil)]), capi_handler:respond(logic_error(invalidPaymentToolToken)); _ -> - PaymentTool + TokenData end; unrecognized -> - encode_legacy_payment_tool_token(Token); + % TODO-162: удалить устаревшие токены, заменить их на актуальные и поправить тесты + decode_legacy_payment_token(Token); {error, {decryption_failed, Error}} -> logger:warning("Payment tool token decryption failed: ~p", [Error]), capi_handler:respond(logic_error(invalidPaymentToolToken)) - end. + end; +decode_payment_token(_Other) -> + undefined. -encode_legacy_payment_tool_token(Token) -> +decode_legacy_payment_token(Token) -> try - capi_handler_encoder:encode_payment_tool(capi_utils:base64url_to_map(Token)) + #{ + payment_tool => capi_handler_encoder:encode_payment_tool(capi_utils:base64url_to_map(Token)) + } catch error:badarg -> capi_handler:respond(logic_error(invalidPaymentToolToken)) diff --git a/apps/capi/src/capi_handler_payments.erl b/apps/capi/src/capi_handler_payments.erl index 714cb96..60ef77b 100644 --- a/apps/capi/src/capi_handler_payments.erl +++ b/apps/capi/src/capi_handler_payments.erl @@ -513,18 +513,11 @@ prepare(_OperationID, _Req, _Context) -> prepare_payment_tool_prototype(undefined) -> #{}; -prepare_payment_tool_prototype(PaymentToken) -> +prepare_payment_tool_prototype(#{bouncer_data := BouncerData}) -> #{ - expiration => prepare_payment_tool_expire(PaymentToken), - scope => prepare_payment_tool_scope(PaymentToken) + payment_tool_context => BouncerData }. -prepare_payment_tool_expire(#{valid_until := ValidUntil}) -> - capi_utils:deadline_to_binary(ValidUntil). - -prepare_payment_tool_scope(#{bouncer_data := BouncerData}) -> - BouncerData. - %% validate_allocation(Allocation) -> case capi_allocation:validate(Allocation) of diff --git a/rebar.config b/rebar.config index 7eb298f..259d980 100644 --- a/rebar.config +++ b/rebar.config @@ -112,7 +112,9 @@ ]} ]}, {test, [ - {dialyzer, [{plt_extra_apps, [eunit, common_test, runtime_tools, bender_proto, payout_manager_proto]}]} + {dialyzer, [ + {plt_extra_apps, [eunit, common_test, runtime_tools, bender_proto, payout_manager_proto]} + ]} ]} ]}.