Commit 957cb7b7 authored by Anel Husakovic's avatar Anel Husakovic

MDEV-22312: Bad error message for SET DEFAULT ROLE when user account is not granted the role

- `SET DEFAULT ROLE xxx [FOR yyy]` should say:
  "User yyy has not been granted a role xxx" if:
    - The current user (not the user `yyy` in the FOR clause) can see the
    role xxx. It can see the role if:
      * role exists in `mysql.roles_mappings` (traverse the graph),
      * If the current user has read access on `mysql.user` table - in
    that case, it can see all roles, granted or not.
    - Otherwise it should be "Invalid role specification".

In other words, it should not be possible to use `SET DEFAULT ROLE` to discover whether a specific role exist or not.
parent dbe447a7
...@@ -14,7 +14,7 @@ set default role role_a for user_a@localhost; ...@@ -14,7 +14,7 @@ set default role role_a for user_a@localhost;
set default role invalid_role for user_a@localhost; set default role invalid_role for user_a@localhost;
ERROR OP000: Invalid role specification `invalid_role`. ERROR OP000: Invalid role specification `invalid_role`.
set default role role_b for user_a@localhost; set default role role_b for user_a@localhost;
ERROR OP000: Invalid role specification `role_b`. ERROR OP000: User `user_a@localhost` has not been granted role `role_b`
set default role role_b for user_b@localhost; set default role role_b for user_b@localhost;
show grants; show grants;
Grants for user_a@localhost Grants for user_a@localhost
...@@ -36,7 +36,7 @@ user host default_role ...@@ -36,7 +36,7 @@ user host default_role
user_a localhost role_a user_a localhost role_a
user_b localhost role_b user_b localhost role_b
set default role role_b for current_user; set default role role_b for current_user;
ERROR OP000: Invalid role specification `role_b`. ERROR OP000: User `user_a@localhost` has not been granted role `role_b`
show grants; show grants;
Grants for user_b@localhost Grants for user_b@localhost
GRANT role_b TO 'user_b'@'localhost' GRANT role_b TO 'user_b'@'localhost'
......
...@@ -38,3 +38,90 @@ ERROR 42000: SELECT command denied to user 'test_user'@'localhost' for table 'us ...@@ -38,3 +38,90 @@ ERROR 42000: SELECT command denied to user 'test_user'@'localhost' for table 'us
drop role test_role; drop role test_role;
drop role not_granted_role; drop role not_granted_role;
drop user test_user@localhost; drop user test_user@localhost;
#
# MDEV-22312: Bad error message for SET DEFAULT ROLE when user account
# is not granted the role
#
CREATE USER a;
CREATE USER b;
CREATE ROLE r1;
CREATE ROLE r2;
SET DEFAULT ROLE r1 FOR a;
ERROR OP000: User `a@%` has not been granted role `r1`
GRANT r1 TO b;
GRANT r2 TO b;
SET DEFAULT ROLE r1 FOR b;
# Change user b
SELECT CURRENT_ROLE;
CURRENT_ROLE
r1
SET ROLE r2;
SELECT CURRENT_ROLE;
CURRENT_ROLE
r2
SET DEFAULT ROLE r1 FOR a;
ERROR 42000: Access denied for user 'b'@'%' to database 'mysql'
SET DEFAULT ROLE r2;
# Change user root (session 1: select_priv to b)
GRANT SELECT ON mysql.* TO b;
# Change user b (session 1: select_priv)
SHOW GRANTS FOR b;
Grants for b@%
GRANT r1 TO 'b'@'%'
GRANT r2 TO 'b'@'%'
GRANT USAGE ON *.* TO 'b'@'%'
GRANT SELECT ON `mysql`.* TO 'b'@'%'
SET DEFAULT ROLE r1 FOR a;
ERROR 42000: Access denied for user 'b'@'%' to database 'mysql'
SELECT CURRENT_ROLE;
CURRENT_ROLE
r2
SET DEFAULT ROLE NONE;
SELECT CURRENT_ROLE;
CURRENT_ROLE
r2
SET DEFAULT ROLE current_role FOR current_user;
SET DEFAULT ROLE invalid_role;
ERROR OP000: Invalid role specification `invalid_role`.
SET DEFAULT ROLE invalid_role FOR a;
ERROR 42000: Access denied for user 'b'@'%' to database 'mysql'
SET DEFAULT ROLE none FOR a;
ERROR 42000: Access denied for user 'b'@'%' to database 'mysql'
# Change user root (session 2: adding update_priv to user b)
GRANT UPDATE ON mysql.* TO b;
# Change user b
SHOW GRANTS FOR b;
Grants for b@%
GRANT r1 TO 'b'@'%'
GRANT r2 TO 'b'@'%'
GRANT USAGE ON *.* TO 'b'@'%'
GRANT SELECT, UPDATE ON `mysql`.* TO 'b'@'%'
SET DEFAULT ROLE r1 FOR a;
ERROR OP000: User `a@%` has not been granted role `r1`
SET DEFAULT ROLE invalid_role;
ERROR OP000: Invalid role specification `invalid_role`.
SET DEFAULT ROLE invalid_role FOR a;
ERROR OP000: Invalid role specification `invalid_role`.
SET DEFAULT ROLE none FOR a;
# Change user root (session 3: Grant role to user a)
GRANT r1 TO a;
SET DEFAULT ROLE r1 FOR a;
# Change user a (verify session 3)
SELECT CURRENT_ROLE;
CURRENT_ROLE
r1
SET DEFAULT ROLE None;
# Change user b (session 3: role granted to user a)
SET DEFAULT ROLE r1 FOR a;
SET DEFAULT ROLE r2 FOR a;
ERROR OP000: User `a@%` has not been granted role `r2`
SET DEFAULT ROLE invalid_role;
ERROR OP000: Invalid role specification `invalid_role`.
SET DEFAULT ROLE invalid_role FOR a;
ERROR OP000: Invalid role specification `invalid_role`.
SELECT user, host, default_role FROM mysql.user where user='a' or user='b';
user host default_role
a % r1
b % r2
DROP ROLE r1, r2;
DROP USER a, b;
...@@ -60,3 +60,110 @@ change_user 'root'; ...@@ -60,3 +60,110 @@ change_user 'root';
drop role test_role; drop role test_role;
drop role not_granted_role; drop role not_granted_role;
drop user test_user@localhost; drop user test_user@localhost;
--echo #
--echo # MDEV-22312: Bad error message for SET DEFAULT ROLE when user account
--echo # is not granted the role
--echo #
CREATE USER a;
CREATE USER b;
CREATE ROLE r1;
CREATE ROLE r2;
# Role has not been granted to user a, but the role is visible to current_user
--error ER_INVALID_ROLE
SET DEFAULT ROLE r1 FOR a;
# Granting roles to user b
GRANT r1 TO b;
GRANT r2 TO b;
# After granting the role, role can be set as default
SET DEFAULT ROLE r1 FOR b;
--echo # Change user b
change_user b;
SELECT CURRENT_ROLE;
SET ROLE r2;
SELECT CURRENT_ROLE;
# User b has no UPDATE_PRIV for mysql.user
--error ER_DBACCESS_DENIED_ERROR
SET DEFAULT ROLE r1 FOR a;
SET DEFAULT ROLE r2;
--echo # Change user root (session 1: select_priv to b)
change_user root;
# Let's grant select_priv to user b
GRANT SELECT ON mysql.* TO b;
--echo # Change user b (session 1: select_priv)
change_user b;
SHOW GRANTS FOR b;
# User must have update_priv before setting the role
--error ER_DBACCESS_DENIED_ERROR
SET DEFAULT ROLE r1 FOR a;
# Testing the `CURRENT_ROLE` as a special case
SELECT CURRENT_ROLE;
SET DEFAULT ROLE NONE;
SELECT CURRENT_ROLE;
SET DEFAULT ROLE current_role FOR current_user;
# Testing of non-existing role
--error ER_INVALID_ROLE
SET DEFAULT ROLE invalid_role;
# Testing of non-existing role for different user
--error ER_DBACCESS_DENIED_ERROR
SET DEFAULT ROLE invalid_role FOR a;
# Testing the `None` role for different user
-- error ER_DBACCESS_DENIED_ERROR
SET DEFAULT ROLE none FOR a;
--echo # Change user root (session 2: adding update_priv to user b)
change_user root;
# update_priv are enough
GRANT UPDATE ON mysql.* TO b;
--echo # Change user b
change_user b;
SHOW GRANTS FOR b;
# In all tests in session user a has not been granted the role
# Testing setting role for different user, should fail with new error
--error ER_INVALID_ROLE
SET DEFAULT ROLE r1 FOR a;
# Testing of non-existing role
--error ER_INVALID_ROLE
SET DEFAULT ROLE invalid_role;
# Testing of non-existing role for different user with update_priv
--error ER_INVALID_ROLE
SET DEFAULT ROLE invalid_role FOR a;
# Testing the `None` role for different user with update_priv
SET DEFAULT ROLE none FOR a;
--echo # Change user root (session 3: Grant role to user a)
change_user root;
# After granting the privilege for a, user b can set default role
GRANT r1 TO a;
SET DEFAULT ROLE r1 FOR a;
--echo # Change user a (verify session 3)
change_user a;
SELECT CURRENT_ROLE;
SET DEFAULT ROLE None;
--echo # Change user b (session 3: role granted to user a)
change_user b;
# This should set role because b has update_priv
SET DEFAULT ROLE r1 FOR a;
# Testing non-granted role r2 still should fail
-- error ER_INVALID_ROLE
SET DEFAULT ROLE r2 FOR a;
# Testing of non-existing role
--error ER_INVALID_ROLE
SET DEFAULT ROLE invalid_role;
# Testing of non-existing role for different user
--error ER_INVALID_ROLE
SET DEFAULT ROLE invalid_role FOR a;
# Clear the workspace
change_user root;
--sorted_result
SELECT user, host, default_role FROM mysql.user where user='a' or user='b';
DROP ROLE r1, r2;
DROP USER a, b;
...@@ -66,7 +66,7 @@ Grants for test_user@localhost ...@@ -66,7 +66,7 @@ Grants for test_user@localhost
GRANT USAGE ON *.* TO 'test_user'@'localhost' GRANT USAGE ON *.* TO 'test_user'@'localhost'
GRANT test_role1 TO 'test_user'@'localhost' GRANT test_role1 TO 'test_user'@'localhost'
set role test_role2; set role test_role2;
ERROR OP000: Invalid role specification `test_role2`. ERROR OP000: User `test_user@localhost` has not been granted role `test_role2`
select current_user(), current_role(); select current_user(), current_role();
current_user() current_role() current_user() current_role()
test_user@localhost NULL test_user@localhost NULL
......
...@@ -923,8 +923,17 @@ int set_var_default_role::check(THD *thd) ...@@ -923,8 +923,17 @@ int set_var_default_role::check(THD *thd)
{ {
#ifndef NO_EMBEDDED_ACCESS_CHECKS #ifndef NO_EMBEDDED_ACCESS_CHECKS
real_user= get_current_user(thd, user); real_user= get_current_user(thd, user);
int status= acl_check_set_default_role(thd, real_user->host.str, real_user->user.str); real_role= role.str;
return status; if (role.str == current_role.str)
{
if (!thd->security_ctx->priv_role[0])
real_role= "NONE";
else
real_role= thd->security_ctx->priv_role;
}
return acl_check_set_default_role(thd, real_user->host.str,
real_user->user.str, real_role);
#else #else
return 0; return 0;
#endif #endif
...@@ -935,7 +944,8 @@ int set_var_default_role::update(THD *thd) ...@@ -935,7 +944,8 @@ int set_var_default_role::update(THD *thd)
#ifndef NO_EMBEDDED_ACCESS_CHECKS #ifndef NO_EMBEDDED_ACCESS_CHECKS
Reprepare_observer *save_reprepare_observer= thd->m_reprepare_observer; Reprepare_observer *save_reprepare_observer= thd->m_reprepare_observer;
thd->m_reprepare_observer= 0; thd->m_reprepare_observer= 0;
int res= acl_set_default_role(thd, real_user->host.str, real_user->user.str, role.str); int res= acl_set_default_role(thd, real_user->host.str, real_user->user.str,
real_role);
thd->m_reprepare_observer= save_reprepare_observer; thd->m_reprepare_observer= save_reprepare_observer;
return res; return res;
#else #else
......
...@@ -339,6 +339,7 @@ class set_var_default_role: public set_var_base ...@@ -339,6 +339,7 @@ class set_var_default_role: public set_var_base
{ {
LEX_USER *user, *real_user; LEX_USER *user, *real_user;
LEX_STRING role; LEX_STRING role;
const char *real_role;
public: public:
set_var_default_role(LEX_USER *user_arg, LEX_STRING role_arg) : set_var_default_role(LEX_USER *user_arg, LEX_STRING role_arg) :
user(user_arg), role(role_arg) {} user(user_arg), role(role_arg) {}
......
...@@ -201,6 +201,8 @@ LEX_STRING current_user= { C_STRING_WITH_LEN("*current_user") }; ...@@ -201,6 +201,8 @@ LEX_STRING current_user= { C_STRING_WITH_LEN("*current_user") };
LEX_STRING current_role= { C_STRING_WITH_LEN("*current_role") }; LEX_STRING current_role= { C_STRING_WITH_LEN("*current_role") };
LEX_STRING current_user_and_current_role= { C_STRING_WITH_LEN("*current_user_and_current_role") }; LEX_STRING current_user_and_current_role= { C_STRING_WITH_LEN("*current_user_and_current_role") };
class ACL_USER;
static ACL_USER *find_user_or_anon(const char *host, const char *user, const char *ip);
#ifndef NO_EMBEDDED_ACCESS_CHECKS #ifndef NO_EMBEDDED_ACCESS_CHECKS
static plugin_ref old_password_plugin; static plugin_ref old_password_plugin;
...@@ -2034,8 +2036,21 @@ bool acl_getroot(Security_context *sctx, char *user, char *host, ...@@ -2034,8 +2036,21 @@ bool acl_getroot(Security_context *sctx, char *user, char *host,
DBUG_RETURN(res); DBUG_RETURN(res);
} }
static int check_user_can_set_role(const char *user, const char *host, static int check_role_is_granted_callback(ACL_USER_BASE *grantee, void *data)
const char *ip, const char *rolename, ulonglong *access) {
LEX_CSTRING *rolename= static_cast<LEX_CSTRING *>(data);
if (rolename->length == grantee->user.length &&
!strcmp(rolename->str, grantee->user.str))
return -1; // End search, we've found our role.
/* Keep looking, we haven't found our role yet. */
return 0;
}
static int check_user_can_set_role(THD *thd, const char *user, const char *host,
const char *ip, const char *rolename,
ulonglong *access)
{ {
ACL_ROLE *role; ACL_ROLE *role;
ACL_USER_BASE *acl_user_base; ACL_USER_BASE *acl_user_base;
...@@ -2053,8 +2068,7 @@ static int check_user_can_set_role(const char *user, const char *host, ...@@ -2053,8 +2068,7 @@ static int check_user_can_set_role(const char *user, const char *host,
acl_user= find_user_wild(host, user, ip); acl_user= find_user_wild(host, user, ip);
if (acl_user == NULL) if (acl_user == NULL)
{ {
my_error(ER_INVALID_CURRENT_USER, MYF(0), rolename); result= ER_INVALID_CURRENT_USER;
result= -1;
} }
else if (access) else if (access)
*access= acl_user->access; *access= acl_user->access;
...@@ -2065,9 +2079,9 @@ static int check_user_can_set_role(const char *user, const char *host, ...@@ -2065,9 +2079,9 @@ static int check_user_can_set_role(const char *user, const char *host,
role= find_acl_role(rolename); role= find_acl_role(rolename);
/* According to SQL standard, the same error message must be presented */ /* According to SQL standard, the same error message must be presented */
if (role == NULL) { if (role == NULL)
my_error(ER_INVALID_ROLE, MYF(0), rolename); {
result= -1; result= ER_INVALID_ROLE;
goto end; goto end;
} }
...@@ -2088,7 +2102,6 @@ static int check_user_can_set_role(const char *user, const char *host, ...@@ -2088,7 +2102,6 @@ static int check_user_can_set_role(const char *user, const char *host,
/* According to SQL standard, the same error message must be presented */ /* According to SQL standard, the same error message must be presented */
if (!is_granted) if (!is_granted)
{ {
my_error(ER_INVALID_ROLE, MYF(0), rolename);
result= 1; result= 1;
goto end; goto end;
} }
...@@ -2097,17 +2110,66 @@ static int check_user_can_set_role(const char *user, const char *host, ...@@ -2097,17 +2110,66 @@ static int check_user_can_set_role(const char *user, const char *host,
{ {
*access = acl_user->access | role->access; *access = acl_user->access | role->access;
} }
end: end:
mysql_mutex_unlock(&acl_cache->lock); mysql_mutex_unlock(&acl_cache->lock);
return result;
/* We present different error messages depending if the user has sufficient
privileges to know if the INVALID_ROLE exists. */
switch (result)
{
case ER_INVALID_CURRENT_USER:
my_error(ER_INVALID_CURRENT_USER, MYF(0), rolename);
break;
case ER_INVALID_ROLE:
/* Role doesn't exist at all */
my_error(ER_INVALID_ROLE, MYF(0), rolename);
break;
case 1:
StringBuffer<1024> c_usr;
LEX_CSTRING role_lex;
/* First, check if current user can see mysql database. */
bool read_access= !check_access(thd, SELECT_ACL, "mysql", NULL, NULL, 1, 1);
role_lex.str= rolename;
role_lex.length= strlen(rolename);
mysql_mutex_lock(&acl_cache->lock);
ACL_USER *cur_user= find_user_or_anon(thd->security_ctx->priv_host,
thd->security_ctx->priv_user,
thd->security_ctx->ip);
/* If the current user does not have select priv to mysql database,
see if the current user can discover the role if it was granted to him.
*/
if (cur_user && (read_access ||
traverse_role_graph_down(cur_user, &role_lex,
check_role_is_granted_callback,
NULL) == -1))
{
/* Role is not granted but current user can see the role */
c_usr.append(user, strlen(user));
c_usr.append('@');
c_usr.append(host, strlen(host));
my_printf_error(ER_INVALID_ROLE, "User %`s has not been granted role %`s",
MYF(0), c_usr.c_ptr(), rolename);
}
else
{
/* Role is not granted and current user cannot see the role */
my_error(ER_INVALID_ROLE, MYF(0), rolename);
}
mysql_mutex_unlock(&acl_cache->lock);
break;
}
return result;
} }
int acl_check_setrole(THD *thd, char *rolename, ulonglong *access) int acl_check_setrole(THD *thd, char *rolename, ulonglong *access)
{ {
/* Yes! priv_user@host. Don't ask why - that's what check_access() does. */ return check_user_can_set_role(thd, thd->security_ctx->priv_user,
return check_user_can_set_role(thd->security_ctx->priv_user, thd->security_ctx->host, thd->security_ctx->ip, rolename, access);
thd->security_ctx->host, thd->security_ctx->ip, rolename, access);
} }
...@@ -2886,9 +2948,12 @@ bool change_password(THD *thd, LEX_USER *user) ...@@ -2886,9 +2948,12 @@ bool change_password(THD *thd, LEX_USER *user)
DBUG_RETURN(result); DBUG_RETURN(result);
} }
int acl_check_set_default_role(THD *thd, const char *host, const char *user) int acl_check_set_default_role(THD *thd, const char *host, const char *user,
const char *role)
{ {
return check_alter_user(thd, host, user); DBUG_ENTER("acl_check_set_default_role");
DBUG_RETURN(check_alter_user(thd, host, user) ||
check_user_can_set_role(thd, user, host, NULL, role, NULL));
} }
int acl_set_default_role(THD *thd, const char *host, const char *user, int acl_set_default_role(THD *thd, const char *host, const char *user,
...@@ -2910,16 +2975,6 @@ int acl_set_default_role(THD *thd, const char *host, const char *user, ...@@ -2910,16 +2975,6 @@ int acl_set_default_role(THD *thd, const char *host, const char *user,
DBUG_PRINT("enter",("host: '%s' user: '%s' rolename: '%s'", DBUG_PRINT("enter",("host: '%s' user: '%s' rolename: '%s'",
safe_str(user), safe_str(host), safe_str(rolename))); safe_str(user), safe_str(host), safe_str(rolename)));
if (rolename == current_role.str) {
if (!thd->security_ctx->priv_role[0])
rolename= "NONE";
else
rolename= thd->security_ctx->priv_role;
}
if (check_user_can_set_role(user, host, host, rolename, NULL))
DBUG_RETURN(result);
if (!strcasecmp(rolename, "NONE")) if (!strcasecmp(rolename, "NONE"))
clear_role= TRUE; clear_role= TRUE;
...@@ -3370,7 +3425,7 @@ static bool test_if_create_new_users(THD *thd) ...@@ -3370,7 +3425,7 @@ static bool test_if_create_new_users(THD *thd)
if (!(db_access & INSERT_ACL)) if (!(db_access & INSERT_ACL))
{ {
if (check_grant(thd, INSERT_ACL, &tl, FALSE, UINT_MAX, TRUE)) if (check_grant(thd, INSERT_ACL, &tl, FALSE, UINT_MAX, TRUE))
create_new_users=0; create_new_users=0;
} }
} }
return create_new_users; return create_new_users;
...@@ -8508,17 +8563,6 @@ void get_mqh(const char *user, const char *host, USER_CONN *uc) ...@@ -8508,17 +8563,6 @@ void get_mqh(const char *user, const char *host, USER_CONN *uc)
mysql_mutex_unlock(&acl_cache->lock); mysql_mutex_unlock(&acl_cache->lock);
} }
static int check_role_is_granted_callback(ACL_USER_BASE *grantee, void *data)
{
LEX_CSTRING *rolename= static_cast<LEX_CSTRING *>(data);
if (rolename->length == grantee->user.length &&
!strcmp(rolename->str, grantee->user.str))
return -1; // End search, we've found our role.
/* Keep looking, we haven't found our role yet. */
return 0;
}
/* /*
Initialize a TABLE_LIST array and open grant tables Initialize a TABLE_LIST array and open grant tables
...@@ -10329,7 +10373,7 @@ acl_check_proxy_grant_access(THD *thd, const char *host, const char *user, ...@@ -10329,7 +10373,7 @@ acl_check_proxy_grant_access(THD *thd, const char *host, const char *user,
Security context in THD contains two pairs of (user,host): Security context in THD contains two pairs of (user,host):
1. (user,host) pair referring to inbound connection. 1. (user,host) pair referring to inbound connection.
2. (priv_user,priv_host) pair obtained from mysql.user table after doing 2. (priv_user,priv_host) pair obtained from mysql.user table after doing
authnetication of incoming connection. authentication of incoming connection.
Privileges should be checked wrt (priv_user, priv_host) tuple, because Privileges should be checked wrt (priv_user, priv_host) tuple, because
(user,host) pair obtained from inbound connection may have different (user,host) pair obtained from inbound connection may have different
values than what is actually stored in mysql.user table and while granting values than what is actually stored in mysql.user table and while granting
...@@ -10746,7 +10790,7 @@ int fill_schema_user_privileges(THD *thd, TABLE_LIST *tables, COND *cond) ...@@ -10746,7 +10790,7 @@ int fill_schema_user_privileges(THD *thd, TABLE_LIST *tables, COND *cond)
ulong j,test_access= want_access & ~GRANT_ACL; ulong j,test_access= want_access & ~GRANT_ACL;
for (priv_id=0, j = SELECT_ACL;j <= GLOBAL_ACLS; priv_id++,j <<= 1) for (priv_id=0, j = SELECT_ACL;j <= GLOBAL_ACLS; priv_id++,j <<= 1)
{ {
if (test_access & j) if (test_access & j)
{ {
if (update_schema_privilege(thd, table, buff, 0, 0, 0, 0, if (update_schema_privilege(thd, table, buff, 0, 0, 0, 0,
command_array[priv_id], command_array[priv_id],
......
...@@ -402,7 +402,7 @@ bool acl_check_proxy_grant_access (THD *thd, const char *host, const char *user, ...@@ -402,7 +402,7 @@ bool acl_check_proxy_grant_access (THD *thd, const char *host, const char *user,
bool with_grant); bool with_grant);
int acl_setrole(THD *thd, char *rolename, ulonglong access); int acl_setrole(THD *thd, char *rolename, ulonglong access);
int acl_check_setrole(THD *thd, char *rolename, ulonglong *access); int acl_check_setrole(THD *thd, char *rolename, ulonglong *access);
int acl_check_set_default_role(THD *thd, const char *host, const char *user); int acl_check_set_default_role(THD *thd, const char *host, const char *user, const char *role);
int acl_set_default_role(THD *thd, const char *host, const char *user, int acl_set_default_role(THD *thd, const char *host, const char *user,
const char *rolename); const char *rolename);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment