MSPF-206: Add user identity extension initial attempt (#1)

* MSPF-206 Add user identity extension initial attempt
This commit is contained in:
Artem Ocheredko 2017-04-04 18:46:47 +04:00 committed by GitHub
parent 89d8d0ec3e
commit ef4bd49144
12 changed files with 471 additions and 2 deletions

16
.gitignore vendored Normal file
View File

@ -0,0 +1,16 @@
.rebar3
_*
.eunit
*.o
*.beam
*.plt
*.swp
*.swo
.erlang.cookie
ebin
log
erl_crash.dump
.rebar
logs
_build
erlang.sublime-*

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "build_utils"]
path = build_utils
url = git@github.com:rbkmoney/build_utils.git

43
Jenkinsfile vendored Normal file
View File

@ -0,0 +1,43 @@
#!groovy
def finalHook = {
runStage('store CT logs') {
archive '_build/test/logs/'
}
}
build('woody_user_identity', 'docker-host', finalHook) {
checkoutRepo()
loadBuildUtils()
def pipeDefault
def withWsCache
runStage('load pipeline') {
env.JENKINS_LIB = "build_utils/jenkins_lib"
pipeDefault = load("${env.JENKINS_LIB}/pipeDefault.groovy")
withWsCache = load("${env.JENKINS_LIB}/withWsCache.groovy")
}
pipeDefault() {
runStage('compile') {
withGithubPrivkey {
sh 'make wc_compile'
}
}
runStage('lint') {
sh 'make wc_lint'
}
runStage('xref') {
sh 'make wc_xref'
}
runStage('dialyze') {
withWsCache("_build/default/rebar3_19.1_plt") {
sh 'make wc_dialyze'
}
}
runStage('test') {
sh "make wc_test"
}
}
}

58
Makefile Normal file
View File

@ -0,0 +1,58 @@
REBAR := $(shell which rebar3 2>/dev/null || which ./rebar3)
SUBMODULES = build_utils
SUBTARGETS = $(patsubst %,%/.git,$(SUBMODULES))
UTILS_PATH := build_utils
TEMPLATES_PATH := .
BUILD_IMAGE_TAG := 55e987e74e9457191a5b4a7c5dc9e3838ae82d2b
CALL_ANYWHERE := all submodules rebar-update compile xref lint dialyze test start devrel release clean distclean
SERVICE_NAME := woody_user_identity
# Hint: 'test' might be a candidate for CALL_W_CONTAINER-only target
CALL_W_CONTAINER := $(CALL_ANYWHERE)
.PHONY: $(CALL_W_CONTAINER) generate
all: compile
-include $(UTILS_PATH)/make_lib/utils_container.mk
$(SUBTARGETS): %/.git: %
git submodule update --init $<
touch $@
submodules: $(SUBTARGETS)
rebar-update:
$(REBAR) update
compile: submodules rebar-update
$(REBAR) compile
devrel: submodules
$(REBAR) release
test: submodules
$(REBAR) do eunit, ct
xref: submodules
$(REBAR) xref
clean:
$(REBAR) clean
distclean: clean
rm -rf _build
dialyze:
$(REBAR) dialyzer
release:
$(REBAR) as prod release
lint:
elvis rock

View File

@ -1,2 +1,9 @@
# woody_erlang_user_identity
Woody Erlang extension for user identity
Woody User Identity
=====
A simple library that allows to manipulate user identity through context
Build
-----
$ rebar3 compile

1
build_utils Submodule

@ -0,0 +1 @@
Subproject commit 877ae1c8299675fff8c4d445657b60064176b077

48
elvis.config Normal file
View File

