FF-73: call wallets from wapi using thrift API (#75)

* fix test

* FF-73: init

* FF-73

* FF-73: wapi cut off utils

* FF-73: cut off wallet from wapi_ff_backend

* FF-73: encode to marshal

* impl thrift changes

* FF-73: add test

* idempotense wallet

* uncomment options

* fix bugs

* del junk

* del maybe_marshal

* little change

* Remove races in tests

* refactored, added wallet mock tests

* Fix missed variable

* fixed, added thrift handler

* merge master

* daylizer fix

* FF-94: Wapi identity thrift (#88)

* added identity create and get methods

* added identity challenges back, get events back

* added tests

* nano

* added some fixed

* fixed tests

* fixed tests

* refactored

* refactored

* added new suite, some fixes

* added more tests

* fixed types

* minor

* fixed tests

* fixed linter

* updated proto

* fixed

* updated proto

* fixed

* fixed

* nano

Co-authored-by: Артем <WWW_cool@inbox.ru>
Co-authored-by: Andrey Fadeev <me@ciiol.net>
This commit is contained in:
Boris 2020-02-14 18:54:55 +03:00 committed by GitHub
parent 208e9c538d
commit 2b335fa3d4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 2338 additions and 329 deletions

View File

@ -120,7 +120,9 @@ start_app(wapi_woody_client = AppName) ->
{service_urls, #{
cds_storage => "http://cds:8022/v1/storage",
identdoc_storage => "http://cds:8022/v1/identity_document_storage",
fistful_stat => "http://fistful-magista:8022/stat"
fistful_stat => "http://fistful-magista:8022/stat",
fistful_wallet => "http://localhost:8022/v1/wallet",
fistful_identity => "http://localhost:8022/v1/identity"
}},
{service_retries, #{
fistful_stat => #{

View File

@ -35,6 +35,8 @@ handle_function_('Create', [IdentityParams], Opts) ->
woody_error:raise(business, #fistful_IdentityClassNotFound{});
{error, {inaccessible, _}} ->
woody_error:raise(business, #fistful_PartyInaccessible{});
{error, exists} ->
woody_error:raise(business, #fistful_IDExists{});
{error, Error} ->
woody_error:raise(system, {internal, result_unexpected, woody_error:format_details(Error)})
end;
@ -84,6 +86,7 @@ handle_function_('GetChallenges', [ID], _Opts) ->
{error, notfound} ->
woody_error:raise(business, #fistful_IdentityNotFound{})
end;
handle_function_('GetEvents', [IdentityID, RangeParams], _Opts) ->
Range = ff_identity_codec:unmarshal(range, RangeParams),
case ff_identity_machine:events(IdentityID, Range) of

View File

@ -4,26 +4,65 @@
-include_lib("fistful_proto/include/ff_proto_wallet_thrift.hrl").
-export([marshal_wallet/1]).
-export([unmarshal_wallet/1]).
-export([unmarshal_wallet_params/1]).
-export([marshal/2]).
-export([unmarshal/2]).
%% API
-spec marshal_wallet(ff_wallet:wallet()) ->
ff_proto_wallet_thrift:'Wallet'().
marshal_wallet(Wallet) ->
#wlt_Wallet{
name = marshal(string, ff_wallet:name(Wallet)),
blocking = marshal(blocking, ff_wallet:blocking(Wallet)),
account = marshal(account, ff_wallet:account(Wallet)),
external_id = marshal(id, ff_wallet:external_id(Wallet))
}.
-spec unmarshal_wallet(ff_proto_wallet_thrift:'Wallet'()) ->
ff_wallet:wallet().
unmarshal_wallet(#wlt_Wallet{
name = Name,
external_id = ExternalID
}) ->
genlib_map:compact(#{
name => unmarshal(string, Name),
external_id => unmarshal(id, ExternalID)
}).
-spec unmarshal_wallet_params(ff_proto_wallet_thrift:'WalletParams'()) ->
ff_wallet_machine:params().
unmarshal_wallet_params(#wlt_WalletParams{
id = ID,
account_params = AccountParams,
name = Name,
external_id = ExternalID
}) ->
{IdentityID, Currency} = unmarshal(account_params, AccountParams),
genlib_map:compact(#{
id => unmarshal(id, ID),
name => unmarshal(string, Name),
identity => IdentityID,
currency => Currency,
external_id => maybe_unmarshal(id, ExternalID)
}).
-spec marshal(ff_codec:type_name(), ff_codec:decoded_value()) ->
ff_codec:encoded_value().
marshal(change, {created, Wallet}) ->
{created, marshal(wallet, Wallet)};
{created, marshal_wallet(Wallet)};
marshal(change, {account, AccountChange}) ->
{account, marshal(account_change, AccountChange)};
marshal(wallet, Wallet) ->
Name = maps:get(name, Wallet, undefined),
ExternalID = maps:get(external_id, Wallet, undefined),
#wlt_Wallet{
name = marshal(string, Name),
external_id = marshal(id, ExternalID)
};
marshal(ctx, Ctx) ->
marshal(context, Ctx);
marshal(T, V) ->
ff_codec:marshal(T, V).
@ -46,14 +85,14 @@ unmarshal(change, {created, Wallet}) ->
unmarshal(change, {account, AccountChange}) ->
{account, unmarshal(account_change, AccountChange)};
unmarshal(wallet, #wlt_Wallet{
name = Name,
external_id = ExternalID
unmarshal(account_params, #account_AccountParams{
identity_id = IdentityID,
symbolic_code = SymbolicCode
}) ->
genlib_map:compact(#{
name => unmarshal(string, Name),
external_id => unmarshal(id, ExternalID)
});
{unmarshal(id, IdentityID), unmarshal(string, SymbolicCode) };
unmarshal(ctx, Ctx) ->
maybe_unmarshal(context, Ctx);
unmarshal(T, V) ->
ff_codec:unmarshal(T, V).

View File

@ -11,6 +11,7 @@
%%
-spec handle_function(woody:func(), woody:args(), woody:options()) ->
{ok, woody:result()} | no_return().
handle_function(Func, Args, Opts) ->
scoper:scope(wallet, #{},
fun() ->
@ -24,8 +25,8 @@ handle_function(Func, Args, Opts) ->
handle_function_('Create', [Params], Opts) ->
WalletID = Params#wlt_WalletParams.id,
case ff_wallet_machine:create(
decode(wallet_params, Params),
decode(context, Params#wlt_WalletParams.context))
ff_wallet_codec:unmarshal_wallet_params(Params),
ff_wallet_codec:unmarshal(ctx, Params#wlt_WalletParams.context))
of
ok ->
handle_function_('Get', [WalletID], Opts);
@ -42,45 +43,15 @@ handle_function_('Create', [Params], Opts) ->
handle_function_('Get', [ID], _Opts) ->
case ff_wallet_machine:get(ID) of
{ok, Machine} ->
{ok, encode(wallet, {ID, Machine})};
Wallet = ff_wallet_machine:wallet(Machine),
Ctx = ff_machine:ctx(Machine),
CreatedAt = ff_machine:created(Machine),
Response = ff_wallet_codec:marshal_wallet(Wallet),
{ok, Response#wlt_Wallet{
id = ff_wallet_codec:marshal(id, ID),
created_at = ff_wallet_codec:marshal(timestamp, CreatedAt),
context = ff_wallet_codec:marshal(ctx, Ctx)
}};
{error, notfound} ->
woody_error:raise(business, #fistful_WalletNotFound{})
end.
encode(wallet, {ID, Machine}) ->
Wallet = ff_wallet_machine:wallet(Machine),
Ctx = ff_wallet_machine:ctx(Machine),
#wlt_Wallet{
id = ID,
name = ff_wallet:name(Wallet),
blocking = ff_wallet:blocking(Wallet),
account = encode(account, ff_wallet:account(Wallet)),
external_id = ff_wallet:external_id(Wallet),
context = encode(context, Ctx)
};
encode(context, Ctx) ->
ff_entity_context_codec:marshal(Ctx);
encode(account, Account) ->
#account_Account{
id = ff_account:id(Account),
identity = ff_account:identity(Account),
currency = encode(currency, ff_account:currency(Account)),
accounter_account_id = ff_account:accounter_account_id(Account)
};
encode(currency, CurrencyId) ->
#'CurrencyRef'{symbolic_code = CurrencyId}.
decode(wallet_params, Params) ->
AccountParams = Params#wlt_WalletParams.account_params,
#{
id => Params#wlt_WalletParams.id,
name => Params#wlt_WalletParams.name,
identity => AccountParams#account_AccountParams.identity_id,
currency => AccountParams#account_AccountParams.symbolic_code,
external_id => Params#wlt_WalletParams.external_id
};
decode(context, undefined) ->
undefined;
decode(context, Ctx) ->
ff_entity_context_codec:unmarshal(Ctx).

View File

@ -403,6 +403,7 @@ create_destination(IID, C) ->
process_withdrawal(WalID, DestID) ->
WdrID = generate_id(),
ok = ff_withdrawal_machine:create(
#{id => WdrID, wallet_id => WalID, destination_id => DestID, body => {4240, <<"RUB">>}},
ff_entity_context:new()

View File

@ -196,7 +196,7 @@ block_party(Party, C) ->
construct_wallet_params(ID, IdentityID, Currency) ->
#wlt_WalletParams{
id = ID,
id = ID,
name = <<"Valet">>,
account_params = #account_AccountParams{
identity_id = IdentityID,
@ -205,7 +205,7 @@ construct_wallet_params(ID, IdentityID, Currency) ->
}.
construct_wallet_params(ID, IdentityID, Currency, ExternalID) ->
#wlt_WalletParams{
id = ID,
id = ID,
name = <<"Valet">>,
external_id = ExternalID,
account_params = #account_AccountParams{
@ -215,7 +215,7 @@ construct_wallet_params(ID, IdentityID, Currency, ExternalID) ->
}.
construct_wallet_params(ID, IdentityID, Currency, ExternalID, Ctx) ->
#wlt_WalletParams{
id = ID,
id = ID,
name = <<"Valet">>,
external_id = ExternalID,
context = Ctx,

View File

@ -117,6 +117,7 @@ deposit_via_admin_ok(C) ->
currency = #'CurrencyRef'{symbolic_code = <<"RUB">>},
resource = {internal, #src_Internal{details = <<"Infinite source of cash">>}}
}]),
SrcID = Src1#src_Source.id,
{authorized, #src_Authorized{}} = ct_helper:await(
{authorized, #src_Authorized{}},
@ -164,6 +165,7 @@ deposit_via_admin_fails(C) ->
currency = #'CurrencyRef'{symbolic_code = <<"RUB">>},
resource = {internal, #src_Internal{details = <<"Infinite source of cash">>}}
}]),
SrcID = Src1#src_Source.id,
{authorized, #src_Authorized{}} = ct_helper:await(
{authorized, #src_Authorized{}},
@ -206,6 +208,7 @@ deposit_via_admin_amount_fails(C) ->
SrcID = genlib:unique(),
DepID = genlib:unique(),
% Create source
{ok, _Src1} = call_admin('CreateSource', [#ff_admin_SourceParams{
id = SrcID,
name = <<"HAHA NO">>,
@ -213,6 +216,7 @@ deposit_via_admin_amount_fails(C) ->
currency = #'CurrencyRef'{symbolic_code = <<"RUB">>},
resource = {internal, #src_Internal{details = <<"Infinite source of cash">>}}
}]),
{authorized, #src_Authorized{}} = ct_helper:await(
{authorized, #src_Authorized{}},
fun () ->
@ -249,6 +253,7 @@ deposit_via_admin_currency_fails(C) ->
currency = #'CurrencyRef'{symbolic_code = <<"RUB">>},
resource = {internal, #src_Internal{details = <<"Infinite source of cash">>}}
}]),
SrcID = Src1#src_Source.id,
{authorized, #src_Authorized{}} = ct_helper:await(
{authorized, #src_Authorized{}},

View File

@ -73,8 +73,8 @@
-spec blocking(wallet()) ->
blocking().
account(#{account := V}) ->
V.
account(Wallet) ->
maps:get(account, Wallet, undefined).
id(Wallet) ->
ff_account:id(account(Wallet)).

View File

@ -15,7 +15,16 @@
-type st() :: ff_machine:st(wallet()).
-type params() :: #{
id := id(),
identity := ff_identity_machine:id(),
name := binary(),
currency := ff_currency:id(),
external_id => id()
}.
-export_type([id/0]).
-export_type([params/0]).
-export([create/2]).
-export([get/1]).
@ -55,14 +64,6 @@ ctx(St) ->
%%
-type params() :: #{
id := id(),
identity := ff_identity_machine:id(),
name := binary(),
currency := ff_currency:id(),
external_id => id()
}.
-spec create(params(), ctx()) ->
ok | {error, exists | ff_wallet:create_error() }.

View File

@ -0,0 +1,60 @@
-module(wapi_access_backend).
-include_lib("fistful_proto/include/ff_proto_identity_thrift.hrl").
-include_lib("fistful_proto/include/ff_proto_wallet_thrift.hrl").
-export([check_resource/3]).
-export([check_resource_by_id/3]).
-type id() :: binary().
-type resource_type() :: identity | wallet.
-type handler_context() :: wapi_handler:context().
-type data() ::
ff_proto_identity_thrift:'Identity'() |
ff_proto_wallet_thrift:'Wallet'().
-define(CTX_NS, <<"com.rbkmoney.wapi">>).
%% Pipeline
-spec check_resource(resource_type(), data(), handler_context()) ->
ok | {error, unauthorized}.
check_resource(Resource, Data, Context) ->
Owner = get_context(Resource, Data),
check_resource_access(is_resource_owner(Owner, Context)).
-spec check_resource_by_id(resource_type(), id(), handler_context()) ->
ok | {error, unauthorized}.
check_resource_by_id(Resource, ID, Context) ->
Owner = get_context_by_id(Resource, ID, Context),
check_resource_access(is_resource_owner(Owner, Context)).
%%
%% Internal
%%
get_context_by_id(Resource = identity, IdentityID, WoodyCtx) ->
Request = {fistful_identity, 'Get', [IdentityID]},
{ok, Identity} = wapi_handler_utils:service_call(Request, WoodyCtx),
get_context(Resource, Identity);
get_context_by_id(Resource = wallet, WalletID, WoodyCtx) ->
Request = {fistful_wallet, 'Get', [WalletID]},
{ok, Wallet} = wapi_handler_utils:service_call(Request, WoodyCtx),
get_context(Resource, Wallet).
get_context(identity, Identity) ->
#idnt_Identity{context = Ctx} = Identity,
Context = ff_codec:unmarshal(context, Ctx),
wapi_backend_utils:get_from_ctx(<<"owner">>, Context);
get_context(wallet, Wallet) ->
#wlt_Wallet{context = Ctx} = Wallet,
Context = ff_codec:unmarshal(context, Ctx),
wapi_backend_utils:get_from_ctx(<<"owner">>, Context).
is_resource_owner(Owner, HandlerCtx) ->
Owner =:= wapi_handler_utils:get_owner(HandlerCtx).
check_resource_access(true) -> ok;
check_resource_access(false) -> {error, unauthorized}.

View File

@ -0,0 +1,97 @@
-module(wapi_backend_utils).
-define(EXTERNAL_ID, <<"externalID">>).
-define(CTX_NS, <<"com.rbkmoney.wapi">>).
-define(PARAMS_HASH, <<"params_hash">>).
-define(BENDER_DOMAIN, <<"wapi">>).
%% Context
-type md() :: ff_entity_context:md().
-type context() :: ff_entity_context:context().
-type handler_context() :: wapi_handler:context().
-type id() :: binary().
-type hash() :: integer().
-type params() :: map().
-type gen_type() :: identity | identity_challenge | wallet.
-export([gen_id/3]).
-export([gen_id/4]).
-export([make_ctx/2]).
-export([add_to_ctx/2]).
-export([add_to_ctx/3]).
-export([get_from_ctx/2]).
%% Pipeline
-spec gen_id(gen_type(), params(), handler_context()) ->
{ok, id()} | {error, {external_id_conflict, id()}}.
gen_id(Type, Params, Context) ->
ExternalID = maps:get(?EXTERNAL_ID, Params, undefined),
Hash = create_params_hash(Params),
gen_id(Type, ExternalID, Hash, Context).
-spec gen_id(gen_type(), id() | undefined, hash(), handler_context()) ->
{ok, id()} | {error, {external_id_conflict, id()}}.
gen_id(Type, ExternalID, Hash, Context) ->
PartyID = wapi_handler_utils:get_owner(Context),
IdempotentKey = bender_client:get_idempotent_key(?BENDER_DOMAIN, Type, PartyID, ExternalID),
gen_id_by_type(Type, IdempotentKey, Hash, Context).
%@TODO: Bring back later
%gen_id_by_type(withdrawal = Type, IdempotentKey, Hash, Context) ->
% gen_snowflake_id(Type, IdempotentKey, Hash, Context);
gen_id_by_type(Type, IdempotentKey, Hash, Context) ->
gen_sequence_id(Type, IdempotentKey, Hash, Context).
%@TODO: Bring back later
%gen_snowflake_id(_Type, IdempotentKey, Hash, #{woody_context := WoodyCtx}) ->
% bender_client:gen_by_snowflake(IdempotentKey, Hash, WoodyCtx).
gen_sequence_id(Type, IdempotentKey, Hash, #{woody_context := WoodyCtx}) ->
BinType = atom_to_binary(Type, utf8),
bender_client:gen_by_sequence(IdempotentKey, BinType, Hash, WoodyCtx).
-spec make_ctx(params(), handler_context()) ->
context().
make_ctx(Params, Context) ->
#{?CTX_NS => genlib_map:compact(#{
<<"owner">> => wapi_handler_utils:get_owner(Context),
<<"metadata">> => maps:get(<<"metadata">>, Params, undefined),
?PARAMS_HASH => create_params_hash(Params)
})}.
-spec add_to_ctx({md(), md() | undefined} | list() | map(), context()) ->
context().
add_to_ctx({Key, Value}, Context) ->
add_to_ctx(Key, Value, Context);
add_to_ctx(Map, Context = #{?CTX_NS := Ctx}) when is_map(Map) ->
Context#{?CTX_NS => maps:merge(Ctx, Map)};
add_to_ctx(KVList, Context) when is_list(KVList) ->
lists:foldl(
fun({K, V}, Ctx) -> add_to_ctx(K, V, Ctx) end,
Context,
KVList
).
-spec add_to_ctx(md(), md() | undefined, context()) ->
context().
add_to_ctx(_Key, undefined, Context) ->
Context;
add_to_ctx(Key, Value, Context = #{?CTX_NS := Ctx}) ->
Context#{?CTX_NS => Ctx#{Key => Value}}.
-spec get_from_ctx(md(), context()) ->
md().
get_from_ctx(Key, #{?CTX_NS := Ctx}) ->
maps:get(Key, Ctx, undefined).
-spec create_params_hash(term()) ->
integer().
create_params_hash(Value) ->
erlang:phash2(Value).

View File

@ -46,6 +46,7 @@
%% API
-define(request_result, wapi_req_result).
-define(APP, wapi).
-spec handle_request(tag(), operation_id(), req_data(), swagger_context(), opts()) ->
request_result().
@ -71,7 +72,7 @@ process_request(Tag, OperationID, Req, SwagContext, Opts, WoodyContext) ->
ok = ff_context:save(create_ff_context(WoodyContext, Opts)),
Context = create_handler_context(SwagContext, WoodyContext),
Handler = get_handler(Tag),
Handler = get_handler(Tag, genlib_app:env(?APP, transport)),
case wapi_auth:authorize_operation(OperationID, Req, Context) of
{ok, AuthDetails} ->
ok = logger:info("Operation ~p authorized via ~p", [OperationID, AuthDetails]),
@ -94,8 +95,9 @@ process_request(Tag, OperationID, Req, SwagContext, Opts, WoodyContext) ->
throw_result(Res) ->
erlang:throw({?request_result, Res}).
get_handler(wallet) -> wapi_wallet_handler;
get_handler(payres) -> wapi_payres_handler.
get_handler(wallet, thrift) -> wapi_wallet_thrift_handler;
get_handler(wallet, _) -> wapi_wallet_handler;
get_handler(payres, _) -> wapi_payres_handler.
-spec create_woody_context(tag(), req_data(), wapi_auth:context(), opts()) ->
woody_context:ctx().
@ -113,8 +115,6 @@ attach_deadline(undefined, Context) ->
attach_deadline(Deadline, Context) ->
woody_context:set_deadline(Deadline, Context).
-define(APP, wapi).
collect_user_identity(AuthContext, _Opts) ->
genlib_map:compact(#{
id => wapi_auth:get_subject_id(AuthContext),

View File

@ -0,0 +1,475 @@
-module(wapi_identity_backend).
-type handler_context() :: wapi_handler:context().
-type response_data() :: wapi_handler:response_data().
-type params() :: map().
-type id() :: binary().
-type status() :: binary().
-type result(T, E) :: {ok, T} | {error, E}.
-export([create_identity/2]).
-export([get_identity/2]).
-export([get_identities/2]).
-export([create_identity_challenge/3]).
-export([get_identity_challenge/3]).
-export([get_identity_challenges/3]).
-export([get_identity_challenge_events/2]).
-export([get_identity_challenge_event/2]).
-include_lib("fistful_proto/include/ff_proto_identity_thrift.hrl").
-include_lib("fistful_proto/include/ff_proto_base_thrift.hrl").
%% Pipeline
-spec get_identity(id(), handler_context()) ->
{ok, response_data()} |
{error, {identity, notfound}} |
{error, {identity, unauthorized}} .
get_identity(IdentityID, HandlerContext) ->
Request = {fistful_identity, 'Get', [IdentityID]},
case service_call(Request, HandlerContext) of
{ok, IdentityThrift} ->
case wapi_access_backend:check_resource(identity, IdentityThrift, HandlerContext) of
ok ->
{ok, unmarshal(identity, IdentityThrift)};
{error, unauthorized} ->
{error, {identity, unauthorized}}
end;
{exception, #fistful_IdentityNotFound{}} ->
{error, {identity, notfound}}
end.
-spec create_identity(params(), handler_context()) -> result(map(),
{provider, notfound} |
{identity_class, notfound} |
{external_id_conflict, id()} |
inaccessible |
_Unexpected
).
create_identity(Params, HandlerContext) ->
case create_id(identity, Params, HandlerContext) of
{ok, ID} ->
create_identity(ID, Params, HandlerContext);
{error, {external_id_conflict, _}} = Error ->
Error
end.
create_identity(ID, Params, HandlerContext) ->
IdentityParams = marshal(identity_params, {
Params#{<<"id">> => ID},
wapi_handler_utils:get_owner(HandlerContext),
create_context(Params, HandlerContext)
}),
Request = {fistful_identity, 'Create', [IdentityParams]},
case service_call(Request, HandlerContext) of
{ok, Identity} ->
{ok, unmarshal(identity, Identity)};
{exception, #fistful_ProviderNotFound{}} ->
{error, {provider, notfound}};
{exception, #fistful_IdentityClassNotFound{}} ->
{error, {identity_class, notfound}};
{exception, #fistful_PartyInaccessible{}} ->
{error, inaccessible};
{exception, #fistful_IDExists{}} ->
get_identity(ID, HandlerContext);
{exception, Details} ->
{error, Details}
end.
-spec get_identities(params(), handler_context()) -> no_return().
get_identities(_Params, _Context) ->
wapi_handler_utils:throw_not_implemented().
-spec create_identity_challenge(id(), params(), handler_context()) -> result(map(),
{identity, notfound} |
{identity, unauthorized} |
{challenge, pending} |
{challenge, {class, notfound}} |
{challenge, {proof, notfound}} |
{challenge, {proof, insufficient}} |
{challenge, level} |
{challenge, conflict} |
{external_id_conflict, id()}
).
create_identity_challenge(IdentityID, Params, HandlerContext) ->
case create_id(identity_challenge, Params, HandlerContext) of
{ok, ID} ->
create_identity_challenge(ID, IdentityID, Params, HandlerContext);
{error, {external_id_conflict, _}} = Error ->
Error
end.
create_identity_challenge(ChallengeID, IdentityID, Params, HandlerContext) ->
case wapi_access_backend:check_resource_by_id(identity, IdentityID, HandlerContext) of
ok ->
ChallengeParams = marshal(challenge_params, {ChallengeID, Params}),
Request = {fistful_identity, 'StartChallenge', [IdentityID, ChallengeParams]},
case service_call(Request, HandlerContext) of
{ok, Challenge} ->
{ok, unmarshal(challenge, {Challenge, HandlerContext})};
{exception, #fistful_IdentityNotFound{}} ->
{error, {identity, notfound}};
{exception, #fistful_ChallengePending{}} ->
{error, {challenge, pending}};
{exception, #fistful_ChallengeClassNotFound{}} ->
{error, {challenge, {class, notfound}}};
{exception, #fistful_ProofNotFound{}} ->
{error, {challenge, {proof, notfound}}};
{exception, #fistful_ProofInsufficient{}} ->
{error, {challenge, {proof, insufficient}}};
{exception, #fistful_ChallengeLevelIncorrect{}} ->
{error, {challenge, level}};
{exception, #fistful_ChallengeConflict{}} ->
{error, {challenge, conflict}};
{exception, Details} ->
{error, Details}
end;
{error, unauthorized} ->
{error, {identity, unauthorized}}
end.
-spec get_identity_challenge(id(), id(), handler_context()) -> result(map(),
{identity, notfound} |
{identity, unauthorized} |
{challenge, notfound}
).
get_identity_challenge(IdentityID, ChallengeID, HandlerContext) ->
case wapi_access_backend:check_resource_by_id(identity, IdentityID, HandlerContext) of
ok ->
Request = {fistful_identity, 'GetChallenges', [IdentityID]},
case service_call(Request, HandlerContext) of
{ok, Challenges} ->
get_challenge_by_id(ChallengeID, Challenges, HandlerContext);
{exception, #fistful_IdentityNotFound{}} ->
{error, {identity, notfound}};
{exception, Details} ->
{error, Details}
end;
{error, unauthorized} ->
{error, {identity, unauthorized}}
end.
-spec get_identity_challenges(id(), status(), handler_context()) -> result(map(),
{identity, notfound} |
{identity, unauthorized} |
{challenge, notfound}
).
get_identity_challenges(IdentityID, Status, HandlerContext) ->
case wapi_access_backend:check_resource_by_id(identity, IdentityID, HandlerContext) of
ok ->
Request = {fistful_identity, 'GetChallenges', [IdentityID]},
case service_call(Request, HandlerContext) of
{ok, Challenges} ->
Filtered = filter_challenges_by_status(Status, Challenges, HandlerContext, []),
{ok, unmarshal({list, challenge}, Filtered)};
{exception, #fistful_IdentityNotFound{}} ->
{error, {identity, notfound}};
{exception, Details} ->
{error, Details}
end;
{error, unauthorized} ->
{error, {identity, unauthorized}}
end.
-spec get_identity_challenge_events(params(), handler_context()) -> result(map(),
{identity, notfound} |
{identity, unauthorized}
).
get_identity_challenge_events(Params = #{
'identityID' := IdentityID,
'challengeID' := ChallengeID,
'limit' := Limit
}, HandlerContext) ->
case wapi_access_backend:check_resource_by_id(identity, IdentityID, HandlerContext) of
ok ->
Cursor = maps:get('eventCursor', Params, undefined),
EventRange = marshal(event_range, {Cursor, Limit}),
Request = {fistful_identity, 'GetEvents', [IdentityID, EventRange]},
case service_call(Request, HandlerContext) of
{ok, Events} ->
Filtered = filter_events_by_challenge_id(ChallengeID, Events, []),
{ok, unmarshal({list, identity_challenge_event}, Filtered)};
{exception, #fistful_IdentityNotFound{}} ->
{error, {identity, notfound}};
{exception, Details} ->
{error, Details}
end;
{error, unauthorized} ->
{error, {identity, unauthorized}}
end.
-spec get_identity_challenge_event(params(), handler_context()) -> result(map(),
{identity, notfound} |
{identity, unauthorized} |
{event, notfound}
).
get_identity_challenge_event(Params = #{
'identityID' := IdentityID
}, HandlerContext) ->
case wapi_access_backend:check_resource_by_id(identity, IdentityID, HandlerContext) of
ok ->
get_identity_challenge_event_(Params, HandlerContext);
{error, unauthorized} ->
{error, {identity, unauthorized}}
end.
get_identity_challenge_event_(#{
'identityID' := IdentityID,
'challengeID' := ChallengeID,
'eventID' := EventId
}, HandlerContext) ->
EventRange = marshal(event_range, {EventId - 1, 1}),
Request = {fistful_identity, 'GetEvents', [IdentityID, EventRange]},
case service_call(Request, HandlerContext) of
{ok, []} ->
{error, {event, notfound}};
{ok, Events} ->
case filter_events_by_challenge_id(ChallengeID, Events, []) of
[Event] ->
{ok, unmarshal(identity_challenge_event, Event)};
_ ->
{error, {event, notfound}}
end;
{exception, #fistful_IdentityNotFound{}} ->
{error, {identity, notfound}};
{exception, Details} ->
{error, Details}
end.
%%
%% Internal
%%
filter_events_by_challenge_id(_ID, [], Result) ->
Result;
filter_events_by_challenge_id(
ID, [
#idnt_IdentityEvent{
change = {identity_challenge, #idnt_ChallengeChange{
id = ID,
payload = {status_changed, _Status} = Payload
}},
occured_at = OccuredAt,
sequence = EventID
} |
Rest
],
Acc
) ->
filter_events_by_challenge_id(ID, Rest, [{EventID, OccuredAt, Payload} | Acc]);
filter_events_by_challenge_id(ID, [_H | Rest], Acc) ->
filter_events_by_challenge_id(ID, Rest, Acc).
get_challenge_by_id(_ID, [], _) ->
{error, {challenge, notfound}};
get_challenge_by_id(ID, [Challenge = #idnt_Challenge{id = ID} | _Rest], HandlerContext) ->
{ok, unmarshal(challenge, {Challenge, HandlerContext})};
get_challenge_by_id(ID, [_Challenge | Rest], HandlerContext) ->
get_challenge_by_id(ID, Rest, HandlerContext).
filter_challenges_by_status(undefined, Challenges, HandlerContext, _) ->
[{Challenge, HandlerContext} || Challenge <- Challenges];
filter_challenges_by_status(_Status, [], _, Result) ->
Result;
filter_challenges_by_status(
FilteringStatus,
[Challenge = #idnt_Challenge{status = Status} | Rest],
HandlerContext,
Acc
) ->
ChallengeStatus = maps:get(<<"status">>, unmarshal(challenge_status, Status), undefined),
case ChallengeStatus =:= FilteringStatus of
false ->
filter_challenges_by_status(FilteringStatus, Rest, HandlerContext, Acc);
true ->
filter_challenges_by_status(FilteringStatus, Rest, HandlerContext, [{Challenge, HandlerContext} | Acc])
end.
enrich_proofs(Proofs, HandlerContext) ->
[enrich_proof(unmarshal(proof, P), HandlerContext) || P <- Proofs].
enrich_proof(#{<<"token">> := Token}, HandlerContext) ->
wapi_privdoc_backend:get_proof(Token, HandlerContext).
create_id(Type, Params, HandlerContext) ->
wapi_backend_utils:gen_id(
Type,
Params,
HandlerContext
).
create_context(Params, HandlerContext) ->
KV = {<<"name">>, maps:get(<<"name">>, Params, undefined)},
wapi_backend_utils:add_to_ctx(KV, wapi_backend_utils:make_ctx(Params, HandlerContext)).
service_call(Params, Ctx) ->
wapi_handler_utils:service_call(Params, Ctx).
%% Marshaling
marshal({list, Type}, List) ->
lists:map(fun(V) -> marshal(Type, V) end, List);
marshal(identity_params, {Params = #{
<<"id">> := ID,
<<"provider">> := Provider,
<<"class">> := Class
}, Owner, Context}) ->
ExternalID = maps:get(<<"externalID">>, Params, undefined),
#idnt_IdentityParams{
id = marshal(id, ID),
party = marshal(id, Owner),
provider = marshal(string, Provider),
cls = marshal(string, Class),
external_id = marshal(id, ExternalID),
context = marshal(context, Context)
};
marshal(challenge_params, {ID, #{
<<"type">> := Class,
<<"proofs">> := Proofs
}}) ->
#idnt_ChallengeParams{
id = marshal(id, ID),
cls = marshal(id, Class),
proofs = marshal({list, proof}, Proofs)
};
marshal(proof, #{<<"token">> := WapiToken}) ->
try
#{<<"type">> := Type, <<"token">> := Token} = wapi_utils:base64url_to_map(WapiToken),
#idnt_ChallengeProof{
type = marshal(proof_type, Type),
token = marshal(string, Token)
}
catch
error:badarg ->
wapi_handler:throw_result(wapi_handler_utils:reply_error(
422,
wapi_handler_utils:get_error_msg(io_lib:format("Invalid proof token: ~p", [WapiToken]))
))
end;
marshal(event_range, {Cursor, Limit}) ->
#'EventRange'{
'after' = marshal(integer, Cursor),
'limit' = marshal(integer, Limit)
};
marshal(context, Ctx) ->
ff_codec:marshal(context, Ctx);
marshal(proof_type, <<"RUSDomesticPassport">>) ->
rus_domestic_passport;
marshal(proof_type, <<"RUSRetireeInsuranceCertificate">>) ->
rus_retiree_insurance_cert;
marshal(T, V) ->
ff_codec:marshal(T, V).
%%
unmarshal({list, Type}, List) ->
lists:map(fun(V) -> unmarshal(Type, V) end, List);
unmarshal(identity, #idnt_Identity{
id = IdentityID,
blocking = Blocking,
cls = Class,
provider = Provider,
level = Level,
effective_challenge = EffectiveChallenge,
external_id = ExternalID,
created_at = CreatedAt,
context = Ctx
}) ->
Context = unmarshal(context, Ctx),
genlib_map:compact(#{
<<"id">> => unmarshal(id, IdentityID),
<<"name">> => wapi_backend_utils:get_from_ctx(<<"name">>, Context),
<<"createdAt">> => maybe_unmarshal(string, CreatedAt),
<<"isBlocked">> => maybe_unmarshal(blocking, Blocking),
<<"class">> => unmarshal(string, Class),
<<"provider">> => unmarshal(id, Provider),
<<"level">> => maybe_unmarshal(id, Level),
<<"effectiveChallenge">> => maybe_unmarshal(id, EffectiveChallenge),
<<"externalID">> => maybe_unmarshal(id, ExternalID),
<<"metadata">> => wapi_backend_utils:get_from_ctx(<<"metadata">>, Context)
});
unmarshal(challenge, {#idnt_Challenge{
id = ID,
cls = Class,
proofs = Proofs,
status = Status
}, HandlerContext}) ->
genlib_map:compact(maps:merge(#{
<<"id">> => unmarshal(id, ID),
<<"type">> => unmarshal(id, Class),
<<"proofs">> => enrich_proofs(Proofs, HandlerContext)
}, unmarshal(challenge_status, Status)));
unmarshal(challenge_status, {pending, #idnt_ChallengePending{}}) ->
#{<<"status">> => <<"Pending">>};
unmarshal(challenge_status, {cancelled, #idnt_ChallengeCancelled{}}) ->
#{<<"status">> => <<"Cancelled">>};
unmarshal(challenge_status, {completed, #idnt_ChallengeCompleted{
valid_until = Time,
resolution = approved
}}) ->
#{
<<"status">> => <<"Completed">>,
<<"validUntil">> => unmarshal(string, Time)
};
unmarshal(challenge_status, {completed, #idnt_ChallengeCompleted{
resolution = denied
}}) ->
%% TODO Add denied reason to proto
unmarshal(challenge_status, {failed, #idnt_ChallengeFailed{}});
unmarshal(challenge_status, {failed, #idnt_ChallengeFailed{}}) ->
#{
<<"status">> => <<"Failed">>,
<<"failureReason">> => <<"Denied">>
};
unmarshal(proof, #idnt_ChallengeProof{
type = Type,
token = Token
}) ->
genlib_map:compact(#{
<<"type">> => unmarshal(proof_type, Type),
<<"token">> => unmarshal(string, Token)
});
unmarshal(proof_type, rus_domestic_passport) ->
<<"RUSDomesticPassport">>;
unmarshal(proof_type, rus_retiree_insurance_cert) ->
<<"RUSRetireeInsuranceCertificate">>;
unmarshal(identity_challenge_event, {ID, Ts, V}) ->
#{
<<"eventID">> => unmarshal(integer, ID),
<<"occuredAt">> => unmarshal(string, Ts),
<<"changes">> => [unmarshal(identity_challenge_event_change, V)]
};
unmarshal(identity_challenge_event_change, {status_changed, S}) ->
maps:merge(
#{<<"type">> => <<"IdentityChallengeStatusChanged">>},
unmarshal(challenge_status, S)
);
unmarshal(blocking, unblocked) ->
false;
unmarshal(blocking, blocked) ->
true;
unmarshal(T, V) ->
ff_codec:unmarshal(T, V).
maybe_unmarshal(_, undefined) ->
undefined;
maybe_unmarshal(T, V) ->
unmarshal(T, V).

View File

@ -0,0 +1,153 @@
-module(wapi_wallet_backend).
-type req_data() :: wapi_handler:req_data().
-type handler_context() :: wapi_handler:context().
-type response_data() :: wapi_handler:response_data().
-type id() :: binary().
-export([create/2]).
-export([get/2]).
-include_lib("fistful_proto/include/ff_proto_wallet_thrift.hrl").
%% Pipeline
-spec create(req_data(), handler_context()) ->
{ok, response_data()} | {error, WalletError}
when WalletError ::
{identity, unauthorized} |
{identity, notfound} |
{currency, notfound} |
inaccessible |
{external_id_conflict, id()}.
create(Params = #{<<"identity">> := IdentityID}, HandlerContext) ->
case wapi_access_backend:check_resource_by_id(identity, IdentityID, HandlerContext) of
ok ->
case wapi_backend_utils:gen_id(wallet, Params, HandlerContext) of
{ok, ID} ->
Context = wapi_backend_utils:make_ctx(Params, HandlerContext),
PreparedParams = genlib_map:compact(Params#{
<<"id">> => ID,
<<"context">> => Context
}),
create(ID, PreparedParams, HandlerContext);
{error, {external_id_conflict, _}} = Error ->
Error
end;
{error, unauthorized} ->
{error, {identity, unauthorized}}
end.
create(WalletID, Params, HandlerContext) ->
WalletParams = marshal(wallet_params, Params#{<<"id">> => WalletID}),
Request = {fistful_wallet, 'Create', [WalletParams]},
case service_call(Request, HandlerContext) of
{ok, Wallet} ->
{ok, unmarshal(wallet, Wallet)};
{exception, #fistful_CurrencyNotFound{}} ->
{error, {currency, notfound}};
{exception, #fistful_PartyInaccessible{}} ->
{error, inaccessible};
{exception, #fistful_IDExists{}} ->
get(WalletID, HandlerContext);
{exception, Details} ->
{error, Details}
end.
-spec get(id(), handler_context()) ->
{ok, response_data()} |
{error, {wallet, notfound}} |
{error, {wallet, unauthorized}}.
get(WalletID, HandlerContext) ->
Request = {fistful_wallet, 'Get', [WalletID]},
case service_call(Request, HandlerContext) of
{ok, WalletThrift} ->
case wapi_access_backend:check_resource(wallet, WalletThrift, HandlerContext) of
ok ->
{ok, unmarshal(wallet, WalletThrift)};
{error, unauthorized} ->
{error, {wallet, unauthorized}}
end;
{exception, #fistful_WalletNotFound{}} ->
{error, {wallet, notfound}}
end.
%%
%% Internal
%%
service_call(Params, Ctx) ->
wapi_handler_utils:service_call(Params, Ctx).
%% Marshaling
marshal(wallet_params, Params = #{
<<"id">> := ID,
<<"name">> := Name,
<<"identity">> := IdentityID,
<<"currency">> := CurrencyID,
<<"context">> := Ctx
}) ->
ExternalID = maps:get(<<"externalID">>, Params, undefined),
#wlt_WalletParams{
id = marshal(id, ID),
name = marshal(string, Name),
account_params = marshal(account_params, {IdentityID, CurrencyID}),
external_id = marshal(id, ExternalID),
context = marshal(context, Ctx)
};
marshal(account_params, {IdentityID, CurrencyID}) ->
#account_AccountParams{
identity_id = marshal(id, IdentityID),
symbolic_code = marshal(string, CurrencyID)
};
marshal(context, Ctx) ->
ff_codec:marshal(context, Ctx);
marshal(T, V) ->
ff_codec:marshal(T, V).
unmarshal(wallet, #wlt_Wallet{
id = WalletID,
name = Name,
blocking = Blocking,
account = Account,
external_id = ExternalID,
created_at = CreatedAt,
context = Ctx
}) ->
#{
identity := Identity,
currency := Currency
} = unmarshal(account, Account),
Context = unmarshal(context, Ctx),
genlib_map:compact(#{
<<"id">> => unmarshal(id, WalletID),
<<"name">> => unmarshal(string, Name),
<<"isBlocked">> => unmarshal(blocking, Blocking),
<<"identity">> => Identity,
<<"currency">> => Currency,
<<"createdAt">> => CreatedAt,
<<"externalID">> => maybe_unmarshal(id, ExternalID),
<<"metadata">> => wapi_backend_utils:get_from_ctx(<<"metadata">>, Context)
});
unmarshal(blocking, unblocked) ->
false;
unmarshal(blocking, blocked) ->
true;
unmarshal(context, Ctx) ->
ff_codec:unmarshal(context, Ctx);
unmarshal(T, V) ->
ff_codec:unmarshal(T, V).
maybe_unmarshal(_, undefined) ->
undefined;
maybe_unmarshal(T, V) ->
unmarshal(T, V).

View File

@ -0,0 +1,148 @@
-module(wapi_wallet_thrift_handler).
-behaviour(swag_server_wallet_logic_handler).
-behaviour(wapi_handler).
%% swag_server_wallet_logic_handler callbacks
-export([authorize_api_key/3]).
-export([handle_request/4]).
%% wapi_handler callbacks
-export([process_request/4]).
%% Types
-type req_data() :: wapi_handler:req_data().
-type handler_context() :: wapi_handler:context().
-type request_result() :: wapi_handler:request_result().
-type operation_id() :: swag_server_wallet:operation_id().
-type api_key() :: swag_server_wallet:api_key().
-type request_context() :: swag_server_wallet:request_context().
-type handler_opts() :: swag_server_wallet:handler_opts(_).
%% API
-spec authorize_api_key(operation_id(), api_key(), handler_opts()) ->
false | {true, wapi_auth:context()}.
authorize_api_key(OperationID, ApiKey, Opts) ->
ok = scoper:add_meta(#{api => wallet, operation_id => OperationID}),
wapi_auth:authorize_api_key(OperationID, ApiKey, Opts).
-spec handle_request(swag_server_wallet:operation_id(), req_data(), request_context(), handler_opts()) ->
request_result().
handle_request(OperationID, Req, SwagContext, Opts) ->
wapi_handler:handle_request(wallet, OperationID, Req, SwagContext, Opts).
-spec process_request(operation_id(), req_data(), handler_context(), handler_opts()) ->
request_result().
%% Identities
process_request('GetIdentity', #{'identityID' := IdentityId}, Context, _Opts) ->
case wapi_identity_backend:get_identity(IdentityId, Context) of
{ok, Identity} -> wapi_handler_utils:reply_ok(200, Identity);
{error, {identity, notfound}} -> wapi_handler_utils:reply_ok(404);
{error, {identity, unauthorized}} -> wapi_handler_utils:reply_ok(404)
end;
process_request('CreateIdentity', #{'Identity' := Params}, Context, Opts) ->
case wapi_identity_backend:create_identity(Params, Context) of
{ok, Identity = #{<<"id">> := IdentityId}} ->
wapi_handler_utils:reply_ok(201, Identity, get_location('GetIdentity', [IdentityId], Opts));
{error, {provider, notfound}} ->
wapi_handler_utils:reply_ok(422, wapi_handler_utils:get_error_msg(<<"No such provider">>));
{error, {identity_class, notfound}} ->
wapi_handler_utils:reply_ok(422, wapi_handler_utils:get_error_msg(<<"No such identity class">>));
{error, inaccessible} ->
wapi_handler_utils:reply_ok(422, wapi_handler_utils:get_error_msg(<<"Identity inaccessible">>));
{error, {external_id_conflict, ID}} ->
wapi_handler_utils:reply_error(409, #{<<"id">> => ID})
end;
process_request('ListIdentityChallenges', #{'identityID' := Id, 'status' := Status}, Context, _Opts) ->
case wapi_identity_backend:get_identity_challenges(Id, Status, Context) of
{ok, Challenges} -> wapi_handler_utils:reply_ok(200, Challenges);
{error, {identity, notfound}} -> wapi_handler_utils:reply_ok(404);
{error, {identity, unauthorized}} -> wapi_handler_utils:reply_ok(404)
end;
process_request('StartIdentityChallenge', #{
'identityID' := IdentityId,
'IdentityChallenge' := Params
}, Context, Opts) ->
case wapi_identity_backend:create_identity_challenge(IdentityId, Params, Context) of
{ok, Challenge = #{<<"id">> := ChallengeId}} ->
wapi_handler_utils:reply_ok(202, Challenge, get_location('GetIdentityChallenge', [ChallengeId], Opts));
{error, {identity, notfound}} ->
wapi_handler_utils:reply_ok(404);
{error, {identity, unauthorized}} ->
wapi_handler_utils:reply_ok(404);
{error, {challenge, conflict}} ->
wapi_handler_utils:reply_ok(409);
{error, {external_id_conflict, ID}} ->
wapi_handler_utils:reply_ok(409, #{<<"id">> => ID});
{error, {challenge, pending}} ->
wapi_handler_utils:reply_ok(409);
{error, {challenge, {class, notfound}}} ->
wapi_handler_utils:reply_ok(422, wapi_handler_utils:get_error_msg(<<"No such challenge type">>));
{error, {challenge, {proof, notfound}}} ->
wapi_handler_utils:reply_ok(422, wapi_handler_utils:get_error_msg(<<"Proof not found">>));
{error, {challenge, {proof, insufficient}}} ->
wapi_handler_utils:reply_ok(422, wapi_handler_utils:get_error_msg(<<"Insufficient proof">>));
{error, {challenge, level}} ->
wapi_handler_utils:reply_ok(422,
wapi_handler_utils:get_error_msg(<<"Illegal identification type for current identity level">>)
)
%% TODO any other possible errors here?
end;
process_request('GetIdentityChallenge', #{
'identityID' := IdentityId,
'challengeID' := ChallengeId
}, Context, _Opts) ->
case wapi_identity_backend:get_identity_challenge(IdentityId, ChallengeId, Context) of
{ok, Challenge} -> wapi_handler_utils:reply_ok(200, Challenge);
{error, {identity, notfound}} -> wapi_handler_utils:reply_ok(404);
{error, {identity, unauthorized}} -> wapi_handler_utils:reply_ok(404);
{error, {challenge, notfound}} -> wapi_handler_utils:reply_ok(404)
end;
process_request('PollIdentityChallengeEvents', Params, Context, _Opts) ->
case wapi_identity_backend:get_identity_challenge_events(Params, Context) of
{ok, Events} -> wapi_handler_utils:reply_ok(200, Events);
{error, {identity, notfound}} -> wapi_handler_utils:reply_ok(404);
{error, {identity, unauthorized}} -> wapi_handler_utils:reply_ok(404)
end;
process_request('GetIdentityChallengeEvent', Params, Context, _Opts) ->
case wapi_identity_backend:get_identity_challenge_event(Params, Context) of
{ok, Event} -> wapi_handler_utils:reply_ok(200, Event);
{error, {identity, notfound}} -> wapi_handler_utils:reply_ok(404);
{error, {identity, unauthorized}} -> wapi_handler_utils:reply_ok(404);
{error, {event, notfound}} -> wapi_handler_utils:reply_ok(404)
end;
%% Wallets
process_request('GetWallet', #{'walletID' := WalletId}, Context, _Opts) ->
case wapi_wallet_backend:get(WalletId, Context) of
{ok, Wallet} -> wapi_handler_utils:reply_ok(200, Wallet);
{error, {wallet, notfound}} -> wapi_handler_utils:reply_ok(404);
{error, {wallet, unauthorized}} -> wapi_handler_utils:reply_ok(404)
end;
process_request('CreateWallet', #{'Wallet' := Params}, Context, Opts) ->
case wapi_wallet_backend:create(Params, Context) of
{ok, Wallet = #{<<"id">> := WalletId}} ->
wapi_handler_utils:reply_ok(201, Wallet, get_location('GetWallet', [WalletId], Opts));
{error, {identity, unauthorized}} ->
wapi_handler_utils:reply_ok(422, wapi_handler_utils:get_error_msg(<<"No such identity">>));
{error, {identity, notfound}} ->
wapi_handler_utils:reply_ok(422, wapi_handler_utils:get_error_msg(<<"No such identity">>));
{error, {currency, notfound}} ->
wapi_handler_utils:reply_ok(422, wapi_handler_utils:get_error_msg(<<"Currency not supported">>));
{error, inaccessible} ->
wapi_handler_utils:reply_ok(422, wapi_handler_utils:get_error_msg(<<"Identity inaccessible">>));
{error, {external_id_conflict, ID}} ->
wapi_handler_utils:reply_error(409, #{<<"id">> => ID})
end;
process_request(OperationID, Params, Context, Opts) ->
wapi_wallet_handler:process_request(OperationID, Params, Context, Opts).
%% Internal functions
get_location(OperationId, Params, Opts) ->
#{path := PathSpec} = swag_server_wallet_router:get_operation(OperationId),
wapi_handler_utils:get_location(PathSpec, Params, Opts).

View File

@ -0,0 +1,317 @@
-module(wapi_identity_tests_SUITE).
-include_lib("common_test/include/ct.hrl").
-include_lib("damsel/include/dmsl_webhooker_thrift.hrl").
-include_lib("jose/include/jose_jwk.hrl").
-include_lib("wapi_wallet_dummy_data.hrl").
-include_lib("fistful_proto/include/ff_proto_identity_thrift.hrl").
-include_lib("identdocstore_proto/include/identdocstore_identity_document_storage_thrift.hrl").
-export([all/0]).
-export([groups/0]).
-export([init_per_suite/1]).
-export([end_per_suite/1]).
-export([init_per_group/2]).
-export([end_per_group/2]).
-export([init_per_testcase/2]).
-export([end_per_testcase/2]).
-export([init/1]).
-export([
create_identity/1,
get_identity/1,
create_identity_challenge/1,
get_identity_challenge/1,
list_identity_challenges/1,
get_identity_challenge_event/1,
poll_identity_challenge_events/1
]).
-define(badresp(Code), {error, {invalid_response_code, Code}}).
-define(emptyresp(Code), {error, {Code, #{}}}).
-type test_case_name() :: atom().
-type config() :: [{atom(), any()}].
-type group_name() :: atom().
-behaviour(supervisor).
-spec init([]) ->
{ok, {supervisor:sup_flags(), [supervisor:child_spec()]}}.
init([]) ->
{ok, {#{strategy => one_for_all, intensity => 1, period => 1}, []}}.
-spec all() ->
[test_case_name()].
all() ->
[
{group, base}
].
-spec groups() ->
[{group_name(), list(), [test_case_name()]}].
groups() ->
[
{base, [],
[
create_identity,
get_identity,
create_identity_challenge,
get_identity_challenge,
list_identity_challenges,
get_identity_challenge_event,
poll_identity_challenge_events
]
}
].
%%
%% starting/stopping
%%
-spec init_per_suite(config()) ->
config().
init_per_suite(Config) ->
%% TODO remove this after cut off wapi
ok = application:set_env(wapi, transport, thrift),
ct_helper:makeup_cfg([
ct_helper:test_case_name(init),
ct_payment_system:setup(#{
optional_apps => [
bender_client,
wapi,
wapi_woody_client
]
})
], Config).
-spec end_per_suite(config()) ->
_.
end_per_suite(C) ->
%% TODO remove this after cut off wapi
ok = application:unset_env(wapi, transport),
ok = ct_payment_system:shutdown(C).
-spec init_per_group(group_name(), config()) ->
config().
init_per_group(Group, Config) when Group =:= base ->
ok = ff_context:save(ff_context:create(#{
party_client => party_client:create_client(),
woody_context => woody_context:new(<<"init_per_group/", (atom_to_binary(Group, utf8))/binary>>)
})),
Party = create_party(Config),
Token = issue_token(Party, [{[party], write}], unlimited),
Config1 = [{party, Party} | Config],
[{context, wapi_ct_helper:get_context(Token)} | Config1];
init_per_group(_, Config) ->
Config.
-spec end_per_group(group_name(), config()) ->
_.
end_per_group(_Group, _C) ->
ok.
-spec init_per_testcase(test_case_name(), config()) ->
config().
init_per_testcase(Name, C) ->
C1 = ct_helper:makeup_cfg([ct_helper:test_case_name(Name), ct_helper:woody_ctx()], C),
ok = ct_helper:set_context(C1),
[{test_sup, wapi_ct_helper:start_mocked_service_sup(?MODULE)} | C1].
-spec end_per_testcase(test_case_name(), config()) ->
config().
end_per_testcase(_Name, C) ->
ok = ct_helper:unset_context(),
wapi_ct_helper:stop_mocked_service_sup(?config(test_sup, C)),
ok.
%%% Tests
-spec create_identity(config()) ->
_.
create_identity(C) ->
PartyID = ?config(party, C),
wapi_ct_helper:mock_services([
{fistful_identity, fun('Create', _) -> {ok, ?IDENTITY(PartyID)} end}
], C),
{ok, _} = call_api(
fun swag_client_wallet_identities_api:create_identity/3,
#{
body => #{
<<"name">> => ?STRING,
<<"class">> => ?STRING,
<<"provider">> => ?STRING
}
},
ct_helper:cfg(context, C)
).
-spec get_identity(config()) ->
_.
get_identity(C) ->
PartyID = ?config(party, C),
wapi_ct_helper:mock_services([
{fistful_identity, fun('Get', _) -> {ok, ?IDENTITY(PartyID)} end}
], C),
{ok, _} = call_api(
fun swag_client_wallet_identities_api:get_identity/3,
#{
binding => #{
<<"identityID">> => ?STRING
}
},
ct_helper:cfg(context, C)
).
-spec create_identity_challenge(config()) ->
_.
create_identity_challenge(C) ->
PartyID = ?config(party, C),
wapi_ct_helper:mock_services([
{fistful_identity, fun
('Get', _) -> {ok, ?IDENTITY(PartyID)};
('StartChallenge', _) -> {ok, ?IDENTITY_CHALLENGE(?IDENTITY_CHALLENGE_STATUS_COMPLETED)}
end},
{identdoc_storage, fun('Get', _) -> {ok, ?IDENT_DOC} end}
], C),
{ok, _} = call_api(
fun swag_client_wallet_identities_api:start_identity_challenge/3,
#{
binding => #{
<<"identityID">> => ?STRING
},
body => #{
<<"type">> => <<"sword-initiation">>,
<<"proofs">> => [
#{
<<"token">> => wapi_utils:map_to_base64url(#{
<<"type">> => <<"RUSRetireeInsuranceCertificate">>,
<<"token">> => ?STRING
})
},
#{
<<"token">> => wapi_utils:map_to_base64url(#{
<<"type">> => <<"RUSDomesticPassport">>,
<<"token">> => ?STRING
})
}
]
}
},
ct_helper:cfg(context, C)
).
-spec get_identity_challenge(config()) ->
_.
get_identity_challenge(C) ->
PartyID = ?config(party, C),
wapi_ct_helper:mock_services([
{fistful_identity, fun
('Get', _) -> {ok, ?IDENTITY(PartyID)};
('GetChallenges', _) -> {ok, [?IDENTITY_CHALLENGE(?IDENTITY_CHALLENGE_STATUS_COMPLETED)]}
end},
{identdoc_storage, fun('Get', _) -> {ok, ?IDENT_DOC} end}
], C),
{ok, _} = call_api(
fun swag_client_wallet_identities_api:get_identity_challenge/3,
#{
binding => #{
<<"identityID">> => ?STRING,
<<"challengeID">> => ?STRING
}
},
ct_helper:cfg(context, C)
).
-spec list_identity_challenges(config()) ->
_.
list_identity_challenges(C) ->
PartyID = ?config(party, C),
wapi_ct_helper:mock_services([
{fistful_identity, fun
('Get', _) -> {ok, ?IDENTITY(PartyID)};
('GetChallenges', _) -> {ok, [?IDENTITY_CHALLENGE(?IDENTITY_CHALLENGE_STATUS_COMPLETED)]}
end},
{identdoc_storage, fun('Get', _) -> {ok, ?IDENT_DOC} end}
], C),
{ok, _} = call_api(
fun swag_client_wallet_identities_api:list_identity_challenges/3,
#{
binding => #{
<<"identityID">> => ?STRING
},
qs_val => #{
<<"status">> => <<"Completed">>
}
},
ct_helper:cfg(context, C)
).
-spec get_identity_challenge_event(config()) ->
_.
get_identity_challenge_event(C) ->
PartyID = ?config(party, C),
wapi_ct_helper:mock_services([
{fistful_identity, fun
('Get', _) -> {ok, ?IDENTITY(PartyID)};
('GetEvents', _) -> {ok, [?IDENTITY_CHALLENGE_EVENT(?CHALLENGE_STATUS_CHANGE)]}
end}
], C),
{ok, _} = call_api(
fun swag_client_wallet_identities_api:get_identity_challenge_event/3,
#{
binding => #{
<<"identityID">> => ?STRING,
<<"challengeID">> => ?STRING,
<<"eventID">> => ?INTEGER
}
},
ct_helper:cfg(context, C)
).
-spec poll_identity_challenge_events(config()) ->
_.
poll_identity_challenge_events(C) ->
PartyID = ?config(party, C),
wapi_ct_helper:mock_services([
{fistful_identity, fun
('Get', _) -> {ok, ?IDENTITY(PartyID)};
('GetEvents', _) -> {ok, [?IDENTITY_CHALLENGE_EVENT(?CHALLENGE_STATUS_CHANGE)]}
end}
], C),
{ok, _} = call_api(
fun swag_client_wallet_identities_api:poll_identity_challenge_events/3,
#{
binding => #{
<<"identityID">> => ?STRING,
<<"challengeID">> => ?STRING
},
qs_val => #{
<<"limit">> => 551,
<<"eventCursor">> => ?INTEGER
}
},
ct_helper:cfg(context, C)
).
%%
-spec call_api(function(), map(), wapi_client_lib:context()) ->
{ok, term()} | {error, term()}.
call_api(F, Params, Context) ->
{Url, PreparedParams, Opts} = wapi_client_lib:make_request(Context, Params),
Response = F(Url, PreparedParams, Opts),
wapi_client_lib:handle_response(Response).
create_party(_C) ->
ID = genlib:bsuuid(),
_ = ff_party:create(ID),
ID.
issue_token(PartyID, ACL, LifeTime) ->
Claims = #{?STRING => ?STRING},
{ok, Token} = wapi_authorizer_jwt:issue({{PartyID, wapi_acl:from_list(ACL)}, Claims}, LifeTime),
Token.

View File

@ -0,0 +1,303 @@
-module(wapi_report_tests_SUITE).
-include_lib("common_test/include/ct.hrl").
-include_lib("damsel/include/dmsl_domain_config_thrift.hrl").
-include_lib("fistful_reporter_proto/include/ff_reporter_reports_thrift.hrl").
-include_lib("file_storage_proto/include/fs_file_storage_thrift.hrl").
-include_lib("fistful_proto/include/ff_proto_base_thrift.hrl").
-include_lib("jose/include/jose_jwk.hrl").
-include_lib("wapi_wallet_dummy_data.hrl").
-export([all/0]).
-export([groups/0]).
-export([init_per_suite/1]).
-export([end_per_suite/1]).
-export([init_per_group/2]).
-export([end_per_group/2]).
-export([init_per_testcase/2]).
-export([end_per_testcase/2]).
-export([init/1]).
-export([
create_report_ok_test/1,
get_report_ok_test/1,
get_reports_ok_test/1,
reports_with_wrong_identity_ok_test/1,
download_file_ok_test/1
]).
-define(badresp(Code), {error, {invalid_response_code, Code}}).
-define(emptyresp(Code), {error, {Code, #{}}}).
-type test_case_name() :: atom().
-type config() :: [{atom(), any()}].
-type group_name() :: atom().
-behaviour(supervisor).
-spec init([]) ->
{ok, {supervisor:sup_flags(), [supervisor:child_spec()]}}.
init([]) ->
{ok, {#{strategy => one_for_all, intensity => 1, period => 1}, []}}.
-spec all() ->
[test_case_name()].
all() ->
[
{group, base}
].
-spec groups() ->
[{group_name(), list(), [test_case_name()]}].
groups() ->
[
{base, [],
[
create_report_ok_test,
get_report_ok_test,
get_reports_ok_test,
reports_with_wrong_identity_ok_test,
download_file_ok_test
]
}
].
%%
%% starting/stopping
%%
-spec init_per_suite(config()) ->
config().
init_per_suite(Config) ->
%% TODO remove this after cut off wapi
ok = application:set_env(wapi, transport, thrift),
ct_helper:makeup_cfg([
ct_helper:test_case_name(init),
ct_payment_system:setup(#{
optional_apps => [
bender_client,
wapi,
wapi_woody_client
]
})
], Config).
-spec end_per_suite(config()) ->
_.
end_per_suite(C) ->
%% TODO remove this after cut off wapi
ok = application:unset_env(wapi, transport),
ok = ct_payment_system:shutdown(C).
-spec init_per_group(group_name(), config()) ->
config().
init_per_group(Group, Config) when Group =:= base ->
ok = ff_context:save(ff_context:create(#{
party_client => party_client:create_client(),
woody_context => woody_context:new(<<"init_per_group/", (atom_to_binary(Group, utf8))/binary>>)
})),
Party = create_party(Config),
Token = issue_token(Party, [{[party], write}], unlimited),
Config1 = [{party, Party} | Config],
[{context, wapi_ct_helper:get_context(Token)} | Config1];
init_per_group(_, Config) ->
Config.
-spec end_per_group(group_name(), config()) ->
_.
end_per_group(_Group, _C) ->
ok.
-spec init_per_testcase(test_case_name(), config()) ->
config().
init_per_testcase(Name, C) ->
C1 = ct_helper:makeup_cfg([ct_helper:test_case_name(Name), ct_helper:woody_ctx()], C),
ok = ct_helper:set_context(C1),
[{test_sup, wapi_ct_helper:start_mocked_service_sup(?MODULE)} | C1].
-spec end_per_testcase(test_case_name(), config()) ->
config().
end_per_testcase(_Name, C) ->
ok = ct_helper:unset_context(),
wapi_ct_helper:stop_mocked_service_sup(?config(test_sup, C)),
ok.
%%% Tests
-spec create_report_ok_test(config()) ->
_.
create_report_ok_test(C) ->
{ok, Identity} = create_identity(C),
IdentityID = maps:get(<<"id">>, Identity),
wapi_ct_helper:mock_services([{fistful_report, fun
('GenerateReport', _) -> {ok, ?REPORT_ID};
('GetReport', _) -> {ok, ?REPORT}
end}], C),
{ok, _} = call_api(
fun swag_client_wallet_reports_api:create_report/3,
#{
binding => #{
<<"identityID">> => IdentityID
},
body => #{
<<"reportType">> => <<"withdrawalRegistry">>,
<<"fromTime">> => ?TIMESTAMP,
<<"toTime">> => ?TIMESTAMP
}
},
ct_helper:cfg(context, C)
).
-spec get_report_ok_test(config()) ->
_.
get_report_ok_test(C) ->
{ok, Identity} = create_identity(C),
IdentityID = maps:get(<<"id">>, Identity),
wapi_ct_helper:mock_services([{fistful_report, fun
('GetReport', _) -> {ok, ?REPORT}
end}], C),
{ok, _} = call_api(
fun swag_client_wallet_reports_api:get_report/3,
#{
binding => #{
<<"identityID">> => IdentityID,
<<"reportID">> => ?INTEGER
}
},
ct_helper:cfg(context, C)
).
-spec get_reports_ok_test(config()) ->
_.
get_reports_ok_test(C) ->
{ok, Identity} = create_identity(C),
IdentityID = maps:get(<<"id">>, Identity),
wapi_ct_helper:mock_services([{fistful_report, fun
('GetReports', _) -> {ok, [
?REPORT_EXT(pending, []),
?REPORT_EXT(created, undefined),
?REPORT_WITH_STATUS(canceled)]}
end}], C),
{ok, _} = call_api(
fun swag_client_wallet_reports_api:get_reports/3,
#{
binding => #{
<<"identityID">> => IdentityID
},
qs_val => #{
<<"fromTime">> => ?TIMESTAMP,
<<"toTime">> => ?TIMESTAMP,
<<"type">> => <<"withdrawalRegistry">>
}
},
ct_helper:cfg(context, C)
).
-spec reports_with_wrong_identity_ok_test(config()) ->
_.
reports_with_wrong_identity_ok_test(C) ->
IdentityID = <<"WrongIdentity">>,
wapi_ct_helper:mock_services([{fistful_report, fun
('GenerateReport', _) -> {ok, ?REPORT_ID};
('GetReport', _) -> {ok, ?REPORT};
('GetReports', _) -> {ok, [?REPORT, ?REPORT, ?REPORT]}
end}], C),
?emptyresp(400) = call_api(
fun swag_client_wallet_reports_api:create_report/3,
#{
binding => #{
<<"identityID">> => IdentityID
},
body => #{
<<"reportType">> => <<"withdrawalRegistry">>,
<<"fromTime">> => ?TIMESTAMP,
<<"toTime">> => ?TIMESTAMP
}
},
ct_helper:cfg(context, C)
),
?emptyresp(400) = call_api(
fun swag_client_wallet_reports_api:get_report/3,
#{
binding => #{
<<"identityID">> => IdentityID,
<<"reportID">> => ?INTEGER
}
},
ct_helper:cfg(context, C)
),
?emptyresp(400) = call_api(
fun swag_client_wallet_reports_api:get_reports/3,
#{
binding => #{
<<"identityID">> => IdentityID
},
qs_val => #{
<<"fromTime">> => ?TIMESTAMP,
<<"toTime">> => ?TIMESTAMP,
<<"type">> => <<"withdrawalRegistry">>
}
},
ct_helper:cfg(context, C)
).
-spec download_file_ok_test(config()) ->
_.
download_file_ok_test(C) ->
wapi_ct_helper:mock_services([{file_storage, fun
('GenerateDownloadUrl', _) -> {ok, ?STRING}
end}], C),
{ok, _} = call_api(
fun swag_client_wallet_downloads_api:download_file/3,
#{
binding => #{
<<"fileID">> => ?STRING
},
qs_val => #{
<<"expiresAt">> => ?TIMESTAMP
}
},
ct_helper:cfg(context, C)
).
%%
-spec call_api(function(), map(), wapi_client_lib:context()) ->
{ok, term()} | {error, term()}.
call_api(F, Params, Context) ->
{Url, PreparedParams, Opts} = wapi_client_lib:make_request(Context, Params),
Response = F(Url, PreparedParams, Opts),
wapi_client_lib:handle_response(Response).
create_party(_C) ->
ID = genlib:bsuuid(),
_ = ff_party:create(ID),
ID.
create_identity(C) ->
PartyID = ?config(party, C),
Params = #{
<<"provider">> => <<"good-one">>,
<<"class">> => <<"person">>,
<<"name">> => <<"HAHA NO2">>
},
wapi_wallet_ff_backend:create_identity(Params, create_context(PartyID, C)).
create_context(PartyID, C) ->
maps:merge(create_auth_ctx(PartyID), create_woody_ctx(C)).
create_woody_ctx(C) ->
#{
woody_context => ct_helper:get_woody_ctx(C)
}.
create_auth_ctx(PartyID) ->
#{
swagger_context => #{auth_context => {{PartyID, empty}, #{}}}
}.
issue_token(PartyID, ACL, LifeTime) ->
Claims = #{?STRING => ?STRING},
{ok, Token} = wapi_authorizer_jwt:issue({{PartyID, wapi_acl:from_list(ACL)}, Claims}, LifeTime),
Token.

