mirror of
https://github.com/valitydev/wapi-lib.git
synced 2024-11-06 10:15:17 +00:00
CAPI-279/CAPI-280: add token issue and withdrawal auth via grant/bearer (#5)
* CAPI-280: auth withdrawal via bearer or destination grant * Fix wapi related linter warnings * Do not run lint in CI for now * Add wallet grant implementation and fix some bugs * Refactor resource auth for withdrawal * Fix (dirty) user woody identity handling in ff_party * Fix challenge event search * Fix withdrawal and challenge status 'Failed' handling
This commit is contained in:
parent
529e9201af
commit
b8945abdf2
4
Jenkinsfile
vendored
4
Jenkinsfile
vendored
@ -29,9 +29,13 @@ build('fistful-server', 'docker-host', finalHook) {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO: uncomment when linter warnings are fixed
|
||||
*
|
||||
runStage('lint') {
|
||||
sh 'make wc_lint'
|
||||
}
|
||||
*/
|
||||
|
||||
runStage('xref') {
|
||||
sh 'make wc_xref'
|
||||
|
2
Makefile
2
Makefile
@ -58,7 +58,7 @@ release: submodules generate
|
||||
clean:
|
||||
$(REBAR) clean
|
||||
|
||||
distclean: swag_server.distclean swag_client.distclean
|
||||
distclean: swagger.distclean.server.wallet swagger.distclean.client.wallet
|
||||
$(REBAR) clean -a
|
||||
rm -rf _build
|
||||
|
||||
|
@ -39,10 +39,10 @@
|
||||
ff_identity_challenge:challenge().
|
||||
|
||||
-type event() ::
|
||||
{created , identity()} |
|
||||
{level_changed , level()} |
|
||||
{effective_challenge_changed, challenge_id()} |
|
||||
{challenge , challenge_id(), ff_identity_challenge:ev()} .
|
||||
{created , identity()} |
|
||||
{level_changed , level()} |
|
||||
{effective_challenge_changed, challenge_id()} |
|
||||
{{challenge , challenge_id()}, ff_identity_challenge:ev()}.
|
||||
|
||||
-export_type([identity/0]).
|
||||
-export_type([event/0]).
|
||||
|
@ -184,7 +184,7 @@ generate_uuid() ->
|
||||
%% Party management client
|
||||
|
||||
do_create_party(ID, Params) ->
|
||||
case call('Create', [construct_userinfo(), ID, construct_party_params(Params)]) of
|
||||
case call('Create', [ID, construct_party_params(Params)]) of
|
||||
{ok, ok} ->
|
||||
ok;
|
||||
{exception, #payproc_PartyExists{}} ->
|
||||
@ -194,7 +194,7 @@ do_create_party(ID, Params) ->
|
||||
end.
|
||||
|
||||
do_get_party(ID) ->
|
||||
case call('Get', [construct_userinfo(), ID]) of
|
||||
case call('Get', [ID]) of
|
||||
{ok, #domain_Party{} = Party} ->
|
||||
Party;
|
||||
{exception, Unexpected} ->
|
||||
@ -202,7 +202,7 @@ do_get_party(ID) ->
|
||||
end.
|
||||
|
||||
% do_get_contract(ID, ContractID) ->
|
||||
% case call('GetContract', [construct_userinfo(), ID, ContractID]) of
|
||||
% case call('GetContract', [ID, ContractID]) of
|
||||
% {ok, #domain_Contract{} = Contract} ->
|
||||
% Contract;
|
||||
% {exception, #payproc_ContractNotFound{}} ->
|
||||
@ -212,7 +212,7 @@ do_get_party(ID) ->
|
||||
% end.
|
||||
|
||||
do_get_wallet(ID, WalletID) ->
|
||||
case call('GetWallet', [construct_userinfo(), ID, WalletID]) of
|
||||
case call('GetWallet', [ID, WalletID]) of
|
||||
{ok, #domain_Wallet{} = Wallet} ->
|
||||
{ok, Wallet};
|
||||
{exception, #payproc_WalletNotFound{}} ->
|
||||
@ -222,7 +222,7 @@ do_get_wallet(ID, WalletID) ->
|
||||
end.
|
||||
|
||||
do_create_claim(ID, Changeset) ->
|
||||
case call('CreateClaim', [construct_userinfo(), ID, Changeset]) of
|
||||
case call('CreateClaim', [ID, Changeset]) of
|
||||
{ok, Claim} ->
|
||||
{ok, Claim};
|
||||
{exception, #payproc_InvalidChangeset{
|
||||
@ -241,7 +241,7 @@ do_accept_claim(ID, Claim) ->
|
||||
% such a way which may cause conflicts.
|
||||
ClaimID = Claim#payproc_Claim.id,
|
||||
Revision = Claim#payproc_Claim.revision,
|
||||
case call('AcceptClaim', [construct_userinfo(), ID, ClaimID, Revision]) of
|
||||
case call('AcceptClaim', [ID, ClaimID, Revision]) of
|
||||
{ok, ok} ->
|
||||
accepted;
|
||||
{exception, #payproc_InvalidClaimStatus{status = {accepted, _}}} ->
|
||||
@ -345,10 +345,27 @@ construct_userinfo() ->
|
||||
construct_usertype() ->
|
||||
{service_user, #payproc_ServiceUser{}}.
|
||||
|
||||
construct_useridentity() ->
|
||||
#{
|
||||
id => <<"fistful">>,
|
||||
realm => <<"service">>
|
||||
}.
|
||||
|
||||
%% Woody stuff
|
||||
|
||||
call(Function, Args) ->
|
||||
get_woody_ctx() ->
|
||||
% TODO
|
||||
% - Move auth logic from hellgate to capi the same way as it works
|
||||
% in wapi & fistful. Then the following dirty user_identity hack
|
||||
% will not be necessary anymore.
|
||||
reset_useridentity(ff_woody_ctx:get()).
|
||||
|
||||
reset_useridentity(Ctx) ->
|
||||
woody_user_identity:put(construct_useridentity(), maps:without([meta], Ctx)).
|
||||
|
||||
call(Function, Args0) ->
|
||||
% TODO
|
||||
% - Ideally, we should provide `Client` here explicitly.
|
||||
Service = {dmsl_payment_processing_thrift, 'PartyManagement'},
|
||||
ff_woody_client:call(partymgmt, {Service, Function, Args}).
|
||||
Service = {dmsl_payment_processing_thrift, 'PartyManagement'},
|
||||
Args = [construct_userinfo() | Args0],
|
||||
ff_woody_client:call(partymgmt, {Service, Function, Args}, get_woody_ctx()).
|
||||
|
@ -28,6 +28,7 @@
|
||||
|
||||
-export([new/1]).
|
||||
-export([call/2]).
|
||||
-export([call/3]).
|
||||
|
||||
%%
|
||||
|
||||
@ -56,11 +57,17 @@ new(Url) when is_binary(Url); is_list(Url) ->
|
||||
{ok, woody:result()} |
|
||||
{exception, woody_error:business_error()}.
|
||||
|
||||
call(ServiceID, Request) when is_atom(ServiceID) ->
|
||||
Client = get_service_client(ServiceID),
|
||||
woody_client:call(Request, Client, ff_woody_ctx:get());
|
||||
call(Client, Request) when is_map(Client) ->
|
||||
woody_client:call(Request, Client, ff_woody_ctx:get()).
|
||||
call(ServiceIdOrClient, Request) ->
|
||||
call(ServiceIdOrClient, Request, ff_woody_ctx:get()).
|
||||
|
||||
-spec call(service_id() | client(), woody:request(), woody_context:ctx()) ->
|
||||
{ok, woody:result()} |
|
||||
{exception, woody_error:business_error()}.
|
||||
|
||||
call(ServiceID, Request, Context) when is_atom(ServiceID) ->
|
||||
call(get_service_client(ServiceID), Request, Context);
|
||||
call(Client, Request, Context) when is_map(Client) ->
|
||||
woody_client:call(Request, Client, Context).
|
||||
|
||||
%%
|
||||
|
||||
|
@ -150,12 +150,21 @@ match_scope(_, _) ->
|
||||
decode(V) ->
|
||||
lists:foldl(fun decode_entry/2, new(), V).
|
||||
|
||||
%% TODO
|
||||
%% - Keycloak utilizes string ACLs and so do we for now. Nicer way to handle ACLs
|
||||
%% is to use json instead for wapi issued tokens. That would require providing
|
||||
%% similar routines for ACL normalization as we have for string ACLs.
|
||||
decode_entry(V, ACL) ->
|
||||
case binary:split(V, <<":">>, [global]) of
|
||||
[V1, V2] ->
|
||||
Scope = decode_scope(V1),
|
||||
Permission = decode_permission(V2),
|
||||
insert_scope(Scope, Permission, ACL);
|
||||
%% Skip entries, which are not in wapi hierarchy
|
||||
try
|
||||
Scope = decode_scope(V1),
|
||||
Permission = decode_permission(V2),
|
||||
insert_scope(Scope, Permission, ACL)
|
||||
catch
|
||||
error:{badarg, {resource, _}} -> ACL
|
||||
end;
|
||||
_ ->
|
||||
error({badarg, {role, V}})
|
||||
end.
|
||||
|
@ -9,7 +9,6 @@
|
||||
-export([get_claims/1]).
|
||||
-export([get_claim/2]).
|
||||
-export([get_claim/3]).
|
||||
-export([get_consumer/1]).
|
||||
|
||||
-export([get_resource_hierarchy/0]).
|
||||
|
||||
@ -80,74 +79,131 @@ do_authorize_api_key(_OperationID, bearer, Token) ->
|
||||
% TODO
|
||||
% We need shared type here, exported somewhere in swagger app
|
||||
-type request_data() :: #{atom() | binary() => term()}.
|
||||
-type auth_method() :: bearer_token | grant.
|
||||
-type resource() :: wallet | destination.
|
||||
-type auth_details() :: auth_method() | [{resource(), auth_details()}].
|
||||
-type auth_error() :: [{resource(), [{auth_method(), atom()}]}].
|
||||
|
||||
-spec authorize_operation(
|
||||
OperationID :: operation_id(),
|
||||
Req :: request_data(),
|
||||
Auth :: wapi_authorizer_jwt:t()
|
||||
) ->
|
||||
ok | {error, unauthorized}.
|
||||
-spec authorize_operation(operation_id(), request_data(), wapi_handler:context()) ->
|
||||
{ok, auth_details()} | {error, auth_error()}.
|
||||
|
||||
%% TODO
|
||||
authorize_operation('CreateWithdrawal', #{'WithdrawalParameters' := Params}, Context) ->
|
||||
authorize_withdrawal(Params, Context);
|
||||
%% TODO: implement authorization
|
||||
authorize_operation(_OperationID, _Req, _) ->
|
||||
ok.
|
||||
%% authorize_operation(OperationID, Req, {{_SubjectID, ACL}, _}) ->
|
||||
%% Access = get_operation_access(OperationID, Req),
|
||||
%% _ = case lists:all(
|
||||
%% fun ({Scope, Permission}) ->
|
||||
%% lists:member(Permission, wapi_acl:match(Scope, ACL))
|
||||
%% end,
|
||||
%% Access
|
||||
%% ) of
|
||||
%% true ->
|
||||
%% ok;
|
||||
%% false ->
|
||||
%% {error, unauthorized}
|
||||
%% end.
|
||||
{ok, bearer_token}.
|
||||
|
||||
%%
|
||||
authorize_withdrawal(Params, Context) ->
|
||||
lists:foldl(
|
||||
fun(R, AuthState) ->
|
||||
case {authorize_resource(R, Params, Context), AuthState} of
|
||||
{{ok, AuthMethod}, {ok, AuthData}} -> {ok, [{R, AuthMethod} | AuthData]};
|
||||
{{ok, _}, {error, _}} -> AuthState;
|
||||
{{error, Error}, {error, ErrorData}} -> {error, [{R, Error} | ErrorData]};
|
||||
{{error, Error}, {ok, _}} -> {error, [{R, Error}]}
|
||||
end
|
||||
end,
|
||||
{ok, []},
|
||||
[destination, wallet]
|
||||
).
|
||||
|
||||
authorize_resource(Resource, Params, Context) ->
|
||||
%% TODO
|
||||
%% - ff_pipeline:do/1 would make the code rather more clear here.
|
||||
authorize_resource_by_bearer(authorize_resource_by_grant(Resource, Params), Resource, Params, Context).
|
||||
|
||||
authorize_resource_by_bearer(ok, _Resource, _Params, _Context) ->
|
||||
{ok, grant};
|
||||
authorize_resource_by_bearer({error, GrantError}, Resource, Params, Context) ->
|
||||
case get_resource(Resource, maps:get(genlib:to_binary(Resource), Params), Context) of
|
||||
{ok, _} ->
|
||||
{ok, bearer_token};
|
||||
{error, BearerError} ->
|
||||
{error, [{bearer_token, BearerError}, {grant, GrantError}]}
|
||||
end.
|
||||
|
||||
get_resource(destination, ID, Context) ->
|
||||
wapi_wallet_ff_backend:get_destination(ID, Context);
|
||||
get_resource(wallet, ID, Context) ->
|
||||
wapi_wallet_ff_backend:get_wallet(ID, Context).
|
||||
|
||||
authorize_resource_by_grant(R = destination, #{
|
||||
<<"destination">> := ID,
|
||||
<<"destinationGrant">> := Grant
|
||||
}) ->
|
||||
authorize_resource_by_grant(R, Grant, get_resource_accesses(R, ID, write), undefined);
|
||||
authorize_resource_by_grant(R = wallet, #{
|
||||
<<"wallet">> := ID,
|
||||
<<"walletGrant">> := Grant,
|
||||
<<"body">> := WithdrawalBody
|
||||
}) ->
|
||||
authorize_resource_by_grant(R, Grant, get_resource_accesses(R, ID, write), WithdrawalBody);
|
||||
authorize_resource_by_grant(_, _) ->
|
||||
{error, missing}.
|
||||
|
||||
authorize_resource_by_grant(Resource, Grant, Access, Params) ->
|
||||
case wapi_authorizer_jwt:verify(Grant) of
|
||||
{ok, {{_, ACL}, Claims}} ->
|
||||
verify_claims(Resource, verify_access(Access, ACL, Claims), Params);
|
||||
Error = {error, _} ->
|
||||
Error
|
||||
end.
|
||||
|
||||
get_resource_accesses(Resource, ID, Permission) ->
|
||||
[{get_resource_accesses(Resource, ID), Permission}].
|
||||
|
||||
get_resource_accesses(destination, ID) ->
|
||||
[party, {destinations, ID}];
|
||||
get_resource_accesses(wallet, ID) ->
|
||||
[party, {wallets, ID}].
|
||||
|
||||
verify_access(Access, ACL, Claims) ->
|
||||
case lists:all(
|
||||
fun ({Scope, Permission}) -> lists:member(Permission, wapi_acl:match(Scope, ACL)) end,
|
||||
Access
|
||||
) of
|
||||
true -> {ok, Claims};
|
||||
false -> {error, insufficient_access}
|
||||
end.
|
||||
|
||||
verify_claims(_, Error = {error, _}, _) ->
|
||||
Error;
|
||||
verify_claims(destination, {ok, _Claims}, _) ->
|
||||
ok;
|
||||
verify_claims(wallet,
|
||||
{ok, #{<<"amount">> := GrantAmount, <<"currency">> := Currency}},
|
||||
#{ <<"amount">> := ReqAmount, <<"currency">> := Currency }
|
||||
) when GrantAmount >= ReqAmount ->
|
||||
ok;
|
||||
verify_claims(_, _, _) ->
|
||||
{error, insufficient_claims}.
|
||||
|
||||
-type token_spec() ::
|
||||
{destinations, DestinationID :: binary()}.
|
||||
{destinations, DestinationID :: binary()} |
|
||||
{wallets, WalletID :: binary(), Asset :: map()}.
|
||||
|
||||
-spec issue_access_token(wapi_handler_utils:party_id(), token_spec()) ->
|
||||
-spec issue_access_token(wapi_handler_utils:owner(), token_spec()) ->
|
||||
wapi_authorizer_jwt:token().
|
||||
issue_access_token(PartyID, TokenSpec) ->
|
||||
issue_access_token(PartyID, TokenSpec, unlimited).
|
||||
|
||||
-type expiration() ::
|
||||
{deadline, machinery:timestamp() | pos_integer()} |
|
||||
{lifetime, Seconds :: pos_integer()} |
|
||||
unlimited .
|
||||
|
||||
-spec issue_access_token(wapi_handler_utils:party_id(), token_spec(), expiration()) ->
|
||||
-spec issue_access_token(wapi_handler_utils:owner(), token_spec(), wapi_authorizer_jwt:expiration()) ->
|
||||
wapi_authorizer_jwt:token().
|
||||
issue_access_token(PartyID, TokenSpec, Expiration0) ->
|
||||
Expiration = get_expiration(Expiration0),
|
||||
issue_access_token(PartyID, TokenSpec, Expiration) ->
|
||||
{Claims, ACL} = resolve_token_spec(TokenSpec),
|
||||
wapi_utils:unwrap(wapi_authorizer_jwt:issue({{PartyID, wapi_acl:from_list(ACL)}, Claims}, Expiration)).
|
||||
|
||||
-spec get_expiration(expiration()) ->
|
||||
wapi_authorizer_jwt:expiration().
|
||||
get_expiration(Exp = unlimited) ->
|
||||
Exp;
|
||||
get_expiration({deadline, {DateTime, Usec}}) ->
|
||||
{deadline, genlib_time:to_unixtime(DateTime) + Usec div 1000000};
|
||||
get_expiration(Exp = {deadline, _Sec}) ->
|
||||
Exp;
|
||||
get_expiration(Exp = {lifetime, _Sec}) ->
|
||||
Exp.
|
||||
|
||||
-type acl() :: [{wapi_acl:scope(), wapi_acl:permission()}].
|
||||
|
||||
-spec resolve_token_spec(token_spec()) ->
|
||||
{claims(), acl()}.
|
||||
resolve_token_spec({destinations, DestinationId}) ->
|
||||
Claims = #{},
|
||||
ACL = [
|
||||
{[party, {destinations, DestinationId}], read},
|
||||
{[party, {destinations, DestinationId}], write}
|
||||
],
|
||||
ACL = [{[party, {destinations, DestinationId}], write}],
|
||||
{Claims, ACL};
|
||||
resolve_token_spec({wallets, WalletId, #{<<"amount">> := Amount, <<"currency">> := Currency}}) ->
|
||||
Claims = #{<<"amount">> => Amount, <<"currency">> => Currency},
|
||||
ACL = [{[party, {wallets, WalletId}], write}],
|
||||
{Claims, ACL}.
|
||||
|
||||
-spec get_subject_id(context()) -> binary().
|
||||
@ -176,25 +232,16 @@ get_claim(ClaimName, {_Subject, Claims}, Default) ->
|
||||
%% -spec get_operation_access(operation_id(), request_data()) ->
|
||||
%% [{wapi_acl:scope(), wapi_acl:permission()}].
|
||||
|
||||
%% get_operation_access('StoreBankCard' , _) ->
|
||||
%% get_operation_access('CreateWithdrawal' , #{'WithdrawalParameters' := #{<<"walletGrant">> => }}) ->
|
||||
%% [{[payment_resources], write}].
|
||||
|
||||
-spec get_resource_hierarchy() -> #{atom() => map()}.
|
||||
|
||||
%% TODO add some sence in here
|
||||
%% TODO put some sense in here
|
||||
get_resource_hierarchy() ->
|
||||
#{
|
||||
party => #{
|
||||
wallets => #{},
|
||||
destinations => #{}
|
||||
wallets => #{},
|
||||
destinations => #{}
|
||||
}
|
||||
}.
|
||||
|
||||
-spec get_consumer(claims()) ->
|
||||
consumer().
|
||||
get_consumer(Claims) ->
|
||||
case maps:get(<<"cons">>, Claims, <<"merchant">>) of
|
||||
<<"merchant">> -> merchant;
|
||||
<<"client" >> -> client;
|
||||
<<"provider">> -> provider
|
||||
end.
|
||||
|
@ -280,12 +280,9 @@ validate_claims(Claims, [{Name, Claim, Validator} | Rest], Acc) ->
|
||||
validate_claims(Claims, [], Acc) ->
|
||||
{Acc, Claims}.
|
||||
|
||||
get_result(SubjectID, {_Roles, Claims}) ->
|
||||
get_result(SubjectID, {Roles, Claims}) ->
|
||||
try
|
||||
%% TODO use the real acl decode as soon as wapi roles/scopes are clearly defined
|
||||
%% Subject = {SubjectID, wapi_acl:decode(Roles)},
|
||||
|
||||
Subject = {SubjectID, wapi_acl:new()},
|
||||
Subject = {SubjectID, wapi_acl:decode(Roles)},
|
||||
{ok, {Subject, Claims}}
|
||||
catch
|
||||
error:{badarg, _} = Reason ->
|
||||
@ -351,6 +348,14 @@ decode_roles(Claims = #{
|
||||
}
|
||||
}) when is_list(Roles) ->
|
||||
{Roles, maps:remove(<<"resource_access">>, Claims)};
|
||||
decode_roles(Claims = #{
|
||||
<<"resource_access">> := #{
|
||||
<<"wallet-api">> := #{
|
||||
<<"roles">> := Roles
|
||||
}
|
||||
}
|
||||
}) when is_list(Roles) ->
|
||||
{Roles, maps:remove(<<"resource_access">>, Claims)};
|
||||
decode_roles(_) ->
|
||||
throw({invalid_token, {missing, acl}}).
|
||||
|
||||
|
@ -54,15 +54,15 @@
|
||||
handle_request(OperationID, Req, SwagContext = #{auth_context := AuthContext}, Handler, Opts) ->
|
||||
_ = lager:info("Processing request ~p", [OperationID]),
|
||||
try
|
||||
case wapi_auth:authorize_operation(OperationID, Req, AuthContext) of
|
||||
ok ->
|
||||
WoodyContext = create_woody_context(Req, AuthContext, Opts),
|
||||
Context = create_handler_context(SwagContext, WoodyContext),
|
||||
Handler:process_request(OperationID, Req, Context, Opts)
|
||||
%% ToDo: return back as soon, as authorization is implemented
|
||||
%% {error, _} = Error ->
|
||||
%% _ = lager:info("Operation ~p authorization failed due to ~p", [OperationID, Error]),
|
||||
%% wapi_handler_utils:reply_error(401, wapi_handler_utils:get_error_msg(<<"Unauthorized operation">>))
|
||||
WoodyContext = create_woody_context(Req, AuthContext, Opts),
|
||||
Context = create_handler_context(SwagContext, WoodyContext),
|
||||
case wapi_auth:authorize_operation(OperationID, Req, Context) of
|
||||
{ok, AuthDetails} ->
|
||||
ok = lager:info("Operation ~p authorized via ~p", [OperationID, AuthDetails]),
|
||||
Handler:process_request(OperationID, Req, Context, Opts);
|
||||
{error, Error} ->
|
||||
ok = lager:info("Operation ~p authorization failed due to ~p", [OperationID, Error]),
|
||||
wapi_handler_utils:reply_error(401, wapi_handler_utils:get_error_msg(<<"Unauthorized operation">>))
|
||||
end
|
||||
catch
|
||||
throw:{?request_result, Result} ->
|
||||
|
@ -67,7 +67,9 @@ redact_match([Capture], Message) ->
|
||||
%% mask(Dir, MaskLen, string:length(Str), MaskChar, Str).
|
||||
|
||||
%% mask(Dir, KeepStart, KeepLen, MaskChar, Str) ->
|
||||
%% unicode:characters_to_binary(string:pad(string:slice(Str, KeepStart, KeepLen), string:length(Str), Dir, MaskChar)).
|
||||
%% unicode:characters_to_binary(
|
||||
%% string:pad(string:slice(Str, KeepStart, KeepLen), string:length(Str), Dir, MaskChar)
|
||||
%% ).
|
||||
|
||||
-spec mask_and_keep(leading|trailing, non_neg_integer(), char(), binary()) ->
|
||||
binary().
|
||||
|
@ -1,6 +1,7 @@
|
||||
-module(wapi_wallet_ff_backend).
|
||||
|
||||
-include_lib("dmsl/include/dmsl_payment_processing_thrift.hrl").
|
||||
-include_lib("dmsl/include/dmsl_domain_thrift.hrl").
|
||||
|
||||
%% API
|
||||
-export([get_providers/2]).
|
||||
@ -205,7 +206,7 @@ get_identity_challenge_event(#{
|
||||
'eventID' := EventId
|
||||
}, Context) ->
|
||||
Mapper = fun
|
||||
({ID, {ev, Ts, {challenge, I, Body = {status_changed, _}}}}) when I =:= ChallengeId andalso ID =:= EventId ->
|
||||
({ID, {ev, Ts, {{challenge, I}, Body = {status_changed, _}}}}) when I =:= ChallengeId andalso ID =:= EventId ->
|
||||
{true, {ID, Ts, Body}};
|
||||
(_) ->
|
||||
false
|
||||
@ -232,7 +233,9 @@ create_wallet(Params = #{<<"identity">> := IdenityId}, Context) ->
|
||||
WalletId = next_id('wallet'),
|
||||
do(fun() ->
|
||||
_ = check_resource(identity, IdenityId, Context),
|
||||
ok = unwrap(ff_wallet_machine:create(WalletId, from_swag(wallet_params, Params), make_ctx(Params, [], Context))),
|
||||
ok = unwrap(
|
||||
ff_wallet_machine:create(WalletId, from_swag(wallet_params, Params), make_ctx(Params, [], Context))
|
||||
),
|
||||
unwrap(get_wallet(WalletId, Context))
|
||||
end).
|
||||
|
||||
@ -629,17 +632,16 @@ to_swag(challenge_status, {completed, C = #{resolution := approved}}) ->
|
||||
<<"validUntil">> => to_swag(timestamp, genlib_map:get(valid_until, C))
|
||||
});
|
||||
to_swag(challenge_status, {completed, #{resolution := denied}}) ->
|
||||
#{
|
||||
<<"status">> => <<"Failed">>,
|
||||
<<"failureReason">> => <<"Denied">>
|
||||
};
|
||||
to_swag(challenge_status, {failed, <<"Denied">>});
|
||||
to_swag(challenge_status, {failed, Reason}) ->
|
||||
%% TODO
|
||||
%% - Well, what if Reason is not scalar?
|
||||
#{
|
||||
<<"status">> => <<"Failed">>,
|
||||
<<"failureReason">> => genlib:to_binary(Reason)
|
||||
<<"failureReason">> => to_swag(challenge_failure_reason, Reason)
|
||||
};
|
||||
to_swag(challenge_failure_reason, Failure = #domain_Failure{}) ->
|
||||
to_swag(domain_failure, Failure);
|
||||
to_swag(challenge_failure_reason, Reason) ->
|
||||
genlib:to_binary(Reason);
|
||||
to_swag(identity_challenge_event, {ID, Ts, V}) ->
|
||||
#{
|
||||
<<"eventID">> => ID,
|
||||
@ -733,13 +735,17 @@ to_swag(withdrawal_status, pending) ->
|
||||
#{<<"status">> => <<"Pending">>};
|
||||
to_swag(withdrawal_status, succeeded) ->
|
||||
#{<<"status">> => <<"Succeeded">>};
|
||||
to_swag(withdrawal_status, {failed, Reason}) ->
|
||||
to_swag(withdrawal_status, {failed, Failure}) ->
|
||||
#{
|
||||
<<"status">> => <<"Failed">>,
|
||||
<<"failure">> => #{
|
||||
<<"code">> => genlib:to_binary(Reason)
|
||||
<<"code">> => to_swag(withdrawal_status_failure, Failure)
|
||||
}
|
||||
};
|
||||
to_swag(withdrawal_status_failure, Failure = #domain_Failure{}) ->
|
||||
to_swag(domain_failure, Failure);
|
||||
to_swag(withdrawal_status_failure, Failure) ->
|
||||
genlib:to_binary(Failure);
|
||||
to_swag(withdrawal_event, {EventId, Ts, {status_changed, Status}}) ->
|
||||
to_swag(map, #{
|
||||
<<"eventID">> => EventId,
|
||||
@ -763,6 +769,8 @@ to_swag(currency_object, V) ->
|
||||
<<"exponent">> => maps:get(exponent, V),
|
||||
<<"sign">> => maps:get(sign, V, undefined)
|
||||
});
|
||||
to_swag(domain_failure, Failure = #domain_Failure{}) ->
|
||||
erlang:list_to_binary(payproc_errors:format_raw(Failure));
|
||||
to_swag(is_blocked, {ok, accessible}) ->
|
||||
false;
|
||||
to_swag(is_blocked, _) ->
|
||||
|
@ -133,8 +133,10 @@ process_request('StartIdentityChallenge', #{
|
||||
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">>))
|
||||
{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', #{
|
||||
@ -145,13 +147,13 @@ process_request('GetIdentityChallenge', #{
|
||||
{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)
|
||||
{error, {challenge, notfound}} -> wapi_handler_utils:reply_ok(404)
|
||||
end;
|
||||
process_request('PollIdentityChallengeEvents', Params, Context, _Opts) ->
|
||||
case wapi_wallet_ff_backend:get_identity_challenge_events(Params, Context) of
|
||||
{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)
|
||||
{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_wallet_ff_backend:get_identity_challenge_event(Params, Context) of
|
||||
@ -191,19 +193,24 @@ process_request('GetWalletAccount', #{'walletID' := WalletId}, Context, _Opts) -
|
||||
{error, {wallet, notfound}} -> wapi_handler_utils:reply_ok(404);
|
||||
{error, {wallet, unauthorized}} -> wapi_handler_utils:reply_ok(404)
|
||||
end;
|
||||
|
||||
process_request('IssueWalletGrant', #{
|
||||
'walletID' := WalletId,
|
||||
'WalletGrantRequest' := #{<<"validUntil">> := Expiration, <<"asset">> := Asset}
|
||||
}, Context, _Opts) ->
|
||||
case wapi_wallet_ff_backend:get_wallet(WalletId, Context) of
|
||||
{ok, _} ->
|
||||
%% TODO issue token properly
|
||||
wapi_handler_utils:reply_ok(201, #{
|
||||
<<"token">> => issue_grant_token(wallets, WalletId, Expiration, #{<<"asset">> => Asset}),
|
||||
<<"validUntil">> => Expiration,
|
||||
<<"asset">> => Asset
|
||||
});
|
||||
case issue_grant_token({wallets, WalletId, Asset}, Expiration, Context) of
|
||||
{ok, Token} ->
|
||||
wapi_handler_utils:reply_ok(201, #{
|
||||
<<"token">> => Token,
|
||||
<<"validUntil">> => Expiration,
|
||||
<<"asset">> => Asset
|
||||
});
|
||||
{error, expired} ->
|
||||
wapi_handler_utils:reply_ok(422,
|
||||
wapi_handler_utils:get_error_msg(<<"Invalid expiration: already expired">>)
|
||||
)
|
||||
end;
|
||||
{error, {wallet, notfound}} ->
|
||||
wapi_handler_utils:reply_ok(404);
|
||||
{error, {wallet, unauthorized}} ->
|
||||
@ -242,20 +249,25 @@ process_request('IssueDestinationGrant', #{
|
||||
'destinationID' := DestinationId,
|
||||
'DestinationGrantRequest' := #{<<"validUntil">> := Expiration}
|
||||
}, Context, _Opts) ->
|
||||
%% TODO issue token properly
|
||||
case wapi_wallet_ff_backend:get_destination(DestinationId, Context) of
|
||||
{ok, _} ->
|
||||
wapi_handler_utils:reply_ok(201, #{
|
||||
<<"token">> => issue_grant_token(destinations, DestinationId, Expiration, #{}),
|
||||
<<"validUntil">> => Expiration
|
||||
});
|
||||
case issue_grant_token({destinations, DestinationId}, Expiration, Context) of
|
||||
{ok, Token} ->
|
||||
wapi_handler_utils:reply_ok(201, #{
|
||||
<<"token">> => Token,
|
||||
<<"validUntil">> => Expiration
|
||||
});
|
||||
{error, expired} ->
|
||||
wapi_handler_utils:reply_ok(422,
|
||||
wapi_handler_utils:get_error_msg(<<"Invalid expiration: already expired">>)
|
||||
)
|
||||
end;
|
||||
{error, {destination, notfound}} ->
|
||||
wapi_handler_utils:reply_ok(404);
|
||||
{error, {destination, unauthorized}} ->
|
||||
wapi_handler_utils:reply_ok(404)
|
||||
end;
|
||||
process_request('CreateWithdrawal', #{'WithdrawalParameters' := Params}, Context, Opts) ->
|
||||
%% TODO: properly check authorization tokens here
|
||||
case wapi_wallet_ff_backend:create_withdrawal(Params, Context) of
|
||||
{ok, Withdrawal = #{<<"id">> := WithdrawalId}} ->
|
||||
wapi_handler_utils:reply_ok(202, Withdrawal, get_location('GetWithdrawal', [WithdrawalId], Opts));
|
||||
@ -268,11 +280,17 @@ process_request('CreateWithdrawal', #{'WithdrawalParameters' := Params}, Context
|
||||
{error, {provider, notfound}} ->
|
||||
wapi_handler_utils:reply_ok(422, wapi_handler_utils:get_error_msg(<<"No such provider">>));
|
||||
{error, {wallet, {inaccessible, _}}} ->
|
||||
wapi_handler_utils:reply_ok(422, wapi_handler_utils:get_error_msg(<<"Inaccessible source or destination">>));
|
||||
wapi_handler_utils:reply_ok(422,
|
||||
wapi_handler_utils:get_error_msg(<<"Inaccessible source or destination">>)
|
||||
);
|
||||
{error, {wallet, {currency, invalid}}} ->
|
||||
wapi_handler_utils:reply_ok(422, wapi_handler_utils:get_error_msg(<<"Invalid currency for source or destination">>));
|
||||
wapi_handler_utils:reply_ok(422,
|
||||
wapi_handler_utils:get_error_msg(<<"Invalid currency for source or destination">>)
|
||||
);
|
||||
{error, {wallet, {provider, invalid}}} ->
|
||||
wapi_handler_utils:reply_ok(422, wapi_handler_utils:get_error_msg(<<"Invalid provider for source or destination">>))
|
||||
wapi_handler_utils:reply_ok(422,
|
||||
wapi_handler_utils:get_error_msg(<<"Invalid provider for source or destination">>)
|
||||
)
|
||||
end;
|
||||
process_request('GetWithdrawal', #{'withdrawalID' := WithdrawalId}, Context, _Opts) ->
|
||||
case wapi_wallet_ff_backend:get_withdrawal(WithdrawalId, Context) of
|
||||
@ -321,25 +339,20 @@ get_location(OperationId, Params, Opts) ->
|
||||
not_implemented() ->
|
||||
wapi_handler_utils:throw_not_implemented().
|
||||
|
||||
issue_grant_token(TokenSpec, Expiration, Context) ->
|
||||
case get_expiration_deadline(Expiration) of
|
||||
{ok, Deadline} ->
|
||||
{ok, wapi_auth:issue_access_token(wapi_handler_utils:get_owner(Context), TokenSpec, {deadline, Deadline})};
|
||||
Error = {error, _} ->
|
||||
Error
|
||||
end.
|
||||
|
||||
issue_grant_token(Type, Id, Expiration, Meta) when is_map(Meta) ->
|
||||
wapi_utils:map_to_base64url(#{
|
||||
<<"resourceType">> => Type,
|
||||
<<"resourceID">> => Id,
|
||||
<<"validUntil">> => Expiration,
|
||||
<<"metadata">> => Meta
|
||||
}).
|
||||
|
||||
%% TODO issue token properly
|
||||
%%
|
||||
%% issue_grant_token(destinations, Id, Expiration, _Meta, Context) ->
|
||||
%% {ok, {Date, Time, Usec, _Tz}} = rfc3339:parse(Expiration),
|
||||
%% wapi_auth:issue_access_token(
|
||||
%% wapi_handler_utils:get_owner(Context),
|
||||
%% {destinations, Id},
|
||||
%% {deadline, {{Date, Time}, Usec}}
|
||||
%% ).
|
||||
%%
|
||||
%% is_expired(Expiration) ->
|
||||
%% {ok, ExpirationSec} = rfc3339:to_time(Expiration, second),
|
||||
%% (genlib_time:unow() - ExpirationSec) >= 0.
|
||||
get_expiration_deadline(Expiration) ->
|
||||
{DateTime, MilliSec} = woody_deadline:from_binary(wapi_utils:to_universal_time(Expiration)),
|
||||
Deadline = genlib_time:daytime_to_unixtime(DateTime) + MilliSec div 1000,
|
||||
case genlib_time:unow() - Deadline < 0 of
|
||||
true ->
|
||||
{ok, Deadline};
|
||||
false ->
|
||||
{error, expired}
|
||||
end.
|
||||
|
@ -14,7 +14,7 @@
|
||||
{elvis_style, nesting_level, #{level => 3}},
|
||||
{elvis_style, god_modules, #{limit => 30, ignore => [hg_client_party]}},
|
||||
{elvis_style, no_if_expression},
|
||||
{elvis_style, invalid_dynamic_call, #{ignore => [elvis]}},
|
||||
{elvis_style, invalid_dynamic_call, #{ignore => [wapi_swagger_server]}},
|
||||
{elvis_style, used_ignored_variable},
|
||||
{elvis_style, no_behavior_info},
|
||||
{elvis_style, module_naming_convention, #{regex => "^([a-z][a-z0-9]*_?)*(_SUITE)?$"}},
|
||||
|
@ -1,5 +1,6 @@
|
||||
{"1.1.0",
|
||||
[{<<"base64url">>,{pkg,<<"base64url">>,<<"0.0.1">>},0},
|
||||
{<<"cache">>,{pkg,<<"cache">>,<<"2.2.0">>},1},
|
||||
{<<"certifi">>,{pkg,<<"certifi">>,<<"0.7.0">>},1},
|
||||
{<<"cg_mon">>,
|
||||
{git,"https://github.com/rbkmoney/cg_mon.git",
|
||||
@ -64,7 +65,7 @@
|
||||
0},
|
||||
{<<"machinery">>,
|
||||
{git,"git@github.com:rbkmoney/machinery.git",
|
||||
{ref,"eb1beed9a287d8b6ab8c68b782b2143ef574c99d"}},
|
||||
{ref,"ef24b4fff41b88981e0441b4097cbaa016e8e1b5"}},
|
||||
0},
|
||||
{<<"metrics">>,{pkg,<<"metrics">>,<<"1.0.1">>},1},
|
||||
{<<"mg_proto">>,
|
||||
@ -99,7 +100,7 @@
|
||||
{<<"uuid">>,{pkg,<<"uuid_erl">>,<<"1.7.3">>},0},
|
||||
{<<"woody">>,
|
||||
{git,"git@github.com:rbkmoney/woody_erlang.git",
|
||||
{ref,"06ef3d63c0b6777e7cfa4b4f949eb34008291c0e"}},
|
||||
{ref,"94eb44904e817e615f5e1586d6f3432cdadd5e29"}},
|
||||
0},
|
||||
{<<"woody_user_identity">>,
|
||||
{git,"git@github.com:rbkmoney/woody_erlang_user_identity.git",
|
||||
@ -108,6 +109,7 @@
|
||||
[
|
||||
{pkg_hash,[
|
||||
{<<"base64url">>, <<"36A90125F5948E3AFD7BE97662A1504B934DD5DAC78451CA6E9ABF85A10286BE">>},
|
||||
{<<"cache">>, <<"3C11DBF4CD8FCD5787C95A5FB2A04038E3729CFCA0386016EEA8C953AB48A5AB">>},
|
||||
{<<"certifi">>, <<"861A57F3808F7EB0C2D1802AFEAAE0FA5DE813B0DF0979153CBAFCD853ABABAF">>},
|
||||
{<<"cowboy">>, <<"A324A8DF9F2316C833A470D918AAF73AE894278B8AA6226CE7A9BF699388F878">>},
|
||||
{<<"cowlib">>, <<"9D769A1D062C9C3AC753096F868CA121E2730B9A377DE23DEC0F7E08B1DF84EE">>},
|
||||
|
Loading…
Reference in New Issue
Block a user