@ -0,0 +1,48 @@
[
{elvis, [
{config, [
#{
dirs => ["src"],
filter => "*.erl",
rules => [
{elvis_style, line_length, #{limit => 120, skip_comments => false}},
{elvis_style, no_tabs},
{elvis_style, no_trailing_whitespace},
{elvis_style, macro_module_names},
{elvis_style, operator_spaces, #{rules => [{right, ","}, {right, "++"}, {left, "++"}]}},
{elvis_style, nesting_level, #{level => 4}},
{elvis_style, god_modules, #{limit => 25}},
{elvis_style, no_if_expression},
{elvis_style, invalid_dynamic_call, #{ignore => [elvis]}},
{elvis_style, used_ignored_variable},
{elvis_style, no_behavior_info},
{elvis_style, module_naming_convention, #{regex => "^([a-z][a-z0-9]*_?)*(_SUITE)?$"}},
{elvis_style, function_naming_convention, #{regex => "^([a-z][a-z0-9]*_?)*$"}},
{elvis_style, state_record_and_type},
{elvis_style, no_spec_with_records},
{elvis_style, dont_repeat_yourself, #{min_complexity => 10}},
{elvis_style, no_debug_call, #{ignore => [elvis, elvis_utils]}}
]
},
#{
dirs => ["."],
filter => "Makefile",
ruleset => makefiles
},
#{
dirs => ["."],
filter => "elvis.config",
ruleset => elvis_config
},,
#{
dirs => ["."],
filter => "rebar.config",
rules => [
{elvis_style, line_length, #{limit => 120, skip_comments => false}},
{elvis_style, no_tabs},
{elvis_style, no_trailing_whitespace}
]
}
]}
]}
].

44
rebar.config Normal file
View File

@ -0,0 +1,44 @@
% Common project erlang options.
{erl_opts, [
% mandatory
debug_info,
warnings_as_errors,
warn_export_all,
warn_missing_spec,
warn_untyped_record,
warn_export_vars,
% by default
warn_unused_record,
warn_bif_clash,
warn_obsolete_guard,
warn_unused_vars,
warn_shadow_vars,
warn_unused_import,
warn_unused_function,
warn_deprecated_function
]}.
% Common project dependencies.
{deps, [
{woody, ".*", {git,"git@github.com:rbkmoney/woody_erlang.git",{branch, "master"}}},
{genlib, {git, "https://github.com/rbkmoney/genlib.git", {branch, "master"}}}
]}.
{xref_checks, [
undefined_function_calls,
undefined_functions,
deprecated_functions_calls,
deprecated_functions,
locals_not_used
]}.
{dialyzer, [
{warnings, [
unmatched_returns,
error_handling,
race_conditions,
unknown
]},
{plt_apps, all_deps}
]}.

38
rebar.lock Normal file
View File

@ -0,0 +1,38 @@
{"1.1.0",
[{<<"certifi">>,{pkg,<<"certifi">>,<<"0.7.0">>},2},
{<<"cowboy">>,{pkg,<<"cowboy">>,<<"1.0.4">>},1},
{<<"cowlib">>,{pkg,<<"cowlib">>,<<"1.0.2">>},2},
{<<"genlib">>,
{git,"https://github.com/rbkmoney/genlib.git",
{ref,"82ff16f4314fc406dd90752467a08fe401b009ef"}},
1},
{<<"hackney">>,{pkg,<<"hackney">>,<<"1.6.2">>},1},
{<<"idna">>,{pkg,<<"idna">>,<<"1.2.0">>},2},
{<<"metrics">>,{pkg,<<"metrics">>,<<"1.0.1">>},2},
{<<"mimerl">>,{pkg,<<"mimerl">>,<<"1.0.2">>},2},
{<<"ranch">>,{pkg,<<"ranch">>,<<"1.3.2">>},2},
{<<"snowflake">>,
{git,"https://github.com/rbkmoney/snowflake.git",
{ref,"0a598108f6582affe3b4ae550fc5b9f2062e318a"}},
1},
{<<"ssl_verify_fun">>,{pkg,<<"ssl_verify_fun">>,<<"1.1.1">>},2},
{<<"thrift">>,
{git,"https://github.com/rbkmoney/thrift_erlang.git",
{ref,"240bbc842f6e9b90d01bd07838778cf48752b510"}},
1},
{<<"woody">>,
{git,"git@github.com:rbkmoney/woody_erlang.git",
{ref,"8f1f42e209a272570c5355599f908339cd097b5b"}},
0}]}.
[
{pkg_hash,[
{<<"certifi">>, <<"861A57F3808F7EB0C2D1802AFEAAE0FA5DE813B0DF0979153CBAFCD853ABABAF">>},
{<<"cowboy">>, <<"A324A8DF9F2316C833A470D918AAF73AE894278B8AA6226CE7A9BF699388F878">>},
{<<"cowlib">>, <<"9D769A1D062C9C3AC753096F868CA121E2730B9A377DE23DEC0F7E08B1DF84EE">>},
{<<"hackney">>, <<"96A0A5E7E65B7ACAD8031D231965718CC70A9B4131A8B033B7543BBD673B8210">>},
{<<"idna">>, <<"AC62EE99DA068F43C50DC69ACF700E03A62A348360126260E87F2B54ECED86B2">>},
{<<"metrics">>, <<"25F094DEA2CDA98213CECC3AEFF09E940299D950904393B2A29D191C346A8486">>},
{<<"mimerl">>, <<"993F9B0E084083405ED8252B99460C4F0563E41729AB42D9074FD5E52439BE88">>},
{<<"ranch">>, <<"E4965A144DC9FBE70E5C077C65E73C57165416A901BD02EA899CFD95AA890986">>},
{<<"ssl_verify_fun">>, <<"28A4D65B7F59893BC2C7DE786DEC1E1555BD742D336043FE644AE956C3497FBE">>}]}
].

View File

@ -0,0 +1,17 @@
{application, woody_user_identity,[
{description, "A library to manipulate user identity through woody context"},
{vsn, "0.1.0"},
{registered, []},
{applications,
[
kernel,
stdlib,
woody
]},
{env,[]},
{modules, []},
{maintainers, []},
{licenses, []},
{links, []}
]}.

105
src/woody_user_identity.erl Normal file
View File

@ -0,0 +1,105 @@
-module(woody_user_identity).
%% API exports
-export([put/2]).
-export([get/1]).
-type id() :: binary().
-type email() :: binary().
-type username() :: binary().
-type user_identity() :: #{
id := id(),
email => email(),
username => username()
}.
-export_type([id/0]).
-export_type([email/0]).
-export_type([username/0]).
-export_type([user_identity/0]).
-define(PREFIX, <<"user-identity.">>).
-type key() :: atom().
-type rules() :: [{required, boolean()}].
%%====================================================================
%% API functions
%%====================================================================
-spec put(user_identity(), woody_context:ctx()) -> woody_context:ctx() | no_return().
put(Identity, Context) ->
Meta = prepare_meta(Identity),
woody_context:add_meta(Context, Meta).
-spec get(woody_context:ctx()) -> user_identity() | no_return().
get(Context) ->
get_user_identity(Context).
%%====================================================================
%% Internal functions
%%====================================================================
-spec prepare_meta(map()) -> map() | no_return().
prepare_meta(Identity) ->
lists:foldl(
fun({Key, Rules}, Acc) ->
Required = is_required(Rules),
case maps:get(Key, Identity, undefined) of
undefined when Required =:= false->
Acc;
undefined ->
missing_required_error(Key);
Value ->
MetaKey = encode_key(Key),
Acc#{MetaKey => Value}
end
end,
#{},
get_keys_info()
).
-spec get_user_identity(woody_context:ctx()) -> user_identity() | no_return().
get_user_identity(Context) ->
lists:foldl(
fun({Key, Rules}, Acc) ->
MetaKey = encode_key(Key),
Required = is_required(Rules),
case woody_context:get_meta(MetaKey, Context) of
undefined when Required =:= false->
Acc;
undefined ->
missing_required_error(Key);
Value ->
Acc#{Key => Value}
end
end,
#{},
get_keys_info()
).
-spec get_keys_info() -> [{key(), rules()}].
get_keys_info() ->
[
{id, [{required, true}]},
{email, []},
{username, []}
].
-spec is_required(rules()) -> boolean().
is_required(Rules) ->
case proplists:get_value(required, Rules, false) of
true ->
true;
_ ->
false
end.
-spec encode_key(key()) -> binary().
encode_key(Key) when is_atom(Key) ->
<<(?PREFIX)/binary, (genlib:to_binary(Key))/binary>>.
-spec missing_required_error(key()) -> no_return().
missing_required_error(Key) ->
throw({missing_required, Key}).

View File

@ -0,0 +1,89 @@
-module(woody_user_identity_SUITE).
-include_lib("common_test/include/ct.hrl").
%% common test API
-export([
all/0,
init_per_suite/1,
end_per_suite/1
]).
-export([
put_get_ok_test/1,
put_get_incomplete_ok_test/1,
put_missing_required_error_test/1,
get_missing_required_error_test/1
]).
%%
%% tests descriptions
%%
-spec all() -> _.
all() ->
[
put_get_ok_test,
put_get_incomplete_ok_test,
put_missing_required_error_test,
get_missing_required_error_test
].
%%
%% starting/stopping
%%
-spec init_per_suite(_) -> _.
init_per_suite(C) ->
{ok, Apps} = application:ensure_all_started(woody),
[{apps, Apps}|C].
-spec end_per_suite(_) -> _.
end_per_suite(C) ->
[application:stop(App) || App <- proplists:get_value(apps, C)].
%%
%% tests
%%
-spec put_get_ok_test(_) -> _.
put_get_ok_test(_) ->
Context0 = woody_context:new(),
Identity = #{
id => <<"UserID">>,
email => <<"UserEmail">>,
username => <<"UserName">>
},
Context = woody_user_identity:put(Identity, Context0),
Identity = woody_user_identity:get(Context).
-spec put_get_incomplete_ok_test(_) -> _.
put_get_incomplete_ok_test(_) ->
Context0 = woody_context:new(),
Identity = #{id => <<"UserID">>},
Context = woody_user_identity:put(Identity, Context0),
Identity = woody_user_identity:get(Context).
-spec put_missing_required_error_test(_) -> _.
put_missing_required_error_test(_) ->
Context0 = woody_context:new(),
Identity = #{email => <<"test@test.com">>},
ok = try
woody_user_identity:put(Identity, Context0),
error
catch
throw:{missing_required, id} ->
ok
end.
-spec get_missing_required_error_test(_) -> _.
get_missing_required_error_test(_) ->
Context = woody_context:new(),
ok = try
woody_user_identity:get(Context),
error
catch
throw:{missing_required, id} ->
ok
end.