View File

@ -0,0 +1,338 @@
-module(wapi_thrift_SUITE).
-include_lib("stdlib/include/assert.hrl").
-include_lib("fistful_proto/include/ff_proto_fistful_thrift.hrl").
-include_lib("wapi_wallet_dummy_data.hrl").
-export([all/0]).
-export([groups/0]).
-export([init_per_suite/1]).
-export([end_per_suite/1]).
-export([init_per_group/2]).
-export([end_per_group/2]).
-export([init_per_testcase/2]).
-export([end_per_testcase/2]).
-export([wallet_check_test/1]).
-export([identity_check_test/1]).
-export([identity_challenge_check_test/1]).
-type config() :: ct_helper:config().
-type test_case_name() :: ct_helper:test_case_name().
-type group_name() :: ct_helper:group_name().
-type test_return() :: _ | no_return().
% -import(ct_helper, [cfg/2]).
-spec all() -> [test_case_name() | {group, group_name()}].
all() ->
[
{group, default}
].
-spec groups() -> [{group_name(), list(), [test_case_name()]}].
groups() ->
[
{default, [sequence], [
identity_check_test,
identity_challenge_check_test,
wallet_check_test
]}
].
-spec init_per_suite(config()) -> config().
init_per_suite(C) ->
ct_helper:makeup_cfg([
ct_helper:test_case_name(init),
ct_payment_system:setup(#{
default_termset => get_default_termset(),
optional_apps => [
bender_client,
wapi_woody_client,
wapi
]
})
], C).
-spec end_per_suite(config()) -> _.
end_per_suite(C) ->
ok = ct_payment_system:shutdown(C).
%%
-spec init_per_group(group_name(), config()) -> config().
init_per_group(G, C) ->
ok = ff_context:save(ff_context:create(#{
party_client => party_client:create_client(),
woody_context => woody_context:new(<<"init_per_group/", (atom_to_binary(G, utf8))/binary>>)
})),
Party = create_party(C),
% Token = issue_token(Party, [{[party], write}], unlimited),
Token = issue_token(Party, [{[party], write}], {deadline, 10}),
Context = get_context("localhost:8080", Token),
ContextPcidss = get_context("wapi-pcidss:8080", Token),
[{context, Context}, {context_pcidss, ContextPcidss}, {party, Party} | C].
-spec end_per_group(group_name(), config()) -> _.
end_per_group(_, _) ->
ok.
%%
-spec init_per_testcase(test_case_name(), config()) -> config().
init_per_testcase(Name, C) ->
C1 = ct_helper:makeup_cfg([ct_helper:test_case_name(Name), ct_helper:woody_ctx()], C),
ok = ct_helper:set_context(C1),
ok = application:set_env(wapi, transport, not_thrift),
C1.
-spec end_per_testcase(test_case_name(), config()) -> _.
end_per_testcase(_Name, _C) ->
ok = ct_helper:unset_context().
-define(ID_PROVIDER, <<"good-one">>).
-define(ID_PROVIDER2, <<"good-two">>).
-define(ID_CLASS, <<"person">>).
-spec identity_check_test(config()) -> test_return().
identity_check_test(C) ->
Name = <<"Keyn Fawkes">>,
Provider = ?ID_PROVIDER,
Class = ?ID_CLASS,
IdentityID1 = create_identity(Name, Provider, Class, C),
ok = check_identity(Name, IdentityID1, Provider, Class, C),
Keys = maps:keys(get_identity(IdentityID1, C)),
ok = application:set_env(wapi, transport, thrift),
IdentityID2 = create_identity(Name, Provider, Class, C),
ok = check_identity(Name, IdentityID2, Provider, Class, C),
?assertEqual(Keys, maps:keys(get_identity(IdentityID2, C))).
-spec identity_challenge_check_test(config()) -> test_return().
identity_challenge_check_test(C) ->
Name = <<"Keyn Fawkes">>,
Provider = ?ID_PROVIDER,
Class = ?ID_CLASS,
IdentityID1 = create_identity(Name, Provider, Class, C),
ok = check_identity(Name, IdentityID1, Provider, Class, C),
IdentityChallengeID1 = create_identity_challenge(IdentityID1, C),
Keys = maps:keys(get_identity_challenge(IdentityID1, IdentityChallengeID1, C)),
ok = application:set_env(wapi, transport, thrift),
IdentityID2 = create_identity(Name, Provider, Class, C),
ok = check_identity(Name, IdentityID2, Provider, Class, C),
IdentityChallengeID2 = create_identity_challenge(IdentityID2, C),
?assertEqual(Keys, maps:keys(get_identity_challenge(IdentityID2, IdentityChallengeID2, C))).
-spec wallet_check_test(config()) -> test_return().
wallet_check_test(C) ->
Name = <<"Keyn Fawkes">>,
Provider = ?ID_PROVIDER,
Class = ?ID_CLASS,
IdentityID1 = create_identity(Name, Provider, Class, C),
WalletID1 = create_wallet(IdentityID1, C),
ok = check_wallet(WalletID1, C),
Keys = maps:keys(get_wallet(WalletID1, C)),
ok = application:set_env(wapi, transport, thrift),
IdentityID2 = create_identity(Name, Provider, Class, C),
WalletID2 = create_wallet(IdentityID2, C),
ok = check_wallet(WalletID2, C),
?assertEqual(Keys, maps:keys(get_wallet(WalletID2, C))).
%%
-spec call_api(function(), map(), wapi_client_lib:context()) ->
{ok, term()} | {error, term()}.
call_api(F, Params, Context) ->
{Url, PreparedParams, Opts} = wapi_client_lib:make_request(Context, Params),
Response = F(Url, PreparedParams, Opts),
wapi_client_lib:handle_response(Response).
%%
create_party(_C) ->
ID = genlib:bsuuid(),
_ = ff_party:create(ID),
ID.
issue_token(PartyID, ACL, LifeTime) ->
Claims = #{?STRING => ?STRING},
{ok, Token} = wapi_authorizer_jwt:issue({{PartyID, wapi_acl:from_list(ACL)}, Claims}, LifeTime),
Token.
get_context(Endpoint, Token) ->
wapi_client_lib:get_context(Endpoint, Token, 10000, ipv4).
%%
create_identity(Name, Provider, Class, C) ->
{ok, Identity} = call_api(
fun swag_client_wallet_identities_api:create_identity/3,
#{body => #{
<<"name">> => Name,
<<"provider">> => Provider,
<<"class">> => Class,
<<"metadata">> => #{
?STRING => ?STRING
}
}},
ct_helper:cfg(context, C)
),
maps:get(<<"id">>, Identity).
check_identity(Name, IdentityID, Provider, Class, C) ->
Identity = get_identity(IdentityID, C),
#{
<<"name">> := Name,
<<"provider">> := Provider,
<<"class">> := Class,
<<"metadata">> := #{
?STRING := ?STRING
}
} = maps:with([<<"name">>,
<<"provider">>,
<<"class">>,
<<"metadata">>], Identity),
ok.
get_identity(IdentityID, C) ->
{ok, Identity} = call_api(
fun swag_client_wallet_identities_api:get_identity/3,
#{binding => #{<<"identityID">> => IdentityID}},
ct_helper:cfg(context, C)
),
Identity.
create_identity_challenge(IdentityID, C) ->
{_Cert, CertToken} = ct_identdocstore:rus_retiree_insurance_cert(genlib:unique(), C),
{_Passport, PassportToken} = ct_identdocstore:rus_domestic_passport(C),
{ok, IdentityChallenge} = call_api(
fun swag_client_wallet_identities_api:start_identity_challenge/3,
#{
binding => #{
<<"identityID">> => IdentityID
},
body => #{
<<"type">> => <<"sword-initiation">>,
<<"proofs">> => [
#{
<<"token">> => wapi_utils:map_to_base64url(#{
<<"type">> => <<"RUSRetireeInsuranceCertificate">>,
<<"token">> => CertToken
})
},
#{
<<"token">> => wapi_utils:map_to_base64url(#{
<<"type">> => <<"RUSDomesticPassport">>,
<<"token">> => PassportToken
})
}
]
}
},
ct_helper:cfg(context, C)
),
maps:get(<<"id">>, IdentityChallenge).
get_identity_challenge(IdentityID, ChallengeID, C) ->
{ok, IdentityChallenge} = call_api(
fun swag_client_wallet_identities_api:get_identity_challenge/3,
#{binding => #{<<"identityID">> => IdentityID, <<"challengeID">> => ChallengeID}},
ct_helper:cfg(context, C)
),
IdentityChallenge.
create_wallet(IdentityID, C) ->
create_wallet(IdentityID, #{}, C).
create_wallet(IdentityID, Params, C) ->
DefaultParams = #{
<<"name">> => <<"Worldwide PHP Awareness Initiative">>,
<<"identity">> => IdentityID,
<<"currency">> => <<"RUB">>,
<<"metadata">> => #{
?STRING => ?STRING
}
},
{ok, Wallet} = call_api(
fun swag_client_wallet_wallets_api:create_wallet/3,
#{body => maps:merge(DefaultParams, Params)},
ct_helper:cfg(context, C)
),
maps:get(<<"id">>, Wallet).
check_wallet(WalletID, C) ->
Wallet = get_wallet(WalletID, C),
#{
<<"name">> := <<"Worldwide PHP Awareness Initiative">>,
<<"currency">> := <<"RUB">>,
<<"metadata">> := #{
?STRING := ?STRING
}
} = maps:with([<<"name">>, <<"currency">>, <<"metadata">>], Wallet),
ok.
get_wallet(WalletID, C) ->
{ok, Wallet} = call_api(
fun swag_client_wallet_wallets_api:get_wallet/3,
#{binding => #{<<"walletID">> => WalletID}},
ct_helper:cfg(context, C)
),
Wallet.
%%
-include_lib("ff_cth/include/ct_domain.hrl").
get_default_termset() ->
#domain_TermSet{
wallets = #domain_WalletServiceTerms{
currencies = {value, ?ordset([?cur(<<"RUB">>)])},
wallet_limit = {decisions, [
#domain_CashLimitDecision{
if_ = {condition, {currency_is, ?cur(<<"RUB">>)}},
then_ = {value, ?cashrng(
{inclusive, ?cash(-10000000, <<"RUB">>)},
{exclusive, ?cash( 10000001, <<"RUB">>)}
)}
}
]},
withdrawals = #domain_WithdrawalServiceTerms{
currencies = {value, ?ordset([?cur(<<"RUB">>)])},
cash_limit = {decisions, [
#domain_CashLimitDecision{
if_ = {condition, {currency_is, ?cur(<<"RUB">>)}},
then_ = {value, ?cashrng(
{inclusive, ?cash( 0, <<"RUB">>)},
{exclusive, ?cash(10000000, <<"RUB">>)}
)}
}
]},
cash_flow = {decisions, [
#domain_CashFlowDecision{
if_ = {condition, {currency_is, ?cur(<<"RUB">>)}},
then_ = {value, [
?cfpost(
{wallet, sender_settlement},
{wallet, receiver_destination},
?share(1, 1, operation_amount)
),
?cfpost(
{wallet, receiver_destination},
{system, settlement},
?share(10, 100, operation_amount)
)
]}
}
]}
}
}
}.

