ED-206: Mask forbidden as nonexistent invoice in read-only ops (#558)

* Explain reasoning behind masking in a comment
* Simplify `capi_auth` a bit
* Remove outdated TODOs
This commit is contained in:
Andrew Mayorov 2021-07-30 11:00:20 +03:00 committed by GitHub
parent 96da4b830b
commit 2aa76e2a8b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 303 additions and 254 deletions

View File

@ -30,10 +30,7 @@
auth_data => tk_auth_data:auth_data()
}}.
-type resolution() ::
allowed
| forbidden
| {forbidden, _Reason}.
-type resolution() :: allowed | forbidden.
-type consumer() :: capi_auth_legacy:consumer().
@ -96,8 +93,11 @@ authorize_api_key(?unauthorized({TokenType, Token}), TokenContext, WoodyContext)
ProcessingContext :: capi_handler:processing_context()
) -> resolution().
authorize_operation(Prototypes, ProcessingContext) ->
AuthContext = extract_auth_context(ProcessingContext),
do_authorize_operation(Prototypes, get_auth_data(AuthContext), ProcessingContext).
AuthData = get_auth_data(extract_auth_context(ProcessingContext)),
#{swagger_context := SwagContext, woody_context := WoodyContext} = ProcessingContext,
Fragments = capi_bouncer:gather_context_fragments(AuthData, SwagContext, WoodyContext),
Fragments1 = capi_bouncer_context:build(Prototypes, Fragments, WoodyContext),
capi_bouncer:judge(Fragments1, WoodyContext).
%%
@ -155,8 +155,3 @@ make_context(AuthData, LegacyContext) ->
legacy => LegacyContext,
auth_data => AuthData
}).
do_authorize_operation(Prototypes, AuthData, #{swagger_context := SwagContext, woody_context := WoodyContext}) ->
Fragments = capi_bouncer:gather_context_fragments(AuthData, SwagContext, WoodyContext),
Fragments1 = capi_bouncer_context:build(Prototypes, Fragments, WoodyContext),
capi_bouncer:judge(Fragments1, WoodyContext).

View File

@ -10,6 +10,7 @@
-export([handle_request/4]).
-export([respond/1]).
-export([respond_if_undefined/2]).
-export([respond_if_forbidden/2]).
%%
@ -58,7 +59,6 @@
%% @WARNING Must be refactored in case of different classes of users using this API
-define(REALM, <<"external">>).
-define(DOMAIN, <<"common-api">>).
-spec authorize_api_key(operation_id(), swag_server:api_key(), request_context(), handler_opts()) ->
Result :: false | {true, capi_auth:preauth_context()}.
@ -153,10 +153,6 @@ handle_function_(OperationID, Req, SwagContext0, HandlerOpts) ->
allowed ->
Process();
forbidden ->
_ = logger:info("Authorization failed"),
{ok, {401, #{}, undefined}};
{forbidden, Error} ->
_ = logger:info("Authorization failed due to ~p", [Error]),
{ok, {401, #{}, undefined}}
end
catch
@ -204,6 +200,13 @@ respond_if_undefined(undefined, Response) ->
respond_if_undefined(_, _Response) ->
ok.
-spec respond_if_forbidden(Resolution, response()) -> Resolution | throw(response()) when
Resolution :: capi_auth:resolution().
respond_if_forbidden(forbidden, Response) ->
respond(Response);
respond_if_forbidden(allowed, _Response) ->
allowed.
%%
get_auth_context(#{auth_context := AuthContext}) ->

View File

@ -6,7 +6,11 @@
-export([prepare/3]).
-import(capi_handler_utils, [general_error/2, logic_error/2]).
-import(capi_handler_utils, [
general_error/2,
logic_error/2,
map_service_result/1
]).
-spec prepare(
OperationID :: capi_handler:operation_id(),
@ -51,32 +55,29 @@ prepare('CreateCustomer' = OperationID, Req, Context) ->
{ok, #{authorize => Authorize, process => Process}};
prepare('GetCustomerById' = OperationID, Req, Context) ->
CustomerID = maps:get('customerID', Req),
CustomerReply = get_customer_by_id(CustomerID, Context),
Customer = map_service_result(get_customer_by_id(CustomerID, Context)),
Authorize = fun() ->
Prototypes = [
{operation, #{id => OperationID, customer => CustomerID}},
{payproc, #{customer => maybe_woody_reply(CustomerReply)}}
{payproc, #{customer => Customer}}
],
{ok, capi_auth:authorize_operation(Prototypes, Context)}
{ok, mask_customer_notfound(capi_auth:authorize_operation(Prototypes, Context))}
end,
Process = fun() ->
case CustomerReply of
{ok, Customer} ->
case Customer of
#payproc_Customer{} ->
{ok, {200, #{}, decode_customer(Customer)}};
{exception, #payproc_InvalidUser{}} ->
{ok, general_error(404, <<"Customer not found">>)};
{exception, #payproc_CustomerNotFound{}} ->
undefined ->
{ok, general_error(404, <<"Customer not found">>)}
end
end,
{ok, #{authorize => Authorize, process => Process}};
prepare('DeleteCustomer' = OperationID, Req, Context) ->
CustomerID = maps:get('customerID', Req),
CustomerReply = get_customer_by_id(CustomerID, Context),
Authorize = fun() ->
Prototypes = [
{operation, #{id => OperationID, customer => CustomerID}},
{payproc, #{customer => maybe_woody_reply(CustomerReply)}}
{payproc, #{customer => CustomerID}}
],
{ok, capi_auth:authorize_operation(Prototypes, Context)}
end,
@ -98,22 +99,20 @@ prepare('DeleteCustomer' = OperationID, Req, Context) ->
{ok, #{authorize => Authorize, process => Process}};
prepare('CreateCustomerAccessToken' = OperationID, Req, Context) ->
CustomerID = maps:get('customerID', Req),
CustomerReply = get_customer_by_id(CustomerID, Context),
Customer = map_service_result(get_customer_by_id(CustomerID, Context)),
Authorize = fun() ->
Prototypes = [
{operation, #{id => OperationID, customer => CustomerID}},
{payproc, #{customer => maybe_woody_reply(CustomerReply)}}
{payproc, #{customer => Customer}}
],
{ok, capi_auth:authorize_operation(Prototypes, Context)}
end,
Process = fun() ->
case CustomerReply of
{ok, #payproc_Customer{owner_id = PartyID}} ->
case Customer of
#payproc_Customer{owner_id = PartyID} ->
Response = capi_handler_utils:issue_access_token(PartyID, {customer, CustomerID}),
{ok, {201, #{}, Response}};
{exception, #payproc_InvalidUser{}} ->
{ok, general_error(404, <<"Customer not found">>)};
{exception, #payproc_CustomerNotFound{}} ->
undefined ->
{ok, general_error(404, <<"Customer not found">>)}
end
end,
@ -180,21 +179,19 @@ prepare('CreateBinding' = OperationID, Req, Context) ->
{ok, #{authorize => Authorize, process => Process}};
prepare('GetBindings' = OperationID, Req, Context) ->
CustomerID = maps:get(customerID, Req),
CustomerReply = get_customer_by_id(CustomerID, Context),
Customer = map_service_result(get_customer_by_id(CustomerID, Context)),
Authorize = fun() ->
Prototypes = [
{operation, #{id => OperationID, customer => CustomerID}},
{payproc, #{customer => maybe_woody_reply(CustomerReply)}}
{payproc, #{customer => Customer}}
],
{ok, capi_auth:authorize_operation(Prototypes, Context)}
{ok, mask_customer_notfound(capi_auth:authorize_operation(Prototypes, Context))}
end,
Process = fun() ->
case CustomerReply of
{ok, #payproc_Customer{bindings = Bindings}} ->
case Customer of
#payproc_Customer{bindings = Bindings} ->
{ok, {200, #{}, [decode_customer_binding(B, Context) || B <- Bindings]}};
{exception, #payproc_InvalidUser{}} ->
{ok, general_error(404, <<"Customer not found">>)};
{exception, #payproc_CustomerNotFound{}} ->
undefined ->
{ok, general_error(404, <<"Customer not found">>)}
end
end,
@ -202,26 +199,24 @@ prepare('GetBindings' = OperationID, Req, Context) ->
prepare('GetBinding' = OperationID, Req, Context) ->
CustomerID = maps:get(customerID, Req),
CustomerBindingID = maps:get(customerBindingID, Req),
CustomerReply = get_customer_by_id(CustomerID, Context),
Customer = map_service_result(get_customer_by_id(CustomerID, Context)),
Authorize = fun() ->
Prototypes = [
{operation, #{id => OperationID, customer => CustomerID, binding => CustomerBindingID}},
{payproc, #{customer => maybe_woody_reply(CustomerReply)}}
{payproc, #{customer => Customer}}
],
{ok, capi_auth:authorize_operation(Prototypes, Context)}
{ok, mask_customer_notfound(capi_auth:authorize_operation(Prototypes, Context))}
end,
Process = fun() ->
case CustomerReply of
{ok, #payproc_Customer{bindings = Bindings}} ->
case Customer of
#payproc_Customer{bindings = Bindings} ->
case lists:keyfind(CustomerBindingID, #payproc_CustomerBinding.id, Bindings) of
#payproc_CustomerBinding{} = B ->
{ok, {200, #{}, decode_customer_binding(B, Context)}};
false ->
{ok, general_error(404, <<"Customer binding not found">>)}
end;
{exception, #payproc_InvalidUser{}} ->
{ok, general_error(404, <<"Customer not found">>)};
{exception, #payproc_CustomerNotFound{}} ->
undefined ->
{ok, general_error(404, <<"Customer not found">>)}
end
end,
@ -233,7 +228,7 @@ prepare('GetCustomerEvents' = OperationID, Req, Context) ->
{operation, #{id => OperationID, customer => CustomerID}},
{payproc, #{customer => CustomerID}}
],
{ok, capi_auth:authorize_operation(Prototypes, Context)}
{ok, mask_customer_notfound(capi_auth:authorize_operation(Prototypes, Context))}
end,
Process = fun() ->
GetterFun = fun(Range) ->
@ -266,15 +261,19 @@ prepare(_OperationID, _Req, _Context) ->
%%
maybe_woody_reply({ok, Reply}) ->
Reply;
maybe_woody_reply({exception, _}) ->
undefined.
get_customer_by_id(CustomerID, Context) ->
EventRange = #payproc_EventRange{},
capi_handler_utils:service_call({customer_management, 'Get', {CustomerID, EventRange}}, Context).
mask_customer_notfound(Resolution) ->
% ED-206
% When bouncer says "forbidden" we can't really tell the difference between "forbidden because
% of no such customer", "forbidden because client has no access to it" and "forbidden because
% client has no permission to act on it". From the point of view of existing integrations this
% is not great, so we have to mask specific instances of missing authorization as if specified
% customer is nonexistent.
capi_handler:respond_if_forbidden(Resolution, general_error(404, <<"Customer not found">>)).
generate_customer_id(OperationID, PartyID, CustomerParams, #{woody_context := WoodyContext}) ->
ExternalID = maps:get(<<"externalID">>, CustomerParams, undefined),
IdempKey = {OperationID, PartyID, ExternalID},

View File

@ -139,7 +139,6 @@ decode_payment(InvoiceID, Payment, Context) ->
<<"externalID">> => Payment#domain_InvoicePayment.external_id,
<<"invoiceID">> => InvoiceID,
<<"createdAt">> => Payment#domain_InvoicePayment.created_at,
% TODO whoops, nothing to get it from yet
<<"flow">> => decode_flow(Payment#domain_InvoicePayment.flow),
<<"amount">> => Amount,
<<"currency">> => capi_handler_decoder_utils:decode_currency(Currency),

View File

@ -7,7 +7,11 @@
-export([prepare/3]).
-import(capi_handler_utils, [general_error/2, logic_error/2]).
-import(capi_handler_utils, [
general_error/2,
logic_error/2,
map_service_result/1
]).
-spec prepare(
OperationID :: capi_handler:operation_id(),
@ -63,23 +67,13 @@ prepare('CreateInvoiceTemplate' = OperationID, Req, Context) ->
{ok, #{authorize => Authorize, process => Process}};
prepare('GetInvoiceTemplateByID' = OperationID, Req, Context) ->
InvoiceTemplateID = maps:get('invoiceTemplateID', Req),
InvoiceTpl =
case get_invoice_template(InvoiceTemplateID, Context) of
{ok, Result} ->
Result;
{exception, E} when
E == #payproc_InvalidUser{};
E == #payproc_InvoiceTemplateNotFound{};
E == #payproc_InvoiceTemplateRemoved{}
->
undefined
end,
InvoiceTpl = map_service_result(get_invoice_template(InvoiceTemplateID, Context)),
Authorize = fun() ->
Prototypes = [
{operation, #{invoice_template => InvoiceTemplateID, id => OperationID}},
{payproc, #{invoice_template => InvoiceTpl}}
],
Resolution = capi_auth:authorize_operation(Prototypes, Context),
Resolution = mask_invoice_template_notfound(capi_auth:authorize_operation(Prototypes, Context)),
{ok, Resolution}
end,
Process = fun() ->
@ -89,22 +83,10 @@ prepare('GetInvoiceTemplateByID' = OperationID, Req, Context) ->
{ok, #{authorize => Authorize, process => Process}};
prepare('UpdateInvoiceTemplate' = OperationID, Req, Context) ->
InvoiceTemplateID = maps:get('invoiceTemplateID', Req),
InvoiceTpl =
case get_invoice_template(InvoiceTemplateID, Context) of
{ok, Result} ->
Result;
{exception, E} when
E == #payproc_InvalidUser{};
E == #payproc_InvoiceTemplateNotFound{};
E == #payproc_InvoiceTemplateRemoved{}
->
undefined
end,
Authorize = fun() ->
InvoiceTemplateID = maps:get('invoiceTemplateID', Req),
Prototypes = [
{operation, #{invoice_template => InvoiceTemplateID, id => OperationID}},
{payproc, #{invoice_template => InvoiceTpl}}
{payproc, #{invoice_template => InvoiceTemplateID}}
],
Resolution = capi_auth:authorize_operation(Prototypes, Context),
{ok, Resolution}
@ -180,13 +162,7 @@ prepare('DeleteInvoiceTemplate' = OperationID, Req, Context) ->
{ok, #{authorize => Authorize, process => Process}};
prepare('CreateInvoiceWithTemplate' = OperationID, Req, Context) ->
InvoiceTplID = maps:get('invoiceTemplateID', Req),
InvoiceTpl =
case get_invoice_template(InvoiceTplID, Context) of
{ok, Result} ->
Result;
{exception, _Exception} ->
undefined
end,
InvoiceTpl = map_service_result(get_invoice_template(InvoiceTplID, Context)),
Authorize = fun() ->
Prototypes = [
{operation, #{invoice_template => InvoiceTplID, id => OperationID}},
@ -239,19 +215,13 @@ prepare('CreateInvoiceWithTemplate' = OperationID, Req, Context) ->
{ok, #{authorize => Authorize, process => Process}};
prepare('GetInvoicePaymentMethodsByTemplateID' = OperationID, Req, Context) ->
InvoiceTemplateID = maps:get('invoiceTemplateID', Req),
InvoiceTemplate =
case get_invoice_template(InvoiceTemplateID, Context) of
{ok, Result} ->
Result;
{exception, _Exception} ->
undefined
end,
InvoiceTemplate = map_service_result(get_invoice_template(InvoiceTemplateID, Context)),
Authorize = fun() ->
Prototypes = [
{operation, #{invoice_template => InvoiceTemplateID, id => OperationID}},
{payproc, #{invoice_template => InvoiceTemplate}}
],
Resolution = capi_auth:authorize_operation(Prototypes, Context),
Resolution = mask_invoice_template_notfound(capi_auth:authorize_operation(Prototypes, Context)),
{ok, Resolution}
end,
Process = fun() ->
@ -277,6 +247,15 @@ prepare('GetInvoicePaymentMethodsByTemplateID' = OperationID, Req, Context) ->
prepare(_OperationID, _Req, _Context) ->
{error, noimpl}.
mask_invoice_template_notfound(Resolution) ->
% ED-206
% When bouncer says "forbidden" we can't really tell the difference between "forbidden because
% of no such invoice teplate", "forbidden because client has no access to it" and "forbidden
% because client has no permission to act on it". From the point of view of existing integrations
% this is not great, so we have to mask specific instances of missing authorization as if
% specified invoice template is nonexistent.
capi_handler:respond_if_forbidden(Resolution, general_error(404, <<"Invoice template not found">>)).
%%
create_invoice(PartyID, InvoiceTplID, InvoiceParams, Context, BenderPrefix) ->

View File

@ -6,7 +6,11 @@
-export([prepare/3]).
-import(capi_handler_utils, [general_error/2, logic_error/2]).
-import(capi_handler_utils, [
general_error/2,
logic_error/2,
map_service_result/1
]).
-spec prepare(
OperationID :: capi_handler:operation_id(),
@ -68,13 +72,7 @@ prepare('CreateInvoice' = OperationID, Req, Context) ->
{ok, #{authorize => Authorize, process => Process}};
prepare('CreateInvoiceAccessToken' = OperationID, Req, Context) ->
InvoiceID = maps:get(invoiceID, Req),
ResultInvoice =
case capi_handler_utils:get_invoice_by_id(InvoiceID, Context) of
{ok, Result} ->
Result;
{exception, _Exception} ->
undefined
end,
ResultInvoice = map_service_result(capi_handler_utils:get_invoice_by_id(InvoiceID, Context)),
Authorize = fun() ->
Prototypes = [
{operation, #{id => OperationID, invoice => InvoiceID}},
@ -85,9 +83,8 @@ prepare('CreateInvoiceAccessToken' = OperationID, Req, Context) ->
end,
Process = fun() ->
capi_handler:respond_if_undefined(ResultInvoice, general_error(404, <<"Invoice not found">>)),
#'payproc_Invoice'{invoice = Invoice} = ResultInvoice,
#payproc_Invoice{invoice = #domain_Invoice{owner_id = PartyID}} = ResultInvoice,
ExtraProperties = capi_handler_utils:get_extra_properties(Context),
#'domain_Invoice'{owner_id = PartyID} = Invoice,
Response = capi_handler_utils:issue_access_token(
PartyID,
{invoice, InvoiceID},
@ -98,19 +95,13 @@ prepare('CreateInvoiceAccessToken' = OperationID, Req, Context) ->
{ok, #{authorize => Authorize, process => Process}};
prepare('GetInvoiceByID' = OperationID, Req, Context) ->
InvoiceID = maps:get(invoiceID, Req),
ResultInvoice =
case capi_handler_utils:get_invoice_by_id(InvoiceID, Context) of
{ok, Result} ->
Result;
{exception, _Exception} ->
undefined
end,
ResultInvoice = map_service_result(capi_handler_utils:get_invoice_by_id(InvoiceID, Context)),
Authorize = fun() ->
Prototypes = [
{operation, #{id => OperationID, invoice => InvoiceID}},
{payproc, #{invoice => ResultInvoice}}
],
Resolution = capi_auth:authorize_operation(Prototypes, Context),
Resolution = mask_invoice_notfound(capi_auth:authorize_operation(Prototypes, Context)),
{ok, Resolution}
end,
Process = fun() ->
@ -135,7 +126,7 @@ prepare('GetInvoiceByExternalID' = OperationID, Req, Context) ->
{operation, #{id => OperationID, invoice => InvoiceID}},
{payproc, #{invoice => ResultInvoice}}
],
Resolution = capi_auth:authorize_operation(Prototypes, Context),
Resolution = mask_invoice_notfound(capi_auth:authorize_operation(Prototypes, Context)),
{ok, Resolution}
end,
Process = fun() ->
@ -218,7 +209,7 @@ prepare('GetInvoiceEvents' = OperationID, Req, Context) ->
{operation, #{id => OperationID, invoice => InvoiceID}},
{payproc, #{invoice => InvoiceID}}
],
Resolution = capi_auth:authorize_operation(Prototypes, Context),
Resolution = mask_invoice_notfound(capi_auth:authorize_operation(Prototypes, Context)),
{ok, Resolution}
end,
Process = fun() ->
@ -256,19 +247,13 @@ prepare('GetInvoiceEvents' = OperationID, Req, Context) ->
{ok, #{authorize => Authorize, process => Process}};
prepare('GetInvoicePaymentMethods' = OperationID, Req, Context) ->
InvoiceID = maps:get(invoiceID, Req),
ResultInvoice =
case capi_handler_utils:get_invoice_by_id(InvoiceID, Context) of
{ok, Result} ->
Result;
{exception, _Exception} ->
undefined
end,
ResultInvoice = map_service_result(capi_handler_utils:get_invoice_by_id(InvoiceID, Context)),
Authorize = fun() ->
Prototypes = [
{operation, #{id => OperationID, invoice => InvoiceID}},
{payproc, #{invoice => ResultInvoice}}
],
Resolution = capi_auth:authorize_operation(Prototypes, Context),
Resolution = mask_invoice_notfound(capi_auth:authorize_operation(Prototypes, Context)),
{ok, Resolution}
end,
Process = fun() ->
@ -490,3 +475,12 @@ get_invoice_by_external_id(ExternalID, #{woody_context := WoodyContext} = Contex
Error ->
Error
end.
mask_invoice_notfound(Resolution) ->
% ED-206
% When bouncer says "forbidden" we can't really tell the difference between "forbidden because
% of no such invoice", "forbidden because client has no access to it" and "forbidden because
% client has no permission to act on it". From the point of view of existing integrations this
% is not great, so we have to mask specific instances of missing authorization as if specified
% invoice is nonexistent.
capi_handler:respond_if_forbidden(Resolution, general_error(404, <<"Invoice not found">>)).

View File

@ -75,7 +75,7 @@ prepare('GetPartyByID' = OperationID, Req, Context) ->
PartyID = maps:get(partyID, Req),
Authorize = fun() ->
Prototypes = [{operation, #{id => OperationID, party => PartyID}}],
{ok, capi_auth:authorize_operation(Prototypes, Context)}
{ok, mask_party_notfound(capi_auth:authorize_operation(Prototypes, Context))}
end,
Process = fun() ->
case capi_party:get_party(PartyID, Context) of
@ -163,3 +163,12 @@ create_party(PartyID, Context) ->
Error ->
Error
end.
mask_party_notfound(Resolution) ->
% ED-206
% When bouncer says "forbidden" we can't really tell the difference between "forbidden because
% of no such party", "forbidden because client has no access to it" and "forbidden because
% client has no permission to act on it". From the point of view of existing integrations this
% is not great, so we have to mask specific instances of missing authorization as if specified
% party is nonexistent.
capi_handler:respond_if_forbidden(Resolution, general_error(404, <<"Party not found">>)).

View File

@ -17,7 +17,7 @@
) -> {ok, capi_handler:request_state()} | {error, noimpl}.
prepare(OperationID = 'CreatePayment', Req, Context) ->
InvoiceID = maps:get(invoiceID, Req),
Invoice = map_result(get_invoice_by_id(InvoiceID, Context)),
Invoice = get_invoice_by_id(InvoiceID, Context),
PaymentParams = maps:get('PaymentParams', Req),
Authorize = fun() ->
Prototypes = [
@ -90,13 +90,13 @@ prepare(OperationID = 'CreatePayment', Req, Context) ->
{ok, #{authorize => Authorize, process => Process}};
prepare(OperationID = 'GetPayments', Req, Context) ->
InvoiceID = maps:get(invoiceID, Req),
Invoice = map_result(get_invoice_by_id(InvoiceID, Context)),
Invoice = get_invoice_by_id(InvoiceID, Context),
Authorize = fun() ->
Prototypes = [
{operation, #{id => OperationID, invoice => InvoiceID}},
{payproc, #{invoice => Invoice}}
],
{ok, capi_auth:authorize_operation(Prototypes, Context)}
{ok, mask_invoice_notfound(capi_auth:authorize_operation(Prototypes, Context))}
end,
Process = fun() ->
capi_handler:respond_if_undefined(Invoice, general_error(404, <<"Invoice not found">>)),
@ -107,31 +107,30 @@ prepare(OperationID = 'GetPayments', Req, Context) ->
prepare(OperationID = 'GetPaymentByID', Req, Context) ->
InvoiceID = maps:get(invoiceID, Req),
PaymentID = maps:get(paymentID, Req),
Invoice = map_result(get_invoice_by_id(InvoiceID, Context)),
Invoice = get_invoice_by_id(InvoiceID, Context),
Authorize = fun() ->
Prototypes = [
{operation, #{id => OperationID, invoice => InvoiceID, payment => PaymentID}},
{payproc, #{invoice => Invoice}}
],
{ok, capi_auth:authorize_operation(Prototypes, Context)}
{ok, mask_invoice_notfound(capi_auth:authorize_operation(Prototypes, Context))}
end,
Process = fun() ->
capi_handler:respond_if_undefined(Invoice, general_error(404, <<"Invoice not found">>)),
case find_payment_by_id(PaymentID, Invoice) of
{ok, Payment} ->
Payment when Payment /= undefined ->
{ok, {200, #{}, decode_invoice_payment(InvoiceID, Payment, Context)}};
{error, payment_not_found} ->
undefined ->
{ok, general_error(404, <<"Payment not found">>)}
end
end,
{ok, #{authorize => Authorize, process => Process}};
prepare(OperationID = 'GetPaymentByExternalID', Req, Context) ->
ExternalID = maps:get(externalID, Req),
InternalID = map_result(get_payment_by_external_id(ExternalID, Context)),
Invoice = map_result(
maybe(InternalID, fun({InvoiceID, _}) ->
get_invoice_by_id(InvoiceID, Context)
end)
InternalID = get_payment_by_external_id(ExternalID, Context),
Invoice = maybe(
InternalID,
fun({InvoiceID, _}) -> get_invoice_by_id(InvoiceID, Context) end
),
OperationPrototype = maybe(
@ -146,17 +145,16 @@ prepare(OperationID = 'GetPaymentByExternalID', Req, Context) ->
{operation, genlib:define(OperationPrototype, #{id => OperationID})},
{payproc, #{invoice => Invoice}}
],
{ok, capi_auth:authorize_operation(Prototypes, Context)}
{ok, mask_payment_notfound(capi_auth:authorize_operation(Prototypes, Context))}
end,
Process = fun() ->
capi_handler:respond_if_undefined(InternalID, general_error(404, <<"Payment not found">>)),
capi_handler:respond_if_undefined(Invoice, general_error(404, <<"Invoice not found">>)),
{InvoiceID, PaymentID} = InternalID,
case find_payment_by_id(PaymentID, Invoice) of
{ok, Payment} ->
Payment when Payment /= undefined ->
{ok, {200, #{}, decode_invoice_payment(InvoiceID, Payment, Context)}};
{error, payment_not_found} ->
undefined ->
{ok, general_error(404, <<"Payment not found">>)}
end
end,
@ -164,7 +162,7 @@ prepare(OperationID = 'GetPaymentByExternalID', Req, Context) ->
prepare(OperationID = 'CapturePayment', Req, Context) ->
InvoiceID = maps:get(invoiceID, Req),
PaymentID = maps:get(paymentID, Req),
Invoice = map_result(get_invoice_by_id(InvoiceID, Context)),
Invoice = get_invoice_by_id(InvoiceID, Context),
Authorize = fun() ->
Prototypes = [
{operation, #{id => OperationID, invoice => InvoiceID, payment => PaymentID}},
@ -231,11 +229,10 @@ prepare(OperationID = 'CapturePayment', Req, Context) ->
prepare(OperationID = 'CancelPayment', Req, Context) ->
InvoiceID = maps:get(invoiceID, Req),
PaymentID = maps:get(paymentID, Req),
Invoice = map_result(get_invoice_by_id(InvoiceID, Context)),
Authorize = fun() ->
Prototypes = [
{operation, #{id => OperationID, invoice => InvoiceID, payment => PaymentID}},
{payproc, #{invoice => Invoice}}
{payproc, #{invoice => InvoiceID}}
],
{ok, capi_auth:authorize_operation(Prototypes, Context)}
end,
@ -351,24 +348,24 @@ prepare(OperationID = 'CreateRefund', Req, Context) ->
prepare(OperationID = 'GetRefunds', Req, Context) ->
InvoiceID = maps:get(invoiceID, Req),
PaymentID = maps:get(paymentID, Req),
Invoice = map_result(get_invoice_by_id(InvoiceID, Context)),
Invoice = get_invoice_by_id(InvoiceID, Context),
Authorize = fun() ->
Prototypes = [
{operation, #{id => OperationID, invoice => InvoiceID, payment => PaymentID}},
{payproc, #{invoice => Invoice}}
],
{ok, capi_auth:authorize_operation(Prototypes, Context)}
{ok, mask_invoice_notfound(capi_auth:authorize_operation(Prototypes, Context))}
end,
Process = fun() ->
capi_handler:respond_if_undefined(Invoice, general_error(404, <<"Invoice not found">>)),
case find_payment_by_id(PaymentID, Invoice) of
{ok, #payproc_InvoicePayment{refunds = Refunds}} ->
#payproc_InvoicePayment{refunds = Refunds} ->
{ok,
{200, #{}, [
capi_handler_decoder_invoicing:decode_refund(R, Context)
|| #payproc_InvoicePaymentRefund{refund = R} <- Refunds
]}};
{error, payment_not_found} ->
undefined ->
{ok, general_error(404, <<"Payment not found">>)}
end
end,
@ -377,14 +374,14 @@ prepare(OperationID = 'GetRefundByID', Req, Context) ->
InvoiceID = maps:get(invoiceID, Req),
PaymentID = maps:get(paymentID, Req),
RefundID = maps:get(refundID, Req),
Invoice = map_result(get_invoice_by_id(InvoiceID, Context)),
Payment = map_result(find_payment_by_id(PaymentID, Invoice)),
Invoice = get_invoice_by_id(InvoiceID, Context),
Payment = find_payment_by_id(PaymentID, Invoice),
Authorize = fun() ->
Prototypes = [
{operation, #{id => OperationID, invoice => InvoiceID, payment => PaymentID, refund => RefundID}},
{payproc, #{invoice => Invoice}}
],
{ok, capi_auth:authorize_operation(Prototypes, Context)}
{ok, mask_invoice_notfound(capi_auth:authorize_operation(Prototypes, Context))}
end,
Process = fun() ->
@ -392,44 +389,45 @@ prepare(OperationID = 'GetRefundByID', Req, Context) ->
capi_handler:respond_if_undefined(Payment, general_error(404, <<"Payment not found">>)),
case find_refund_by_id(RefundID, Payment) of
{ok, #payproc_InvoicePaymentRefund{refund = Refund}} ->
#payproc_InvoicePaymentRefund{refund = Refund} ->
{ok, {200, #{}, capi_handler_decoder_invoicing:decode_refund(Refund, Context)}};
{error, refund_not_found} ->
undefined ->
{ok, general_error(404, <<"Invoice payment refund not found">>)}
end
end,
{ok, #{authorize => Authorize, process => Process}};
prepare(OperationID = 'GetRefundByExternalID', Req, Context) ->
ExternalID = maps:get(externalID, Req),
InternalID = map_result(get_refund_by_external_id(ExternalID, Context)),
Invoice = map_result(
maybe(InternalID, fun({InvoiceID, _PaymentID, _RefundID}) ->
get_invoice_by_id(InvoiceID, Context)
end)
InternalID = get_refund_by_external_id(ExternalID, Context),
Invoice = maybe(
InternalID,
fun({InvoiceID, _PaymentID, _RefundID}) -> get_invoice_by_id(InvoiceID, Context) end
),
OperationPrototype = maybe(
InternalID,
fun({InvoiceID, PaymentID, RefundID}) ->
#{id => OperationID, invoice => InvoiceID, payment => PaymentID, refund => RefundID}
end
),
OperationPrototype = maybe(InternalID, fun({InvoiceID, PaymentID, RefundID}) ->
#{id => OperationID, invoice => InvoiceID, payment => PaymentID, refund => RefundID}
end),
Authorize = fun() ->
Prototypes = [
{operation, genlib:define(OperationPrototype, #{id => OperationID})},
{payproc, #{invoice => Invoice}}
],
{ok, capi_auth:authorize_operation(Prototypes, Context)}
{ok, mask_refund_notfound(capi_auth:authorize_operation(Prototypes, Context))}
end,
Process = fun() ->
capi_handler:respond_if_undefined(InternalID, general_error(404, <<"Refund not found">>)),
capi_handler:respond_if_undefined(Invoice, general_error(404, <<"Invoice not found">>)),
{_InvoiceID, PaymentID, RefundID} = InternalID,
Payment = map_result(find_payment_by_id(PaymentID, Invoice)),
Payment = find_payment_by_id(PaymentID, Invoice),
capi_handler:respond_if_undefined(Payment, general_error(404, <<"Payment not found">>)),
case find_refund_by_id(RefundID, Payment) of
{ok, #payproc_InvoicePaymentRefund{refund = Refund}} ->
#payproc_InvoicePaymentRefund{refund = Refund} ->
{ok, {200, #{}, capi_handler_decoder_invoicing:decode_refund(Refund, Context)}};
{error, refund_not_found} ->
undefined ->
{ok, general_error(404, <<"Invoice payment refund not found">>)}
end
end,
@ -437,7 +435,7 @@ prepare(OperationID = 'GetRefundByExternalID', Req, Context) ->
prepare(OperationID = 'GetChargebacks', Req, Context) ->
InvoiceID = maps:get(invoiceID, Req),
PaymentID = maps:get(paymentID, Req),
Invoice = map_result(get_invoice_by_id(InvoiceID, Context)),
Invoice = get_invoice_by_id(InvoiceID, Context),
Authorize = fun() ->
Prototypes = [
{operation, #{id => OperationID, invoice => InvoiceID, payment => PaymentID}},
@ -451,9 +449,9 @@ prepare(OperationID = 'GetChargebacks', Req, Context) ->
capi_handler_decoder_invoicing:decode_chargeback(C#payproc_InvoicePaymentChargeback.chargeback, Context)
end,
case find_payment_by_id(PaymentID, Invoice) of
{ok, #payproc_InvoicePayment{chargebacks = Chargebacks}} ->
#payproc_InvoicePayment{chargebacks = Chargebacks} ->
{ok, {200, #{}, [DecodeChargebackFun(C) || C <- Chargebacks]}};
{error, payment_not_found} ->
undefined ->
{ok, general_error(404, <<"Payment not found">>)}
end
end,
@ -462,9 +460,8 @@ prepare(OperationID = 'GetChargebackByID', Req, Context) ->
InvoiceID = maps:get(invoiceID, Req),
PaymentID = maps:get(paymentID, Req),
ChargebackID = maps:get(chargebackID, Req),
Invoice = map_result(get_invoice_by_id(InvoiceID, Context)),
Payment = map_result(find_payment_by_id(PaymentID, Invoice)),
Invoice = get_invoice_by_id(InvoiceID, Context),
Payment = find_payment_by_id(PaymentID, Invoice),
Authorize = fun() ->
Prototypes = [
{operation, #{id => OperationID, invoice => InvoiceID, payment => PaymentID}},
@ -517,23 +514,13 @@ find_payment_by_id(PaymentID, #payproc_Invoice{payments = Payments}) ->
Fun = fun(#payproc_InvoicePayment{payment = #domain_InvoicePayment{id = ID}}) ->
PaymentID == ID
end,
case find_by(Fun, genlib:define(Payments, [])) of
undefined ->
{error, payment_not_found};
Payment ->
{ok, Payment}
end.
find_by(Fun, genlib:define(Payments, [])).
find_refund_by_id(RefundID, #payproc_InvoicePayment{refunds = Refunds}) ->
Fun = fun(#payproc_InvoicePaymentRefund{refund = Refund}) ->
Refund#domain_InvoicePaymentRefund.id == RefundID
end,
case find_by(Fun, genlib:define(Refunds, [])) of
undefined ->
{error, refund_not_found};
Refund ->
{ok, Refund}
end.
find_by(Fun, genlib:define(Refunds, [])).
find_chargeback_by_id(ChargebackID, #payproc_InvoicePayment{chargebacks = Chargebacks}) ->
Fun = fun(#payproc_InvoicePaymentChargeback{chargeback = Chargeback}) ->
@ -557,18 +544,13 @@ find_by(_, []) ->
get_invoice_by_id(InvoiceID, Context) ->
case capi_handler_utils:get_invoice_by_id(InvoiceID, Context) of
{ok, Invoice} ->
{ok, Invoice};
Invoice;
{exception, #payproc_InvalidUser{}} ->
{error, invalid_user};
undefined;
{exception, #payproc_InvoiceNotFound{}} ->
{error, invoice_not_found}
undefined
end.
map_result({ok, Value}) ->
Value;
map_result(_) ->
undefined.
decrypt_payer(#{<<"payerType">> := <<"PaymentResourcePayer">>} = Payer) ->
#{<<"paymentToolToken">> := Token} = Payer,
Payer2 = maps:without([<<"paymentToolToken">>], Payer),
@ -695,23 +677,22 @@ get_refund_by_external_id(ExternalID, #{woody_context := WoodyContext} = Context
{ok, RefundID, CtxData} ->
InvoiceID = maps:get(<<"invoice_id">>, CtxData),
PaymentID = maps:get(<<"payment_id">>, CtxData),
{ok, {InvoiceID, PaymentID, RefundID}};
{error, internal_id_not_found} = Error ->
Error
{InvoiceID, PaymentID, RefundID};
{error, internal_id_not_found} ->
undefined
end.
-spec get_payment_by_external_id(binary(), capi_handler:processing_context()) ->
{ok, {binary(), binary()}}
| {error, internal_id_not_found}.
{binary(), binary()} | undefined.
get_payment_by_external_id(ExternalID, #{woody_context := WoodyContext} = Context) ->
PartyID = capi_handler_utils:get_party_id(Context),
IdempotentKey = {'CreatePayment', PartyID, ExternalID},
case capi_bender:get_internal_id(IdempotentKey, WoodyContext) of
{ok, PaymentID, CtxData} ->
InvoiceID = maps:get(<<"invoice_id">>, CtxData),
{ok, {InvoiceID, PaymentID}};
{error, internal_id_not_found} = Error ->
Error
{InvoiceID, PaymentID};
{error, internal_id_not_found} ->
undefined
end.
encode_processing_deadline(Deadline) ->
@ -758,6 +739,22 @@ refund_payment(RefundID, InvoiceID, PaymentID, RefundParams, Context) ->
Call = {invoicing, 'RefundPayment', CallArgs},
capi_handler_utils:service_call_with([user_info], Call, Context).
%% ED-206
%% When bouncer says "forbidden" we can't really tell the difference between "forbidden because
%% of no such invoice", "forbidden because client has no access to it" and "forbidden because
%% client has no permission to act on it". From the point of view of existing integrations this
%% is not great, so we have to mask specific instances of missing authorization as if specified
%% invoice / payment / refund is nonexistent.
mask_invoice_notfound(Resolution) ->
capi_handler:respond_if_forbidden(Resolution, general_error(404, <<"Invoice not found">>)).
mask_payment_notfound(Resolution) ->
capi_handler:respond_if_forbidden(Resolution, general_error(404, <<"Payment not found">>)).
mask_refund_notfound(Resolution) ->
capi_handler:respond_if_forbidden(Resolution, general_error(404, <<"Invoice payment refund not found">>)).
%%
create_sequence_id([Identifier | Rest], BenderPrefix) ->

View File

@ -89,10 +89,6 @@ prepare(OperationID = 'GetShopsForParty', Req, Context) ->
{ok, capi_auth:authorize_operation(Prototypes, Context)}
end,
Process = fun() ->
% TODO
% Here we're relying on hellgate ownership check, thus no explicit authorization.
% Hovewer we're going to drop hellgate authz eventually, then we'll need to make sure that operation
% remains authorized.
case capi_party:get_party(PartyID, Context) of
{ok, Party} ->
{ok, {200, #{}, decode_shops_map(Party#domain_Party.shops)}};
@ -111,10 +107,6 @@ prepare(OperationID = 'GetShopByIDForParty', Req, Context) ->
{ok, capi_auth:authorize_operation(Prototypes, Context)}
end,
Process = fun() ->
% TODO
% Here we're relying on hellgate ownership check, thus no explicit authorization.
% Hovewer we're going to drop hellgate authz eventually, then we'll need to make sure that operation
% remains authorized.
case capi_party:get_shop(PartyID, ShopID, Context) of
{ok, Shop} ->
{ok, {200, #{}, decode_shop(Shop)}};
@ -135,10 +127,6 @@ prepare(OperationID = 'ActivateShopForParty', Req, Context) ->
{ok, capi_auth:authorize_operation(Prototypes, Context)}
end,
Process = fun() ->
% TODO
% Here we're relying on hellgate ownership check, thus no explicit authorization.
% Hovewer we're going to drop hellgate authz eventually, then we'll need to make sure that operation
% remains authorized.
case capi_party:activate_shop(PartyID, ShopID, Context) of
ok ->
{ok, {204, #{}, undefined}};

View File

@ -12,6 +12,7 @@
-export([service_call_with/3]).
-export([service_call/2]).
-export([map_service_result/1]).
-export([get_auth_context/1]).
-export([get_user_info/1]).
@ -99,6 +100,13 @@ service_call_with_([], Call, Context) ->
service_call({ServiceName, Function, Args}, #{woody_context := WoodyContext}) ->
capi_woody_client:call_service(ServiceName, Function, Args, WoodyContext).
-spec map_service_result({ok, Result} | {exception, woody_error:business_error()}) ->
Result | undefined.
map_service_result({ok, Result}) ->
Result;
map_service_result({exception, _}) ->
undefined.
-spec get_auth_context(processing_context()) -> any().
get_auth_context(#{swagger_context := #{auth_context := AuthContext}}) ->
AuthContext.

View File

@ -33,7 +33,7 @@ init([]) ->
{LogicHandler, []} = get_logic_handler_info(#{party_client => PartyClient}),
AdditionalRoutes = [{'_', [erl_health_handle:get_route(HealthCheck), get_prometheus_route()]}],
SwaggerHandlerOpts = genlib_app:env(?APP, swagger_handler_opts, #{}),
SwaggerSpec = capi_swagger_server:child_spec({AdditionalRoutes, LogicHandler, SwaggerHandlerOpts}),
SwaggerSpec = capi_swagger_server:child_spec(AdditionalRoutes, LogicHandler, SwaggerHandlerOpts),
{ok,
{
{one_for_all, 0, 1},
@ -43,12 +43,7 @@ init([]) ->
-spec get_logic_handler_info(capi_handler:handler_opts()) ->
{Handler :: swag_server:logic_handler(_), [Spec :: supervisor:child_spec()] | []}.
get_logic_handler_info(HandlerOpts) ->
case genlib_app:env(?APP, service_type) of
real ->
{{capi_handler, HandlerOpts}, []};
undefined ->
exit(undefined_service_type)
end.
{{capi_handler, HandlerOpts}, []}.
-spec enable_health_logging(erl_health:check()) -> erl_health:check().
enable_health_logging(Check) ->

View File

@ -1,6 +1,6 @@
-module(capi_swagger_server).
-export([child_spec/1]).
-export([child_spec/3]).
-define(APP, capi).
-define(DEFAULT_ACCEPTORS_POOLSIZE, 100).
@ -8,16 +8,9 @@
-define(DEFAULT_PORT, 8080).
-define(RANCH_REF, ?MODULE).
-define(START_TIME_TAG, processing_start_time).
-type params() :: {
cowboy_router:routes(),
swag_server:logic_handler(_),
swag_server_router:swagger_handler_opts()
}.
-spec child_spec(params()) -> supervisor:child_spec().
child_spec({AdditionalRoutes, LogicHandler, SwaggerHandlerOpts}) ->
-spec child_spec(cowboy_router:routes(), swag_server:logic_handler(_), swag_server_router:swagger_handler_opts()) ->
supervisor:child_spec().
child_spec(AdditionalRoutes, LogicHandler, SwaggerHandlerOpts) ->
{Transport, TransportOpts} = get_socket_transport(),
CowboyOpts = get_cowboy_config(AdditionalRoutes, LogicHandler, SwaggerHandlerOpts),
GsTimeout = genlib_app:env(?APP, graceful_shutdown_timeout, 5000),

View File

@ -3,6 +3,7 @@
-include_lib("common_test/include/ct.hrl").
-include_lib("capi_dummy_data.hrl").
-include_lib("damsel/include/dmsl_payment_processing_thrift.hrl").
-include_lib("jose/include/jose_jwk.hrl").
-export([all/0]).
@ -22,6 +23,15 @@
authorization_bad_token_error_test/1
]).
-export([
get_party_forbidden_notfound/1,
get_invoice_forbidden_notfound/1,
get_invoice_by_external_id_forbidden_notfound/1,
get_payment_by_external_id_forbidden_notfound/1,
get_refund_by_external_id_forbidden_notfound/1,
get_customer_forbidden_notfound/1
]).
-define(emptyresp(Code), {error, {Code, #{}}}).
-type test_case_name() :: atom().
@ -37,7 +47,8 @@ init([]) ->
-spec all() -> [{group, test_case_name()}].
all() ->
[
{group, authorization}
{group, authorization},
{group, forbidden_masking}
].
-spec groups() -> [{group_name(), list(), [test_case_name()]}].
@ -47,6 +58,14 @@ groups() ->
authorization_error_no_header_test,
authorization_error_no_permission_test,
authorization_bad_token_error_test
]},
{forbidden_masking, [], [
get_party_forbidden_notfound,
get_invoice_forbidden_notfound,
get_invoice_by_external_id_forbidden_notfound,
get_payment_by_external_id_forbidden_notfound,
get_refund_by_external_id_forbidden_notfound,
get_customer_forbidden_notfound
]}
].
@ -63,42 +82,45 @@ end_per_suite(C) ->
_ = [application:stop(App) || App <- proplists:get_value(apps, C)],
ok.
-spec init_per_group(group_name(), config()) -> config().
init_per_group(_, Config) ->
SupPid = capi_ct_helper:start_mocked_service_sup(?MODULE),
Apps1 = capi_ct_helper_tk:mock_service(capi_ct_helper_tk:user_session_handler(), SupPid),
[{group_apps, Apps1}, {group_test_sup, SupPid} | Config].
-spec end_per_group(group_name(), config()) -> _.
end_per_group(_Group, C) ->
_ = capi_utils:maybe(?config(group_test_sup, C), fun capi_ct_helper:stop_mocked_service_sup/1),
ok.
-spec init_per_testcase(test_case_name(), config()) -> config().
init_per_testcase(authorization_error_no_permission_test, C) ->
SupPid = capi_ct_helper:start_mocked_service_sup(?MODULE),
Apps0 = capi_ct_helper_bouncer:mock_arbiter(capi_ct_helper_bouncer:judge_always_forbidden(), SupPid),
[{test_apps, Apps0}, {test_sup, SupPid} | C];
init_per_testcase(_Name, C) ->
SupPid = capi_ct_helper:start_mocked_service_sup(?MODULE),
Apps0 = capi_ct_helper_bouncer:mock_arbiter(capi_ct_helper_bouncer:judge_always_allowed(), SupPid),
[{test_apps, Apps0}, {test_sup, SupPid} | C].
[{test_sup, capi_ct_helper:start_mocked_service_sup(?MODULE)} | C].
-spec end_per_testcase(test_case_name(), config()) -> _.
end_per_testcase(_Name, C) ->
capi_ct_helper:stop_mocked_service_sup(?config(test_sup, C)),
ok.
-spec init_per_group(group_name(), config()) -> config().
init_per_group(GroupName, Config) ->
SupPid = capi_ct_helper:start_mocked_service_sup(?MODULE),
Apps1 = capi_ct_helper_tk:mock_service(capi_ct_helper_tk:user_session_handler(), SupPid),
Apps2 =
case GroupName of
forbidden_masking ->
capi_ct_helper_bouncer:mock_arbiter(capi_ct_helper_bouncer:judge_always_forbidden(), SupPid);
_ ->
[]
end,
[{group_apps, Apps1 ++ Apps2}, {group_test_sup, SupPid} | Config].
-spec end_per_group(group_name(), config()) -> _.
end_per_group(_Group, C) ->
_ = capi_utils:maybe(?config(group_test_sup, C), fun capi_ct_helper:stop_mocked_service_sup/1),
ok.
%%% Tests
-spec authorization_error_no_header_test(config()) -> _.
authorization_error_no_header_test(_Config) ->
authorization_error_no_header_test(Config) ->
Token = <<>>,
_ = capi_ct_helper_bouncer:mock_arbiter(capi_ct_helper_bouncer:judge_always_allowed(), Config),
?emptyresp(401) = capi_client_categories:get_categories(capi_ct_helper:get_context(Token)).
-spec authorization_error_no_permission_test(config()) -> _.
authorization_error_no_permission_test(_Config) ->
authorization_error_no_permission_test(Config) ->
Token = capi_ct_helper:issue_token(unlimited),
_ = capi_ct_helper_bouncer:mock_arbiter(capi_ct_helper_bouncer:judge_always_forbidden(), Config),
?emptyresp(401) = capi_client_parties:get_my_party(capi_ct_helper:get_context(Token)).
-spec authorization_bad_token_error_test(config()) -> _.
@ -128,3 +150,68 @@ issue_dummy_token(ACL, Config) ->
JWT = jose_jwt:sign(BadJWK, #{<<"alg">> => <<"RS256">>, <<"kid">> => KID}, Claims),
{_Modules, Token} = jose_jws:compact(JWT),
{ok, Token}.
%%%
-spec get_party_forbidden_notfound(config()) -> _.
-spec get_invoice_forbidden_notfound(config()) -> _.
-spec get_invoice_by_external_id_forbidden_notfound(config()) -> _.
-spec get_payment_by_external_id_forbidden_notfound(config()) -> _.
-spec get_refund_by_external_id_forbidden_notfound(config()) -> _.
-spec get_customer_forbidden_notfound(config()) -> _.
get_party_forbidden_notfound(Config) ->
PartyID = <<"NONEXISTENT">>,
_ = capi_ct_helper:mock_services(
[
{party_management, fun
('GetRevision', _) -> {ok, ?INTEGER};
('Checkout', _) -> {throwing, #payproc_PartyNotFound{}}
end}
],
Config
),
{error, {404, _}} = capi_client_parties:get_party_by_id(mk_context(), PartyID).
get_invoice_forbidden_notfound(Config) ->
InvoiceID = <<"NONEXISTENT">>,
_ = capi_ct_helper:mock_services(
[{invoicing, fun('Get', _) -> {throwing, #payproc_InvoiceNotFound{}} end}],
Config
),
{error, {404, _}} = capi_client_invoices:get_invoice_by_id(mk_context(), InvoiceID).
get_invoice_by_external_id_forbidden_notfound(Config) ->
ExternalID = <<"NEVERWAS">>,
_ = capi_ct_helper:mock_services(
[{bender, fun('GetInternalID', _) -> {throwing, capi_ct_helper_bender:no_internal_id()} end}],
Config
),
{error, {404, _}} = capi_client_invoices:get_invoice_by_external_id(mk_context(), ExternalID).
get_payment_by_external_id_forbidden_notfound(Config) ->
ExternalID = <<"NEVERWAS">>,
_ = capi_ct_helper:mock_services(
[{bender, fun('GetInternalID', _) -> {throwing, capi_ct_helper_bender:no_internal_id()} end}],
Config
),
{error, {404, _}} = capi_client_payments:get_payment_by_external_id(mk_context(), ExternalID).
get_refund_by_external_id_forbidden_notfound(Config) ->
ExternalID = <<"WHEREDIDYOUGETIT">>,
_ = capi_ct_helper:mock_services(
[{bender, fun('GetInternalID', _) -> {throwing, capi_ct_helper_bender:no_internal_id()} end}],
Config
),
{error, {404, _}} = capi_client_payments:get_refund_by_external_id(mk_context(), ExternalID).
get_customer_forbidden_notfound(Config) ->
CustomerID = <<"NONEXISTENT">>,
_ = capi_ct_helper:mock_services(
[{customer_management, fun('Get', _) -> {throwing, #payproc_CustomerNotFound{}} end}],
Config
),
{error, {404, _}} = capi_client_customers:get_customer_by_id(mk_context(), CustomerID).
mk_context() ->
capi_ct_helper:get_context(capi_ct_helper:issue_token(unlimited)).

View File

@ -45,7 +45,10 @@ init_suite(Module, Config, CapiEnv) ->
{
'Repository',
{dmsl_domain_config_thrift, 'Repository'},
fun('Checkout', _) -> {ok, ?SNAPSHOT} end
fun
('Checkout', _) -> {ok, ?SNAPSHOT};
('PullRange', _) -> {ok, #{}}
end
}
],
SupPid