mirror of
https://github.com/valitydev/capi-v2.git
synced 2024-11-06 01:55:20 +00:00
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:
parent
96da4b830b
commit
2aa76e2a8b
@ -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).
|
||||
|
@ -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}) ->
|
||||
|
@ -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},
|
||||
|
@ -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),
|
||||
|
@ -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) ->
|
||||
|
@ -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">>)).
|
||||
|
@ -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">>)).
|
||||
|
@ -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) ->
|
||||
|
@ -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}};
|
||||
|
@ -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.
|
||||
|
@ -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) ->
|
||||
|
@ -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),
|
||||
|
@ -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)).
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user