List:Internals« Previous MessageNext Message »
From:Sergei Golubchik Date:August 2 2008 7:33am
Subject:Re: SoC project "Roles": code review 2678
View as plain text  
Hi!

On Aug 02, Sergey Kudriavtsev wrote:
> 2008/7/31 Sergei Golubchik <serg@stripped>:
> 
> >> === modified file 'scripts/mysql_system_tables_fix.sql'
> >> --- scripts/mysql_system_tables_fix.sql       2008-04-29 04:37:22 +0000
> >> +++ scripts/mysql_system_tables_fix.sql       2008-07-06 19:53:28 +0000
> >> @@ -520,6 +520,30 @@ ALTER TABLE tables_priv MODIFY Table_pri
> >> +
> >> +SET @hadCreateRolePriv := 0;
> >> +SELECT @hadCreateRolePriv :=1 FROM user WHERE Create_role_priv LIKE '%';
> >> +ALTER TABLE user ADD Create_role_priv enum('N','Y') COLLATE utf8_general_ci
> DEFAULT 'N' NOT NULL AFTER Trigger_priv;
> >> +ALTER TABLE user MODIFY Create_role_priv enum('N','Y') COLLATE
> utf8_general_ci DEFAULT 'N' NOT NULL AFTER Trigger_priv;
> >> +UPDATE user SET Create_role_priv=Create_user_priv WHERE @hadCreateRolePriv
> = 0;
> >> +SET @hadDropRolePriv := 0;
> >> +SELECT @hadDropRolePriv :=1 FROM user WHERE Drop_role_priv LIKE '%';
> >> +ALTER TABLE user ADD Drop_role_priv enum('N','Y') COLLATE utf8_general_ci
> DEFAULT 'N' NOT NULL AFTER Create_role_priv;
> >> +ALTER TABLE user MODIFY Drop_role_priv enum('N','Y') COLLATE
> utf8_general_ci DEFAULT 'N' NOT NULL AFTER Create_role_priv;
> >> +UPDATE user SET Drop_role_priv=Create_user_priv WHERE @hadDropRolePriv =
> 0;
> >
> > Why do you handle Drop_role_priv separetely instead of adding it in the same
> > ALTER TABLE that adds Create_role_priv ?
> 
> Thought it may help reading and understanding script.

I don't think it does. And MODIFY certainly doesn't - it's completely
pointless and thus only confusing.

Write as
 
 SET @hadCreateRolePriv := 0;
 SELECT @hadCreateRolePriv :=1 FROM user WHERE Create_role_priv LIKE '%';
 ALTER TABLE user ADD Create_role_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N'
NOT NULL AFTER Trigger_priv,
                  ADD Drop_role_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT
NULL AFTER Create_role_priv;
 UPDATE user SET Create_role_priv=Create_user_priv,
                 Drop_role_priv=Create_user_priv WHERE @hadCreateRolePriv = 0;

> >> === modified file 'sql/sql_class.h'
> >> --- sql/sql_class.h   2008-06-28 11:00:59 +0000
> >> +++ sql/sql_class.h   2008-07-21 21:13:34 +0000
> >> @@ -828,14 +828,17 @@ public:
> >>      the connection
> >>      priv_user - The user privilege we are using. May be "" for anonymous
> user.
> >>      ip - client IP
> >> +    role - current user role
> >>    */
> >> -  char   *host, *user, *priv_user, *ip;
> >> +  char   *host, *user, *priv_user, *ip, *role, *priv_role;
> >>    /* The host privilege we are using */
> >>    char   priv_host[MAX_HOSTNAME];
> >>    /* points to host if host is available, otherwise points to ip */
> >>    const char *host_or_ip;
> >> -  ulong master_access;                 /* Global privileges from mysql.user
> */
> >> -  ulong db_access;                     /* Privileges for current db */
> >> +  ulonglong master_access;             /* Global mixed privileges for
> current user an role from mysql.user */
> >
> > not "mixed" but "combined" or "merged"
> 
> Accepted.
> 
> >> +  ulonglong db_access;                 /* Privileges for current db */
> >
> > also combined ?
> 
> Yes, of course.

I mean, the comment should mention it
 
