|
|
@@ -92,21 +92,79 @@ add_default_user() ->
|
|
|
add_user(Username, Password, Desc) when
|
|
|
is_binary(Username), is_binary(Password)
|
|
|
->
|
|
|
- case legal_username(Username) of
|
|
|
- true ->
|
|
|
- return(
|
|
|
- mria:transaction(?DASHBOARD_SHARD, fun add_user_/3, [Username, Password, Desc])
|
|
|
- );
|
|
|
- false ->
|
|
|
+ case {legal_username(Username), legal_password(Password)} of
|
|
|
+ {ok, ok} -> do_add_user(Username, Password, Desc);
|
|
|
+ {{error, Reason}, _} -> {error, Reason};
|
|
|
+ {_, {error, Reason}} -> {error, Reason}
|
|
|
+ end.
|
|
|
+
|
|
|
+do_add_user(Username, Password, Desc) ->
|
|
|
+ Res = mria:transaction(?DASHBOARD_SHARD, fun add_user_/3, [Username, Password, Desc]),
|
|
|
+ return(Res).
|
|
|
+
|
|
|
+%% 0-9 or A-Z or a-z or $_
|
|
|
+legal_username(<<>>) ->
|
|
|
+ {error, <<"Username can not be empty">>};
|
|
|
+legal_username(UserName) ->
|
|
|
+ case re:run(UserName, "^[_a-zA-Z0-9]*$", [{capture, none}]) of
|
|
|
+ nomatch ->
|
|
|
{error, <<
|
|
|
"Bad Username."
|
|
|
" Only upper and lower case letters, numbers and underscores are supported"
|
|
|
- >>}
|
|
|
+ >>};
|
|
|
+ match ->
|
|
|
+ ok
|
|
|
+ end.
|
|
|
+
|
|
|
+-define(LOW_LETTER_CHARS, "abcdefghijklmnopqrstuvwxyz").
|
|
|
+-define(UPPER_LETTER_CHARS, "ABCDEFGHIJKLMNOPQRSTUVWXYZ").
|
|
|
+-define(LETTER, ?LOW_LETTER_CHARS ++ ?UPPER_LETTER_CHARS).
|
|
|
+-define(NUMBER, "0123456789").
|
|
|
+-define(SPECIAL_CHARS, "!@#$%^&*()_+-=[]{}\"|;':,./<>?`~ ").
|
|
|
+-define(INVALID_PASSWORD_MSG, <<
|
|
|
+ "Bad username."
|
|
|
+ "At least two different kind of characters from groups of letters, numbers, and special characters."
|
|
|
+ "For example, if password is composed from letters, it must contain at least one number or a special character."
|
|
|
+>>).
|
|
|
+-define(BAD_PASSWORD_LEN, <<"The range of password length is 8~64">>).
|
|
|
+
|
|
|
+legal_password(Password) when is_binary(Password) ->
|
|
|
+ legal_password(binary_to_list(Password));
|
|
|
+legal_password(Password) when is_list(Password) ->
|
|
|
+ legal_password(Password, erlang:length(Password)).
|
|
|
+
|
|
|
+legal_password(Password, Len) when Len >= 8 andalso Len =< 64 ->
|
|
|
+ case is_mixed_password(Password) of
|
|
|
+ true -> ascii_character_validate(Password);
|
|
|
+ false -> {error, ?INVALID_PASSWORD_MSG}
|
|
|
+ end;
|
|
|
+legal_password(_Password, _Len) ->
|
|
|
+ {error, ?BAD_PASSWORD_LEN}.
|
|
|
+
|
|
|
+%% The password must contain at least two different kind of characters
|
|
|
+%% from groups of letters, numbers, and special characters.
|
|
|
+is_mixed_password(Password) -> is_mixed_password(Password, [?NUMBER, ?LETTER, ?SPECIAL_CHARS], 0).
|
|
|
+
|
|
|
+is_mixed_password(_Password, _Chars, 2) ->
|
|
|
+ true;
|
|
|
+is_mixed_password(_Password, [], _Count) ->
|
|
|
+ false;
|
|
|
+is_mixed_password(Password, [Chars | Rest], Count) ->
|
|
|
+ NewCount =
|
|
|
+ case contain(Password, Chars) of
|
|
|
+ true -> Count + 1;
|
|
|
+ false -> Count
|
|
|
+ end,
|
|
|
+ is_mixed_password(Password, Rest, NewCount).
|
|
|
+
|
|
|
+%% regex-non-ascii-character, such as Chinese, Japanese, Korean, etc.
|
|
|
+ascii_character_validate(Password) ->
|
|
|
+ case re:run(Password, "[^\\x00-\\x7F]+", [unicode, {capture, none}]) of
|
|
|
+ match -> {error, <<"Only ascii characters are allowed in the password">>};
|
|
|
+ nomatch -> ok
|
|
|
end.
|
|
|
|
|
|
-%% 0 - 9 or A -Z or a - z or $_
|
|
|
-legal_username(<<>>) -> false;
|
|
|
-legal_username(UserName) -> nomatch /= re:run(UserName, "^[_a-zA-Z0-9]*$").
|
|
|
+contain(Xs, Spec) -> lists:any(fun(X) -> lists:member(X, Spec) end, Xs).
|
|
|
|
|
|
%% black-magic: force overwrite a user
|
|
|
force_add_user(Username, Password, Desc) ->
|
|
|
@@ -188,7 +246,10 @@ change_password(Username, OldPasswd, NewPasswd) when is_binary(Username) ->
|
|
|
end.
|
|
|
|
|
|
change_password(Username, Password) when is_binary(Username), is_binary(Password) ->
|
|
|
- change_password_hash(Username, hash(Password)).
|
|
|
+ case legal_password(Password) of
|
|
|
+ ok -> change_password_hash(Username, hash(Password));
|
|
|
+ Error -> Error
|
|
|
+ end.
|
|
|
|
|
|
change_password_hash(Username, PasswordHash) ->
|
|
|
ChangePWD =
|
|
|
@@ -292,6 +353,45 @@ add_default_user(Username, Password) when ?EMPTY_KEY(Username) orelse ?EMPTY_KEY
|
|
|
{ok, empty};
|
|
|
add_default_user(Username, Password) ->
|
|
|
case lookup_user(Username) of
|
|
|
- [] -> add_user(Username, Password, <<"administrator">>);
|
|
|
+ [] -> do_add_user(Username, Password, <<"administrator">>);
|
|
|
_ -> {ok, default_user_exists}
|
|
|
end.
|
|
|
+
|
|
|
+-ifdef(TEST).
|
|
|
+-include_lib("eunit/include/eunit.hrl").
|
|
|
+
|
|
|
+legal_password_test() ->
|
|
|
+ ?assertEqual({error, ?BAD_PASSWORD_LEN}, legal_password(<<"123">>)),
|
|
|
+ MaxPassword = iolist_to_binary([lists:duplicate(63, "x"), "1"]),
|
|
|
+ ?assertEqual(ok, legal_password(MaxPassword)),
|
|
|
+ TooLongPassword = lists:duplicate(65, "y"),
|
|
|
+ ?assertEqual({error, ?BAD_PASSWORD_LEN}, legal_password(TooLongPassword)),
|
|
|
+
|
|
|
+ ?assertEqual({error, ?INVALID_PASSWORD_MSG}, legal_password(<<"12345678">>)),
|
|
|
+ ?assertEqual({error, ?INVALID_PASSWORD_MSG}, legal_password(?LETTER)),
|
|
|
+ ?assertEqual({error, ?INVALID_PASSWORD_MSG}, legal_password(?NUMBER)),
|
|
|
+ ?assertEqual({error, ?INVALID_PASSWORD_MSG}, legal_password(?SPECIAL_CHARS)),
|
|
|
+ ?assertEqual({error, ?INVALID_PASSWORD_MSG}, legal_password(<<"映映映映无天在请"/utf8>>)),
|
|
|
+ ?assertEqual(
|
|
|
+ {error, <<"Only ascii characters are allowed in the password">>},
|
|
|
+ legal_password(<<"️test_for_non_ascii1中"/utf8>>)
|
|
|
+ ),
|
|
|
+ ?assertEqual(
|
|
|
+ {error, <<"Only ascii characters are allowed in the password">>},
|
|
|
+ legal_password(<<"云☁️test_for_unicode"/utf8>>)
|
|
|
+ ),
|
|
|
+
|
|
|
+ ?assertEqual(ok, legal_password(?LOW_LETTER_CHARS ++ ?NUMBER)),
|
|
|
+ ?assertEqual(ok, legal_password(?UPPER_LETTER_CHARS ++ ?NUMBER)),
|
|
|
+ ?assertEqual(ok, legal_password(?LOW_LETTER_CHARS ++ ?SPECIAL_CHARS)),
|
|
|
+ ?assertEqual(ok, legal_password(?UPPER_LETTER_CHARS ++ ?SPECIAL_CHARS)),
|
|
|
+ ?assertEqual(ok, legal_password(?SPECIAL_CHARS ++ ?NUMBER)),
|
|
|
+
|
|
|
+ ?assertEqual(ok, legal_password(<<"abckldiekflkdf12">>)),
|
|
|
+ ?assertEqual(ok, legal_password(<<"abckldiekflkdf w">>)),
|
|
|
+ ?assertEqual(ok, legal_password(<<"# abckldiekflkdf w">>)),
|
|
|
+ ?assertEqual(ok, legal_password(<<"# 12344858">>)),
|
|
|
+ ?assertEqual(ok, legal_password(<<"# %12344858">>)),
|
|
|
+ ok.
|
|
|
+
|
|
|
+-endif.
|