Import definitions: support user limits
This commit is contained in:
parent
dcb8e0f877
commit
a22e9abb89
|
|
@ -20,9 +20,9 @@
|
|||
set_tags/3, set_permissions/6, clear_permissions/3,
|
||||
set_topic_permissions/6, clear_topic_permissions/3, clear_topic_permissions/4,
|
||||
add_user_sans_validation/3, put_user/2, put_user/3,
|
||||
change_password_and_tags/4,
|
||||
change_password_hash_and_tags/4,
|
||||
add_user_sans_validation/5]).
|
||||
update_user/5,
|
||||
update_user_with_hash/5,
|
||||
add_user_sans_validation/6]).
|
||||
|
||||
-export([set_user_limits/3, clear_user_limits/3, is_over_connection_limit/1,
|
||||
is_over_channel_limit/1, get_user_limits/0, get_user_limits/1]).
|
||||
|
|
@ -215,18 +215,21 @@ add_user(Username, Password, ActingUser) ->
|
|||
rabbit_types:username(), [atom()]) -> 'ok' | {'error', string()}.
|
||||
|
||||
add_user(Username, Password, ActingUser, Tags) ->
|
||||
add_user(Username, Password, ActingUser, undefined, Tags).
|
||||
|
||||
add_user(Username, Password, ActingUser, Limits, Tags) ->
|
||||
validate_and_alternate_credentials(Username, Password, ActingUser,
|
||||
add_user_sans_validation(Tags)).
|
||||
add_user_sans_validation(Limits, Tags)).
|
||||
|
||||
add_user_sans_validation(Username, Password, ActingUser) ->
|
||||
add_user_sans_validation(Username, Password, ActingUser, []).
|
||||
add_user_sans_validation(Username, Password, ActingUser, undefined, []).
|
||||
|
||||
add_user_sans_validation(Tags) ->
|
||||
add_user_sans_validation(Limits, Tags) ->
|
||||
fun(Username, Password, ActingUser) ->
|
||||
add_user_sans_validation(Username, Password, ActingUser, Tags)
|
||||
add_user_sans_validation(Username, Password, ActingUser, Limits, Tags)
|
||||
end.
|
||||
|
||||
add_user_sans_validation(Username, Password, ActingUser, Tags) ->
|
||||
add_user_sans_validation(Username, Password, ActingUser, Limits, Tags) ->
|
||||
rabbit_log:debug("Asked to create a new user '~s', password length in bytes: ~p", [Username, bit_size(Password)]),
|
||||
%% hash_password will pick the hashing function configured for us
|
||||
%% but we also need to store a hint as part of the record, so we
|
||||
|
|
@ -235,21 +238,29 @@ add_user_sans_validation(Username, Password, ActingUser, Tags) ->
|
|||
PasswordHash = hash_password(HashingMod, Password),
|
||||
User0 = internal_user:create_user(Username, PasswordHash, HashingMod),
|
||||
ConvertedTags = [rabbit_data_coercion:to_atom(I) || I <- Tags],
|
||||
User = internal_user:set_tags(User0, ConvertedTags),
|
||||
add_user_sans_validation_in(Username, User, ConvertedTags, ActingUser).
|
||||
User1 = internal_user:set_tags(User0, ConvertedTags),
|
||||
User = case Limits of
|
||||
undefined -> User1;
|
||||
Term -> internal_user:update_limits(add, User1, Term)
|
||||
end,
|
||||
add_user_sans_validation_in(Username, User, ConvertedTags, Limits, ActingUser).
|
||||
|
||||
add_user_sans_validation(Username, PasswordHash, HashingAlgorithm, Tags, ActingUser) ->
|
||||
add_user_sans_validation(Username, PasswordHash, HashingAlgorithm, Tags, Limits, ActingUser) ->
|
||||
rabbit_log:debug("Asked to create a new user '~s' with password hash", [Username]),
|
||||
ConvertedTags = [rabbit_data_coercion:to_atom(I) || I <- Tags],
|
||||
HashingMod = rabbit_password:hashing_mod(),
|
||||
User0 = internal_user:create_user(Username, PasswordHash, HashingMod),
|
||||
User = internal_user:set_tags(
|
||||
internal_user:set_password_hash(User0,
|
||||
PasswordHash, HashingAlgorithm),
|
||||
ConvertedTags),
|
||||
add_user_sans_validation_in(Username, User, ConvertedTags, ActingUser).
|
||||
User1 = internal_user:set_tags(
|
||||
internal_user:set_password_hash(User0,
|
||||
PasswordHash, HashingAlgorithm),
|
||||
ConvertedTags),
|
||||
User = case Limits of
|
||||
undefined -> User1;
|
||||
Term -> internal_user:update_limits(add, User1, Term)
|
||||
end,
|
||||
add_user_sans_validation_in(Username, User, ConvertedTags, Limits, ActingUser).
|
||||
|
||||
add_user_sans_validation_in(Username, User, ConvertedTags, ActingUser) ->
|
||||
add_user_sans_validation_in(Username, User, ConvertedTags, Limits, ActingUser) ->
|
||||
try
|
||||
R = rabbit_misc:execute_mnesia_transaction(
|
||||
fun () ->
|
||||
|
|
@ -267,6 +278,10 @@ add_user_sans_validation_in(Username, User, ConvertedTags, ActingUser) ->
|
|||
[] -> ok;
|
||||
_ -> notify_user_tags_set(Username, ConvertedTags, ActingUser)
|
||||
end,
|
||||
case Limits of
|
||||
undefined -> ok;
|
||||
_ -> notify_limit_set(Username, ActingUser, Limits)
|
||||
end,
|
||||
R
|
||||
catch
|
||||
throw:{error, {user_already_exists, _}} = Error ->
|
||||
|
|
@ -360,11 +375,11 @@ change_password_sans_validation(Username, Password, ActingUser) ->
|
|||
erlang:raise(Class, Error, Stacktrace)
|
||||
end.
|
||||
|
||||
change_password_and_tags(Username, Password, Tags, ActingUser) ->
|
||||
update_user(Username, Password, Tags, Limits, ActingUser) ->
|
||||
validate_and_alternate_credentials(Username, Password, ActingUser,
|
||||
change_password_and_tags_sans_validation(Tags)).
|
||||
update_user_sans_validation(Tags, Limits)).
|
||||
|
||||
change_password_and_tags_sans_validation(Tags) ->
|
||||
update_user_sans_validation(Tags, Limits) ->
|
||||
fun(Username, Password, ActingUser) ->
|
||||
try
|
||||
rabbit_log:debug("Asked to change password of user '~s', new password length in bytes: ~p", [Username, bit_size(Password)]),
|
||||
|
|
@ -373,11 +388,12 @@ change_password_and_tags_sans_validation(Tags) ->
|
|||
rabbit_log:debug("Asked to set user tags for user '~s' to ~p", [Username, Tags]),
|
||||
|
||||
ConvertedTags = [rabbit_data_coercion:to_atom(I) || I <- Tags],
|
||||
R = change_password_hash_and_tags(Username,
|
||||
hash_password(rabbit_password:hashing_mod(),
|
||||
Password),
|
||||
HashingAlgorithm,
|
||||
ConvertedTags),
|
||||
R = update_user_with_hash(Username,
|
||||
hash_password(rabbit_password:hashing_mod(),
|
||||
Password),
|
||||
HashingAlgorithm,
|
||||
ConvertedTags,
|
||||
Limits),
|
||||
rabbit_log:info("Successfully changed password for user '~s'", [Username]),
|
||||
rabbit_event:notify(user_password_changed,
|
||||
[{name, Username},
|
||||
|
|
@ -419,18 +435,22 @@ change_password_hash(Username, PasswordHash) ->
|
|||
|
||||
|
||||
change_password_hash(Username, PasswordHash, HashingAlgorithm) ->
|
||||
update_user(Username, fun(User) ->
|
||||
internal_user:set_password_hash(User,
|
||||
PasswordHash, HashingAlgorithm)
|
||||
end).
|
||||
update_user_with_hash(Username, PasswordHash, HashingAlgorithm, [], undefined).
|
||||
|
||||
change_password_hash_and_tags(Username, PasswordHash, HashingAlgorithm, ConvertedTags) ->
|
||||
update_user(Username, fun(User) ->
|
||||
internal_user:set_tags(
|
||||
internal_user:set_password_hash(User,
|
||||
PasswordHash, HashingAlgorithm),
|
||||
ConvertedTags)
|
||||
end).
|
||||
update_user_with_hash(Username, PasswordHash, HashingAlgorithm, ConvertedTags, Limits) ->
|
||||
update_user(Username,
|
||||
fun(User0) ->
|
||||
User1 = internal_user:set_password_hash(User0,
|
||||
PasswordHash, HashingAlgorithm),
|
||||
User2 = case Limits of
|
||||
undefined -> User1;
|
||||
_ -> internal_user:update_limits(add, User1, Limits)
|
||||
end,
|
||||
case ConvertedTags of
|
||||
[] -> User2;
|
||||
_ -> internal_user:set_tags(User2, ConvertedTags)
|
||||
end
|
||||
end).
|
||||
|
||||
-spec set_tags(rabbit_types:username(), [atom()], rabbit_types:username()) -> 'ok'.
|
||||
|
||||
|
|
@ -732,13 +752,27 @@ put_user(User, Version, ActingUser) ->
|
|||
rabbit_credential_validation:validate(Username, Password) =:= ok
|
||||
end,
|
||||
|
||||
Limits = case rabbit_feature_flags:is_enabled(user_limits) of
|
||||
false ->
|
||||
undefined;
|
||||
true ->
|
||||
case maps:get(limits, User, undefined) of
|
||||
undefined ->
|
||||
undefined;
|
||||
Term ->
|
||||
case validate_user_limits(Term) of
|
||||
ok -> Term;
|
||||
Error -> throw(Error)
|
||||
end
|
||||
end
|
||||
end,
|
||||
case exists(Username) of
|
||||
true ->
|
||||
case {HasPassword, HasPasswordHash} of
|
||||
{true, false} ->
|
||||
update_user_password(PassedCredentialValidation, Username, Password, Tags, ActingUser);
|
||||
update_user_password(PassedCredentialValidation, Username, Password, Tags, Limits, ActingUser);
|
||||
{false, true} ->
|
||||
update_user_password_hash(Username, PasswordHash, Tags, User, Version);
|
||||
update_user_password_hash(Username, PasswordHash, Tags, Limits, User, Version);
|
||||
{true, true} ->
|
||||
throw({error, both_password_and_password_hash_are_provided});
|
||||
%% clear password, update tags if needed
|
||||
|
|
@ -749,54 +783,54 @@ put_user(User, Version, ActingUser) ->
|
|||
false ->
|
||||
case {HasPassword, HasPasswordHash} of
|
||||
{true, false} ->
|
||||
create_user_with_password(PassedCredentialValidation, Username, Password, Tags, Permissions, ActingUser);
|
||||
create_user_with_password(PassedCredentialValidation, Username, Password, Tags, Permissions, Limits, ActingUser);
|
||||
{false, true} ->
|
||||
create_user_with_password_hash(Username, PasswordHash, Tags, User, Version, Permissions, ActingUser);
|
||||
create_user_with_password_hash(Username, PasswordHash, Tags, User, Version, Permissions, Limits, ActingUser);
|
||||
{true, true} ->
|
||||
throw({error, both_password_and_password_hash_are_provided});
|
||||
{false, false} ->
|
||||
%% this user won't be able to sign in using
|
||||
%% a username/password pair but can be used for x509 certificate authentication,
|
||||
%% with authn backends such as HTTP or LDAP and so on.
|
||||
create_user_with_password(PassedCredentialValidation, Username, <<"">>, Tags, Permissions, ActingUser)
|
||||
create_user_with_password(PassedCredentialValidation, Username, <<"">>, Tags, Permissions, Limits, ActingUser)
|
||||
end
|
||||
end.
|
||||
|
||||
update_user_password(_PassedCredentialValidation = true, Username, Password, Tags, ActingUser) ->
|
||||
%% change_password, set_tags
|
||||
rabbit_auth_backend_internal:change_password_and_tags(Username, Password, Tags, ActingUser);
|
||||
update_user_password(_PassedCredentialValidation = false, _Username, _Password, _Tags, _ActingUser) ->
|
||||
update_user_password(_PassedCredentialValidation = true, Username, Password, Tags, Limits, ActingUser) ->
|
||||
%% change_password, set_tags and limits
|
||||
rabbit_auth_backend_internal:update_user(Username, Password, Tags, Limits, ActingUser);
|
||||
update_user_password(_PassedCredentialValidation = false, _Username, _Password, _Tags, _Limits, _ActingUser) ->
|
||||
%% we don't log here because
|
||||
%% rabbit_auth_backend_internal will do it
|
||||
throw({error, credential_validation_failed}).
|
||||
|
||||
update_user_password_hash(Username, PasswordHash, Tags, User, Version) ->
|
||||
update_user_password_hash(Username, PasswordHash, Tags, Limits, User, Version) ->
|
||||
%% when a hash this provided, credential validation
|
||||
%% is not applied
|
||||
HashingAlgorithm = hashing_algorithm(User, Version),
|
||||
|
||||
Hash = rabbit_misc:b64decode_or_throw(PasswordHash),
|
||||
ConvertedTags = [rabbit_data_coercion:to_atom(I) || I <- Tags],
|
||||
rabbit_auth_backend_internal:change_password_hash_and_tags(
|
||||
Username, Hash, HashingAlgorithm, ConvertedTags).
|
||||
rabbit_auth_backend_internal:update_user_with_hash(
|
||||
Username, Hash, HashingAlgorithm, ConvertedTags, Limits).
|
||||
|
||||
create_user_with_password(_PassedCredentialValidation = true, Username, Password, Tags, undefined, ActingUser) ->
|
||||
rabbit_auth_backend_internal:add_user(Username, Password, ActingUser, Tags);
|
||||
create_user_with_password(_PassedCredentialValidation = true, Username, Password, Tags, PreconfiguredPermissions, ActingUser) ->
|
||||
rabbit_auth_backend_internal:add_user(Username, Password, ActingUser, Tags),
|
||||
create_user_with_password(_PassedCredentialValidation = true, Username, Password, Tags, undefined, Limits, ActingUser) ->
|
||||
rabbit_auth_backend_internal:add_user(Username, Password, ActingUser, Limits, Tags);
|
||||
create_user_with_password(_PassedCredentialValidation = true, Username, Password, Tags, PreconfiguredPermissions, Limits, ActingUser) ->
|
||||
rabbit_auth_backend_internal:add_user(Username, Password, ActingUser, Limits, Tags),
|
||||
preconfigure_permissions(Username, PreconfiguredPermissions, ActingUser);
|
||||
create_user_with_password(_PassedCredentialValidation = false, _Username, _Password, _Tags, _, _) ->
|
||||
create_user_with_password(_PassedCredentialValidation = false, _Username, _Password, _Tags, _, _, _) ->
|
||||
%% we don't log here because
|
||||
%% rabbit_auth_backend_internal will do it
|
||||
throw({error, credential_validation_failed}).
|
||||
|
||||
create_user_with_password_hash(Username, PasswordHash, Tags, User, Version, PreconfiguredPermissions, ActingUser) ->
|
||||
create_user_with_password_hash(Username, PasswordHash, Tags, User, Version, PreconfiguredPermissions, Limits, ActingUser) ->
|
||||
%% when a hash this provided, credential validation
|
||||
%% is not applied
|
||||
HashingAlgorithm = hashing_algorithm(User, Version),
|
||||
Hash = rabbit_misc:b64decode_or_throw(PasswordHash),
|
||||
|
||||
rabbit_auth_backend_internal:add_user_sans_validation(Username, Hash, HashingAlgorithm, Tags, ActingUser),
|
||||
rabbit_auth_backend_internal:add_user_sans_validation(Username, Hash, HashingAlgorithm, Tags, Limits, ActingUser),
|
||||
preconfigure_permissions(Username, PreconfiguredPermissions, ActingUser).
|
||||
|
||||
preconfigure_permissions(_Username, undefined, _ActingUser) ->
|
||||
|
|
@ -831,8 +865,7 @@ set_user_limits(Username, Definition, ActingUser) when is_map(Definition) ->
|
|||
end.
|
||||
|
||||
validate_parameters_and_update_limit(Username, Term, ActingUser) ->
|
||||
case flatten_errors(rabbit_parameter_validation:proplist(
|
||||
<<"user-limits">>, user_limit_validation(), Term)) of
|
||||
case validate_user_limits(Term) of
|
||||
ok ->
|
||||
update_user(Username, fun(User) ->
|
||||
internal_user:update_limits(add, User, Term)
|
||||
|
|
@ -842,6 +875,10 @@ validate_parameters_and_update_limit(Username, Term, ActingUser) ->
|
|||
{error_string, rabbit_misc:format(Reason, Arguments)}
|
||||
end.
|
||||
|
||||
validate_user_limits(Term) ->
|
||||
flatten_errors(rabbit_parameter_validation:proplist(
|
||||
<<"user-limits">>, user_limit_validation(), Term)).
|
||||
|
||||
user_limit_validation() ->
|
||||
[{<<"max-connections">>, fun rabbit_parameter_validation:integer/2, optional},
|
||||
{<<"max-channels">>, fun rabbit_parameter_validation:integer/2, optional}].
|
||||
|
|
|
|||
|
|
@ -78,6 +78,8 @@ handle_info(tick, #state{timeout = Timeout} = State) ->
|
|||
%% down `rabbit_sup` and the whole `rabbit` app.
|
||||
[]
|
||||
end,
|
||||
|
||||
|
||||
rabbit_core_metrics:queue_stats(QName, Infos),
|
||||
rabbit_event:notify(queue_stats, Infos ++ [{name, QName},
|
||||
{messages, COffs},
|
||||
|
|
|
|||
|
|
@ -47,7 +47,9 @@ groups() ->
|
|||
import_case14,
|
||||
import_case15,
|
||||
import_case16,
|
||||
import_case17
|
||||
import_case17,
|
||||
import_case18,
|
||||
import_case19
|
||||
]},
|
||||
|
||||
{boot_time_import_using_classic_source, [], [
|
||||
|
|
@ -239,6 +241,34 @@ import_case16(Config) ->
|
|||
|
||||
import_case17(Config) -> import_invalid_file_case(Config, "failing_case17").
|
||||
|
||||
import_case18(Config) ->
|
||||
case rabbit_ct_helpers:is_mixed_versions() of
|
||||
false ->
|
||||
case rabbit_ct_broker_helpers:enable_feature_flag(Config, user_limits) of
|
||||
ok ->
|
||||
import_file_case(Config, "case18"),
|
||||
User = <<"limited_guest">>,
|
||||
UserIsImported =
|
||||
fun () ->
|
||||
case user_lookup(Config, User) of
|
||||
{error, not_found} -> false;
|
||||
_ -> true
|
||||
end
|
||||
end,
|
||||
rabbit_ct_helpers:await_condition(UserIsImported, 20000),
|
||||
{ok, UserRec} = user_lookup(Config, User),
|
||||
?assertEqual(#{<<"max-connections">> => 2}, internal_user:get_limits(UserRec)),
|
||||
ok;
|
||||
Skip ->
|
||||
Skip
|
||||
end;
|
||||
_ ->
|
||||
%% skip the test in mixed version mode
|
||||
{skip, "Should not run in mixed version environments"}
|
||||
end.
|
||||
|
||||
import_case19(Config) -> import_invalid_file_case(Config, "failing_case19").
|
||||
|
||||
export_import_round_trip_case1(Config) ->
|
||||
case rabbit_ct_helpers:is_mixed_versions() of
|
||||
false ->
|
||||
|
|
@ -385,3 +415,6 @@ queue_lookup(Config, VHost, Name) ->
|
|||
|
||||
vhost_lookup(Config, VHost) ->
|
||||
rabbit_ct_broker_helpers:rpc(Config, 0, rabbit_vhost, lookup, [VHost]).
|
||||
|
||||
user_lookup(Config, User) ->
|
||||
rabbit_ct_broker_helpers:rpc(Config, 0, rabbit_auth_backend_internal, lookup_user, [User]).
|
||||
|
|
|
|||
|
|
@ -0,0 +1,46 @@
|
|||
{
|
||||
"bindings": [],
|
||||
"exchanges": [],
|
||||
"global_parameters": [
|
||||
{
|
||||
"name": "cluster_name",
|
||||
"value": "rabbitmq@localhost"
|
||||
}
|
||||
],
|
||||
"parameters": [],
|
||||
"permissions": [
|
||||
{
|
||||
"configure": ".*",
|
||||
"read": ".*",
|
||||
"user": "guest",
|
||||
"vhost": "/",
|
||||
"write": ".*"
|
||||
}
|
||||
],
|
||||
"policies": [],
|
||||
"queues": [],
|
||||
"rabbit_version": "3.9.1",
|
||||
"rabbitmq_version": "3.9.1",
|
||||
"topic_permissions": [],
|
||||
"users": [
|
||||
{
|
||||
"hashing_algorithm": "rabbit_password_hashing_sha256",
|
||||
"limits": {"max-connections" : 2},
|
||||
"name": "limited_guest",
|
||||
"password_hash": "wS4AT3B4Z5RpWlFn1FA30osf2C75D7WA3gem591ACDZ6saO6",
|
||||
"tags": [
|
||||
"administrator"
|
||||
]
|
||||
}
|
||||
],
|
||||
"vhosts": [
|
||||
{
|
||||
"limits": [],
|
||||
"name": "/"
|
||||
},
|
||||
{
|
||||
"limits": [],
|
||||
"name": "tagged"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
{
|
||||
"bindings": [],
|
||||
"exchanges": [],
|
||||
"global_parameters": [
|
||||
{
|
||||
"name": "cluster_name",
|
||||
"value": "rabbitmq@localhost"
|
||||
}
|
||||
],
|
||||
"parameters": [],
|
||||
"permissions": [
|
||||
{
|
||||
"configure": ".*",
|
||||
"read": ".*",
|
||||
"user": "guest",
|
||||
"vhost": "/",
|
||||
"write": ".*"
|
||||
}
|
||||
],
|
||||
"policies": [],
|
||||
"queues": [],
|
||||
"rabbit_version": "3.9.1",
|
||||
"rabbitmq_version": "3.9.1",
|
||||
"topic_permissions": [],
|
||||
"users": [
|
||||
{
|
||||
"hashing_algorithm": "rabbit_password_hashing_sha256",
|
||||
"limits": {"max-connections" : "twomincepies"},
|
||||
"name": "limited_guest",
|
||||
"password_hash": "wS4AT3B4Z5RpWlFn1FA30osf2C75D7WA3gem591ACDZ6saO6",
|
||||
"tags": [
|
||||
"administrator"
|
||||
]
|
||||
}
|
||||
],
|
||||
"vhosts": [
|
||||
{
|
||||
"limits": [],
|
||||
"name": "/"
|
||||
},
|
||||
{
|
||||
"limits": [],
|
||||
"name": "tagged"
|
||||
}
|
||||
]
|
||||
}
|
||||
Loading…
Reference in New Issue