> >> === modified file 'sql/sql_parse.cc'
> >> --- sql/sql_parse.cc  2008-06-28 11:00:59 +0000
> >> +++ sql/sql_parse.cc  2008-07-21 21:13:34 +0000
> >> @@ -5057,11 +5093,11 @@ check_access(THD *thd, ulong want_access
> >>        The effective privileges are the union of the global privileges
> >>        and the the intersection of db- and host-privileges.
> >>      */
> >> -    *save_priv=sctx->master_access | db_access;
> >> +    *save_priv= sctx->master_access | db_access;
> >>      DBUG_RETURN(FALSE);
> >>    }
> >> -  if (((want_access & ~sctx->master_access) & ~DB_ACLS) ||
> >> -      ! db && dont_check_global_grants)
> >> +  if (((want_access & ~sctx->master_access) & ~(DB_ACLS |
> EXTRA_ACL)) ||
> >> +      db && dont_check_global_grants)
> >
> > Why ?
> 
> Actually, I don't remember this at all O_O. What original version did
> you use for review?

It's in my copy of your branch.
revid:dotsent@dot-sent-20080725202734-y3im7gyxcdowqab6
 
> >>    {           // We can never grant this
> >>      DBUG_PRINT("error",("No possible access"));
> >>      if (!no_errors)
> >> === modified file 'sql/sql_acl.h'
> >> --- sql/sql_acl.h     2008-03-28 13:33:31 +0000
> >> +++ sql/sql_acl.h     2008-07-21 21:13:34 +0000
> >> @@ -188,13 +191,25 @@ public:
> >>  };
> >>
> >>
> >> +class ACL_ROLE:public ACL_ACCESS
> >> +{
> >> +  public:
> >> +    char* role;
> >> +    /* Shall be used in implementation of role-to-role assignment */
> >
> > you mean it's not used yet ? or just forgot to remove the comment ? :)
> 
> Not used _yet_. As strict timeline prevents me from implementing
> role-to-role assignment in the scope of GSoC project, I think I'll
> continue working on it and implement this.

Ah, right. Sorry. I saw that you use assigned_roles[], that's why I
wrote "forgot to remove the comment?". But now I realize that what I saw
was ACL_USER.assigned_roles[], not ACL_ROLE.assigned_roles[].
 
> >> +    DYNAMIC_ARRAY assigned_roles;
> >> +};
> >> +
> >> +
> >>  class ACL_USER :public ACL_ACCESS
> >>  {
> >>  public:
> >>    acl_host_and_ip host;
> >>    uint hostname_length;
> >>    USER_RESOURCES user_resource;
> >> -  char *user;
> >> +  char *user, *default_role_name;
> >> +  ACL_ROLE *default_role, *current_role;
> >
> > why do you need default_role_name ? Isn't it the same as
> > default_role->role ?
> 
> Data from mysql.users table is loaded before data from mysql.roles table and
> assigning of assigned_roles array, default_role and current_role members is done
> after entire mysql.users table has already been loaded in acl_users array. So we
> need a string variable to store default role name for each user in acl_users.

Yeah, I saw that. I'd prefer a cleaner solution, but I'm ok with yours.

Don't forget to set default_role_name=0 when you assign default_role.
default_role_name is allocated in THD's memroot (which is freed at the
end of the statement) so you'll have a dangling pointer.
 