View File

@ -10,6 +10,13 @@
-define(TIMESTAMP, <<"2016-03-22T06:12:27Z">>).
-define(MD5, <<"033BD94B1168D7E4F0D644C3C95E35BF">>).
-define(SHA256, <<"94EE059335E587E501CC4BF90613E0814F00A7B08BC7C648FD865A2AF6A22CC2">>).
-define(DEFAULT_CONTEXT(PartyID), #{
<<"com.rbkmoney.wapi">> => {obj, #{
{str, <<"owner">>} => {str, PartyID},
{str, <<"name">>} => {str, ?STRING},
{str, <<"metadata">>} => {obj, #{{str, <<"somedata">>} => {str, ?STRING}}}
}}
}).
-define(CASH, #'Cash'{
amount = ?INTEGER,
@ -29,13 +36,65 @@
accounter_account_id = ?INTEGER
}).
-define(WALLET_STATE, #wlt_WalletState{
id = ?STRING,
name = ?STRING,
blocking = ?BLOCKING,
account = ?ACCOUNT
-define(WALLET(PartyID), #wlt_Wallet{
id = ?STRING,
name = ?STRING,
blocking = ?BLOCKING,
account = ?ACCOUNT,
external_id = ?STRING,
created_at = ?TIMESTAMP,
context = ?DEFAULT_CONTEXT(PartyID)
}).
-define(IDENTITY(PartyID), #idnt_Identity{
id = ?STRING,
party = ?STRING,
provider = ?STRING,
cls = ?STRING,
context = ?DEFAULT_CONTEXT(PartyID)
}).
-define(IDENTITY_CHALLENGE(Status), #idnt_Challenge{
cls = ?STRING,
proofs = [
#idnt_ChallengeProof{
type = rus_domestic_passport,
token = ?STRING
}
],
id = ?STRING,
status = Status
}).
-define(IDENTITY_CHALLENGE_STATUS_COMPLETED, {completed, #idnt_ChallengeCompleted{
resolution = approved,
valid_until = ?TIMESTAMP
}}).
-define(IDENTITY_CHALLENGE_EVENT(Change), #idnt_IdentityEvent{
change = Change,
occured_at = ?TIMESTAMP,
sequence = ?INTEGER
}).
-define(CHALLENGE_STATUS_CHANGE, {identity_challenge, #idnt_ChallengeChange{
id = ?STRING,
payload = {status_changed, ?IDENTITY_CHALLENGE_STATUS_COMPLETED}
}}).
-define(IDENT_DOC, {russian_domestic_passport, #'identdocstore_RussianDomesticPassport'{
issuer = ?STRING,
issuer_code = ?STRING,
issued_at = ?TIMESTAMP,
birth_date = ?TIMESTAMP,
birth_place = ?STRING,
series = ?STRING,
number = ?STRING,
first_name = ?STRING,
family_name = ?STRING,
patronymic = ?STRING
}}).
-define(REPORT_ID, ?INTEGER).
-define(REPORT_EXT(Status, FilesList), #ff_reports_Report{

View File

@ -3,11 +3,11 @@
-include_lib("common_test/include/ct.hrl").
-include_lib("damsel/include/dmsl_domain_config_thrift.hrl").
-include_lib("fistful_reporter_proto/include/ff_reporter_reports_thrift.hrl").
-include_lib("file_storage_proto/include/fs_file_storage_thrift.hrl").
-include_lib("fistful_proto/include/ff_proto_base_thrift.hrl").
-include_lib("jose/include/jose_jwk.hrl").
-include_lib("wapi_wallet_dummy_data.hrl").
-include_lib("fistful_proto/include/ff_proto_wallet_thrift.hrl").
-include_lib("fistful_proto/include/ff_proto_webhooker_thrift.hrl").
-export([all/0]).
@ -22,15 +22,8 @@
-export([init/1]).
-export([
create_report_ok_test/1,
get_report_ok_test/1,
get_reports_ok_test/1,
reports_with_wrong_identity_ok_test/1,
download_file_ok_test/1,
create_webhook_ok_test/1,
get_webhooks_ok_test/1,
get_webhook_ok_test/1,
delete_webhook_ok_test/1
create_wallet/1,
get_wallet/1
]).
-define(badresp(Code), {error, {invalid_response_code, Code}}).
@ -60,15 +53,8 @@ groups() ->
[
{base, [],
[
create_report_ok_test,
get_report_ok_test,
get_reports_ok_test,
reports_with_wrong_identity_ok_test,
download_file_ok_test,
create_webhook_ok_test,
get_webhooks_ok_test,
get_webhook_ok_test,
delete_webhook_ok_test
create_wallet,
get_wallet
]
}
].
@ -80,6 +66,7 @@ groups() ->
config().
init_per_suite(Config) ->
%% TODO remove this after cut off wapi
ok = application:set_env(wapi, transport, thrift),
ct_helper:makeup_cfg([
ct_helper:test_case_name(init),
ct_payment_system:setup(#{
@ -95,6 +82,7 @@ init_per_suite(Config) ->
_.
end_per_suite(C) ->
%% TODO remove this after cut off wapi
ok = application:unset_env(wapi, transport),
ok = ct_payment_system:shutdown(C).
-spec init_per_group(group_name(), config()) ->
@ -133,224 +121,42 @@ end_per_testcase(_Name, C) ->
%%% Tests
-spec create_report_ok_test(config()) ->
-spec create_wallet(config()) ->
_.
create_report_ok_test(C) ->
{ok, Identity} = create_identity(C),
IdentityID = maps:get(<<"id">>, Identity),
wapi_ct_helper:mock_services([{fistful_report, fun
('GenerateReport', _) -> {ok, ?REPORT_ID};
('GetReport', _) -> {ok, ?REPORT}
end}], C),
create_wallet(C) ->
PartyID = ?config(party, C),
wapi_ct_helper:mock_services([
{fistful_identity, fun('Get', _) -> {ok, ?IDENTITY(PartyID)} end},
{fistful_wallet, fun('Create', _) -> {ok, ?WALLET(PartyID)} end}
], C),
{ok, _} = call_api(
fun swag_client_wallet_reports_api:create_report/3,
#{
binding => #{
<<"identityID">> => IdentityID
},
body => #{
<<"reportType">> => <<"withdrawalRegistry">>,
<<"fromTime">> => ?TIMESTAMP,
<<"toTime">> => ?TIMESTAMP
}
},
ct_helper:cfg(context, C)
).
-spec get_report_ok_test(config()) ->
_.
get_report_ok_test(C) ->
{ok, Identity} = create_identity(C),
IdentityID = maps:get(<<"id">>, Identity),
wapi_ct_helper:mock_services([{fistful_report, fun
('GetReport', _) -> {ok, ?REPORT}
end}], C),
{ok, _} = call_api(
fun swag_client_wallet_reports_api:get_report/3,
#{
binding => #{
<<"identityID">> => IdentityID,
<<"reportID">> => ?INTEGER
}
},
ct_helper:cfg(context, C)
).
-spec get_reports_ok_test(config()) ->
_.
get_reports_ok_test(C) ->
{ok, Identity} = create_identity(C),
IdentityID = maps:get(<<"id">>, Identity),
wapi_ct_helper:mock_services([{fistful_report, fun
('GetReports', _) -> {ok, [
?REPORT_EXT(pending, []),
?REPORT_EXT(created, undefined),
?REPORT_WITH_STATUS(canceled)]}
end}], C),
{ok, _} = call_api(
fun swag_client_wallet_reports_api:get_reports/3,
#{
binding => #{
<<"identityID">> => IdentityID
},
qs_val => #{
<<"fromTime">> => ?TIMESTAMP,
<<"toTime">> => ?TIMESTAMP,
<<"type">> => <<"withdrawalRegistry">>
}
},
ct_helper:cfg(context, C)
).
-spec reports_with_wrong_identity_ok_test(config()) ->
_.
reports_with_wrong_identity_ok_test(C) ->
IdentityID = <<"WrongIdentity">>,
wapi_ct_helper:mock_services([{fistful_report, fun
('GenerateReport', _) -> {ok, ?REPORT_ID};
('GetReport', _) -> {ok, ?REPORT};
('GetReports', _) -> {ok, [?REPORT, ?REPORT, ?REPORT]}
end}], C),
?emptyresp(400) = call_api(
fun swag_client_wallet_reports_api:create_report/3,
#{
binding => #{
<<"identityID">> => IdentityID
},
body => #{
<<"reportType">> => <<"withdrawalRegistry">>,
<<"fromTime">> => ?TIMESTAMP,
<<"toTime">> => ?TIMESTAMP
}
},
ct_helper:cfg(context, C)
),
?emptyresp(400) = call_api(
fun swag_client_wallet_reports_api:get_report/3,
#{
binding => #{
<<"identityID">> => IdentityID,
<<"reportID">> => ?INTEGER
}
},
ct_helper:cfg(context, C)
),
?emptyresp(400) = call_api(
fun swag_client_wallet_reports_api:get_reports/3,
#{
binding => #{
<<"identityID">> => IdentityID
},
qs_val => #{
<<"fromTime">> => ?TIMESTAMP,
<<"toTime">> => ?TIMESTAMP,
<<"type">> => <<"withdrawalRegistry">>
}
},
ct_helper:cfg(context, C)
).
-spec download_file_ok_test(config()) ->
_.
download_file_ok_test(C) ->
wapi_ct_helper:mock_services([{file_storage, fun
('GenerateDownloadUrl', _) -> {ok, ?STRING}
end}], C),
{ok, _} = call_api(
fun swag_client_wallet_downloads_api:download_file/3,
#{
binding => #{
<<"fileID">> => ?STRING
},
qs_val => #{
<<"expiresAt">> => ?TIMESTAMP
}
},
ct_helper:cfg(context, C)
).
-spec create_webhook_ok_test(config()) ->
_.
create_webhook_ok_test(C) ->
{ok, Identity} = create_identity(C),
IdentityID = maps:get(<<"id">>, Identity),
wapi_ct_helper:mock_services([{webhook_manager, fun
('Create', _) -> {ok, ?WEBHOOK(?DESTINATION_EVENT_FILTER)}
end}], C),
{ok, _} = call_api(
fun swag_client_wallet_webhooks_api:create_webhook/3,
fun swag_client_wallet_wallets_api:create_wallet/3,
#{
body => #{
<<"identityID">> => IdentityID,
<<"url">> => ?STRING,
<<"scope">> => #{
<<"topic">> => <<"DestinationsTopic">>,
<<"eventTypes">> => [<<"DestinationCreated">>]
}
}
<<"name">> => ?STRING,
<<"identity">> => ?STRING,
<<"currency">> => ?RUB
}
},
ct_helper:cfg(context, C)
).
-spec get_webhooks_ok_test(config()) ->
-spec get_wallet(config()) ->
_.
get_webhooks_ok_test(C) ->
{ok, Identity} = create_identity(C),
IdentityID = maps:get(<<"id">>, Identity),
wapi_ct_helper:mock_services([{webhook_manager, fun
('GetList', _) -> {ok, [?WEBHOOK(?WITHDRAWAL_EVENT_FILTER), ?WEBHOOK(?DESTINATION_EVENT_FILTER)]}
end}], C),
get_wallet(C) ->
PartyID = ?config(party, C),
wapi_ct_helper:mock_services([
{fistful_wallet, fun('Get', _) -> {ok, ?WALLET(PartyID)} end}
], C),
{ok, _} = call_api(
fun swag_client_wallet_webhooks_api:get_webhooks/3,
#{
qs_val => #{
<<"identityID">> => IdentityID
}
},
ct_helper:cfg(context, C)
).
-spec get_webhook_ok_test(config()) ->
_.
get_webhook_ok_test(C) ->
{ok, Identity} = create_identity(C),
IdentityID = maps:get(<<"id">>, Identity),
wapi_ct_helper:mock_services([{webhook_manager, fun
('Get', _) -> {ok, ?WEBHOOK(?WITHDRAWAL_EVENT_FILTER)}
end}], C),
{ok, _} = call_api(
fun swag_client_wallet_webhooks_api:get_webhook_by_id/3,
fun swag_client_wallet_wallets_api:get_wallet/3,
#{
binding => #{
<<"webhookID">> => integer_to_binary(?INTEGER)
},
qs_val => #{
<<"identityID">> => IdentityID
<<"walletID">> => ?STRING
}
},
ct_helper:cfg(context, C)
).
-spec delete_webhook_ok_test(config()) ->
_.
delete_webhook_ok_test(C) ->
{ok, Identity} = create_identity(C),
IdentityID = maps:get(<<"id">>, Identity),
wapi_ct_helper:mock_services([{webhook_manager, fun
('Delete', _) -> {ok, ok}
end}], C),
{ok, _} = call_api(
fun swag_client_wallet_webhooks_api:delete_webhook_by_id/3,
#{
binding => #{
<<"webhookID">> => integer_to_binary(?INTEGER)
},
qs_val => #{
<<"identityID">> => IdentityID
}
},
ct_helper:cfg(context, C)
).
ct_helper:cfg(context, C)
).
%%
@ -366,28 +172,6 @@ create_party(_C) ->
_ = ff_party:create(ID),
ID.
create_identity(C) ->
PartyID = ?config(party, C),
Params = #{
<<"provider">> => <<"good-one">>,
<<"class">> => <<"person">>,
<<"name">> => <<"HAHA NO2">>
},
wapi_wallet_ff_backend:create_identity(Params, create_context(PartyID, C)).
create_context(PartyID, C) ->
maps:merge(create_auth_ctx(PartyID), create_woody_ctx(C)).
create_woody_ctx(C) ->
#{
woody_context => ct_helper:get_woody_ctx(C)
}.
create_auth_ctx(PartyID) ->
#{
swagger_context => #{auth_context => {{PartyID, empty}, empty}}
}.
issue_token(PartyID, ACL, LifeTime) ->
Claims = #{?STRING => ?STRING},
{ok, Token} = wapi_authorizer_jwt:issue({{PartyID, wapi_acl:from_list(ACL)}, Claims}, LifeTime),