> >> +  DYNAMIC_ARRAY assigned_roles;
> >> +//  bool is_role;
> >>    uint8 salt[SCRAMBLE_LENGTH+1];       // scrambled password in binary
> form
> >>    uint8 salt_len;        // 0 - no password, 4 - 3.20, 8 - 3.23, 20 -
> 4.1.1
> >>    enum SSL_type ssl_type;
> >> === modified file 'sql/sql_acl.cc'
> >> --- sql/sql_acl.cc    2008-06-28 11:00:59 +0000
> >> +++ sql/sql_acl.cc    2008-07-25 20:27:34 +0000
> >> @@ -619,6 +716,77 @@ static my_bool acl_load(THD *thd, TABLE_
> >> +          if (!strcmp(user, acl_user_tmp->user))
> >> +          {
> >> +            if (host && &acl_user_tmp->host)
> >
> > what does that mean ?
> 
> null-check. Had server crash there sometimes.

Yes, host could be NULL. But &acl_user_tmp->host cannot.
 
> >> @@ -1363,10 +1545,18 @@ ulong acl_get(const char *host, const ch
> >> +    else if (!acl_db->user)
> >> +    {
> >> +      for (uint j=0 ; j < acl_users.elements ; j++)
> >> +      {
> >> +        ACL_USER *acl_user= dynamic_element(&acl_users,j,ACL_USER*);
> >> +        if (strcmp(acl_user->current_role->role, (char*) "NONE")
> && !strcmp(acl_user->current_role->role, acl_db->user))
> >> +        {
> >> +          db_access|= acl_db->access;
> >>          }
> >
> > I don't understand this loop at all.
> 
> sctx->db_access must contain composition of user's and current_role's
> privileges on current table, so we have to get both privileges. First
> is assigned by exisitng code, this loop does the second part (at
> least, it was supposed to).

I don't think it should work that way. Instead, you have current role
(in the active Security_context), get its privileges for the db and
merge. E.g. restore acl_get to its original state, and

  acl_get_with_role(const char *host, const char *ip,
                    const char *user,const char *role,
                    const char *db, my_bool db_is_pattern)
  {
    return acl_get(host,ip,user,db,db_is_pattern) |
           acl_get(0,0,role,db,db_is_pattern);
  }

although you typically will need to set both Security_context::db_access
and true_user_db_access, so you do it as

  sctx->true_user_db_access= acl_get(host,ip,user,db,db_is_pattern);
  sctx->db_access= sctx->true_user_db_access |
                   acl_get(0,0,rule,db,db_is_pattern);

and here you don't need acl_get_with_role() at all.

> >>        }
> >>      }
> >> @@ -3477,6 +3669,146 @@ bool mysql_grant(THD *thd, const char *d
> >>  }
> >>
> >>
> >> +bool mysql_grant_role(THD *thd, List <LEX_USER> &list,
> >> +                 bool revoke_grant)
> >> +{
> >> +  List_iterator <LEX_USER> str_list (list);
> >> +  /* Skipping and saving first record in list as it contains desired
> role*/
> >> +  LEX_USER *Role= str_list++;
> >> +  LEX_USER *Str, *tmp_Str;
> >
> > as you have noticed, we use lowercase in variable names
> >
> 
> OK, will change that. Anyway, mysql_grant looks the same. Should I
> correct it, too?

feel free to
 
> >> +  TABLE_LIST tables[2];
> >> +  TABLE *table;
> >> +  DBUG_ENTER("mysql_grant_role");
> >> +  if (!initialized)
> >> +  {
> >> +    my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0),
> >> +             "--skip-grant-tables");
> >> +    DBUG_RETURN(TRUE);
> >> +  }
............
> >> +          for (uint j=0 ; j < acl_user_tmp->assigned_roles.elements ;
> j++)
> >> +          {
> >> +            ACL_ROLE *acl_role_tmp=
> dynamic_element(&acl_user_tmp->assigned_roles, j, ACL_ROLE*);
> >> +            if (!strcmp(acl_role_tmp->role,Role->user.str))
> >> +              result=-1;
> >
> > where's "break;" ?
> > alternatively, you may skip the check for duplicate grants above,
> > and let ha_write_row() below fail with duplicate key.
> >
> > on the other hand, you *must* verify that the role exists before
> > writing its name to the table.
> 
> OK, will add a break. About verification of existence: what about a
> feature like auto-creating
> user in case of GRANTing privileges to him?

Right, a user can be auto-created. But not a role when you grant it. I
mean in 

       GRANT roleA TO userB;

userB can be created if it does not exists. But if roleA doesn't exist
it should not be created, it's an error.

Regards / Mit vielen Grüssen,
Sergei

-- 
   __  ___     ___ ____  __
  /  |/  /_ __/ __/ __ \/ /   Sergei Golubchik <serg@stripped>
 / /|_/ / // /\ \/ /_/ / /__  Principal Software Engineer/Server Architect
/_/  /_/\_, /___/\___\_\___/  Sun Microsystems GmbH, HRB München 161028
       <___/                  Sonnenallee 1, 85551 Kirchheim-Heimstetten
Geschäftsführer: Thomas Schroeder, Wolfgang Engels, Dr. Roland Boemer
Vorsitzender des Aufsichtsrates: Martin Häring
Thread
SoC project "Roles": code review 2678Sergei Golubchik31 Jul
  • Re: SoC project "Roles": code review 2678Sergey Kudriavtsev1 Aug
    • Re: SoC project "Roles": code review 2678Sergei Golubchik2 Aug