View File

@ -0,0 +1,249 @@
-module(wapi_webhook_tests_SUITE).
-include_lib("common_test/include/ct.hrl").
-include_lib("damsel/include/dmsl_domain_config_thrift.hrl").
-include_lib("jose/include/jose_jwk.hrl").
-include_lib("wapi_wallet_dummy_data.hrl").
-include_lib("fistful_proto/include/ff_proto_webhooker_thrift.hrl").
-export([all/0]).
-export([groups/0]).
-export([init_per_suite/1]).
-export([end_per_suite/1]).
-export([init_per_group/2]).
-export([end_per_group/2]).
-export([init_per_testcase/2]).
-export([end_per_testcase/2]).
-export([init/1]).
-export([
create_webhook_ok_test/1,
get_webhooks_ok_test/1,
get_webhook_ok_test/1,
delete_webhook_ok_test/1
]).
-define(badresp(Code), {error, {invalid_response_code, Code}}).
-define(emptyresp(Code), {error, {Code, #{}}}).
-type test_case_name() :: atom().
-type config() :: [{atom(), any()}].
-type group_name() :: atom().
-behaviour(supervisor).
-spec init([]) ->
{ok, {supervisor:sup_flags(), [supervisor:child_spec()]}}.
init([]) ->
{ok, {#{strategy => one_for_all, intensity => 1, period => 1}, []}}.
-spec all() ->
[test_case_name()].
all() ->
[
{group, base}
].
-spec groups() ->
[{group_name(), list(), [test_case_name()]}].
groups() ->
[
{base, [],
[
create_webhook_ok_test,
get_webhooks_ok_test,
get_webhook_ok_test,
delete_webhook_ok_test
]
}
].
%%
%% starting/stopping
%%
-spec init_per_suite(config()) ->
config().
init_per_suite(Config) ->
%% TODO remove this after cut off wapi
ok = application:set_env(wapi, transport, thrift),
ct_helper:makeup_cfg([
ct_helper:test_case_name(init),
ct_payment_system:setup(#{
optional_apps => [
bender_client,
wapi_woody_client,
wapi
]
})
], Config).
-spec end_per_suite(config()) ->
_.
end_per_suite(C) ->
%% TODO remove this after cut off wapi
ok = application:unset_env(wapi, transport),
ok = ct_payment_system:shutdown(C).
-spec init_per_group(group_name(), config()) ->
config().
init_per_group(Group, Config) when Group =:= base ->
ok = ff_context:save(ff_context:create(#{
party_client => party_client:create_client(),
woody_context => woody_context:new(<<"init_per_group/", (atom_to_binary(Group, utf8))/binary>>)
})),
Party = create_party(Config),
Token = issue_token(Party, [{[party], write}], unlimited),
Config1 = [{party, Party} | Config],
[{context, wapi_ct_helper:get_context(Token)} | Config1];
init_per_group(_, Config) ->
Config.
-spec end_per_group(group_name(), config()) ->
_.
end_per_group(_Group, _C) ->
ok.
-spec init_per_testcase(test_case_name(), config()) ->
config().
init_per_testcase(Name, C) ->
C1 = ct_helper:makeup_cfg([ct_helper:test_case_name(Name), ct_helper:woody_ctx()], C),
ok = ct_helper:set_context(C1),
[{test_sup, wapi_ct_helper:start_mocked_service_sup(?MODULE)} | C1].
-spec end_per_testcase(test_case_name(), config()) ->
config().
end_per_testcase(_Name, C) ->
ok = ct_helper:unset_context(),
wapi_ct_helper:stop_mocked_service_sup(?config(test_sup, C)),
ok.
%%% Tests
-spec create_webhook_ok_test(config()) ->
_.
create_webhook_ok_test(C) ->
{ok, Identity} = create_identity(C),
IdentityID = maps:get(<<"id">>, Identity),
wapi_ct_helper:mock_services([{webhook_manager, fun
('Create', _) -> {ok, ?WEBHOOK(?DESTINATION_EVENT_FILTER)}
end}], C),
{ok, _} = call_api(
fun swag_client_wallet_webhooks_api:create_webhook/3,
#{
body => #{
<<"identityID">> => IdentityID,
<<"url">> => ?STRING,
<<"scope">> => #{
<<"topic">> => <<"DestinationsTopic">>,
<<"eventTypes">> => [<<"DestinationCreated">>]
}
}
},
ct_helper:cfg(context, C)
).
-spec get_webhooks_ok_test(config()) ->
_.
get_webhooks_ok_test(C) ->
{ok, Identity} = create_identity(C),
IdentityID = maps:get(<<"id">>, Identity),
wapi_ct_helper:mock_services([{webhook_manager, fun
('GetList', _) -> {ok, [?WEBHOOK(?WITHDRAWAL_EVENT_FILTER), ?WEBHOOK(?DESTINATION_EVENT_FILTER)]}
end}], C),
{ok, _} = call_api(
fun swag_client_wallet_webhooks_api:get_webhooks/3,
#{
qs_val => #{
<<"identityID">> => IdentityID
}
},
ct_helper:cfg(context, C)
).
-spec get_webhook_ok_test(config()) ->
_.
get_webhook_ok_test(C) ->
{ok, Identity} = create_identity(C),
IdentityID = maps:get(<<"id">>, Identity),
wapi_ct_helper:mock_services([{webhook_manager, fun
('Get', _) -> {ok, ?WEBHOOK(?WITHDRAWAL_EVENT_FILTER)}
end}], C),
{ok, _} = call_api(
fun swag_client_wallet_webhooks_api:get_webhook_by_id/3,
#{
binding => #{
<<"webhookID">> => integer_to_binary(?INTEGER)
},
qs_val => #{
<<"identityID">> => IdentityID
}
},
ct_helper:cfg(context, C)
).
-spec delete_webhook_ok_test(config()) ->
_.
delete_webhook_ok_test(C) ->
{ok, Identity} = create_identity(C),
IdentityID = maps:get(<<"id">>, Identity),
wapi_ct_helper:mock_services([{webhook_manager, fun
('Delete', _) -> {ok, ok}
end}], C),
{ok, _} = call_api(
fun swag_client_wallet_webhooks_api:delete_webhook_by_id/3,
#{
binding => #{
<<"webhookID">> => integer_to_binary(?INTEGER)
},
qs_val => #{
<<"identityID">> => IdentityID
}
},
ct_helper:cfg(context, C)
).
%%
-spec call_api(function(), map(), wapi_client_lib:context()) ->
{ok, term()} | {error, term()}.
call_api(F, Params, Context) ->
{Url, PreparedParams, Opts} = wapi_client_lib:make_request(Context, Params),
Response = F(Url, PreparedParams, Opts),
wapi_client_lib:handle_response(Response).
create_party(_C) ->
ID = genlib:bsuuid(),
_ = ff_party:create(ID),
ID.
create_identity(C) ->
PartyID = ?config(party, C),
Params = #{
<<"provider">> => <<"good-one">>,
<<"class">> => <<"person">>,
<<"name">> => <<"HAHA NO2">>
},
wapi_wallet_ff_backend:create_identity(Params, create_context(PartyID, C)).
create_context(PartyID, C) ->
maps:merge(create_auth_ctx(PartyID), create_woody_ctx(C)).
create_woody_ctx(C) ->
#{
woody_context => ct_helper:get_woody_ctx(C)
}.
create_auth_ctx(PartyID) ->
#{
swagger_context => #{auth_context => {{PartyID, empty}, #{}}}
}.
issue_token(PartyID, ACL, LifeTime) ->
Claims = #{?STRING => ?STRING},
{ok, Token} = wapi_authorizer_jwt:issue({{PartyID, wapi_acl:from_list(ACL)}, Claims}, LifeTime),
Token.

View File

@ -82,6 +82,10 @@ get_service_modname(fistful_report) ->
{ff_reporter_reports_thrift, 'Reporting'};
get_service_modname(file_storage) ->
{fs_file_storage_thrift, 'FileStorage'};
get_service_modname(fistful_identity) ->
{ff_proto_identity_thrift, 'Management'};
get_service_modname(fistful_wallet) ->
{ff_proto_wallet_thrift, 'Management'};
get_service_modname(webhook_manager) ->
{ff_proto_webhooker_thrift, 'WebhookManager'}.

@ -1 +1 @@
Subproject commit b9b18f3ee375aa5fd105daf57189ac242c40f572
Subproject commit ea4aa042f482551d624fd49a570d28488f479e93

View File

@ -1,5 +1,4 @@
[
{kernel, [
{log_level, info},
{logger, [
@ -120,6 +119,7 @@
%% 500 => "oops_bodies/500_body"
%% }},
{realm, <<"external">>},
{transport, thrift},
{public_endpoint, <<"http://wapi">>},
{authorizers, #{
jwt => #{