A role is a bundle of GRANTed privileges which can be assigned to users or which can take the place of users. When there are hundreds or thousands of users, administration becomes a horrible burden if the DBMS doesn't support roles. Our online ANSI-standard book has syntax descriptions. Other DBMSs do support roles, and MySQL has had a worklog task "WL#988 Roles" for many years. Earlier attempts to implement them included a Google Summer of Code project and a MySQL tool released by Google.
Now another google-summer-coder, Vicențiu Ciorbaru, has put together something which will be in MariaDB. MariaDB's official blog says this is "a big thing". I'll compare the MySQL specification to the MariaDB feature.
Unfortunately Oracle made WL#988 Roles a hidden task about a year and a half ago -- you can't find it by looking on dev.mysql.com/worklog. Fortunately the Wayback Machine had crawled the page in 2012 -- you can find it by looking on http://web.archive.org/web/20120508072213/http://forge.mysql.com/worklog/task.php?id=988. Remember that worklog pages are tabbed, and you must click the "High Level Architecture" button to see most of the specification.
I'm using MariaDB 10.0.8 built from source. This is an early version and changes will happen before General Availability happens. Although MariaDB has its own worklog task which I'll refer to as MDEV-4397, I prefer WL#988 for obvious reasons. For this review I'll use the same order and same section names as WL#988 high-level architecture. It will be hard to follow unless you read WL#988 first.
"CREATE ROLE role_1;" works. Good.
"CREATE ROLE IF NOT EXISTS role_2;" does not work. I'm ambivalent about this. I don't like the IF NOT EXISTS clause myself, but I thought it was the way of the future, according to another MySQL Worklog task, WL#3129 Consistent clauses in CREATE and DROP.
Here's the first bug: I get a crash if I use a bad name:
MariaDB [test]> CREATE ROLE ``; ERROR 2013 (HY000): Lost connection to MySQL server during query
MariaDB allows role names to be wildcards or privilege names, for example
MariaDB [test]> CREATE ROLE super; Query OK, 0 rows affected (0.00 sec) MariaDB [d]> create role '%'; Query OK, 0 rows affected (0.00 sec)
I regard that as a flaw. Somewhere, ages hence, somebody will get confused because these names have special meanings.
"DROP ROLE role_1;" works. Good.
"DROP ROLE IF EXISTS role_1;" does not work. Once again I'm ambivalent. WL#988 specifies that a NOT EXISTS clause should be permissible. On the other hand, "DROP USER IF EXISTS u;" doesn't work either.
DROP ROLE does not take effect immediately. If a user has enabled role_x, then that user continues to have the privileges of role_x even after role_x is dropped. I regard that as a flaw. [UPDATE: it's normal, see the comments.]
Here's the second bug: DROP USER crashes if I specify a role name:
MariaDB [test]> CREATE ROLE role_55; Query OK, 0 rows affected (0.00 sec) MariaDB [test]> DROP USER role_55; ERROR 2013 (HY000): Lost connection to MySQL server during query
"GRANT SELECT ON t TO role_1;" works. Good.
"GRANT PROXY ON a TO role_1;" works too. I'm ambivalent yet again. As WL#988 says, it's meaningless to grant a privilege to a role if it only can affect a user. There will always be an argument between people who think this must mean the user is making a mistake, and people who think it doesn't do any harm.
The privilege change does not take effect immediately. Consider what WL#988 says:
"The privilege change takes effect for the next statement that any affected user executes. (A user is affected if he/she has an enabled role that is affected by the privilege change.)
That seems reasonable -- after all, if I say "GRANT ... TO user_x" then user_x gets the privilege immediately. But it doesn't happen for roles. If I say "GRANT ... TO role_x", then role_x gets the privilege -- but users who have enabled role_x do not. I regard this as a flaw. [UPDATE: my test was bad, I was not granting the same type of privilege to both the role and the user, see the comments.]
Here's the third bug: if a user and a role have the same name, GRANT only works on the role. If I say
CREATE USER u_1; CREATE ROLE u_1; GRANT INSERT ON *.* TO u_1; SELECT host,user,insert_priv,is_role FROM mysql.user WHERE mysql.user.user='u_1';
I'd expect that INSERT would be granted to both the user and the role. It's not, it's granted only to the role. Moral: users and roles should not have the same names!
"GRANT role_1 TO user_1;" works. Good. "GRANT role_1 ON *.* TO user_1;" would be good too but an [ON *.*] clause should never be necessary.
"GRANT role_1 TO user_1 WITH GRANT OPTION;" does not work. I'm ambivalent yet again. What we actually have is an ability to say "GRANT role_1 TO user_1 WITH ADMIN OPTION;" and that's okay (WITH ADMIN OPTION is a standard clause), but I'm not sure it's a good idea that it's the default.
"GRANT role_1, role_2 to user_1;" does not work. I regard this as a flaw, but it might have been tough to implement. (UPDATE: 2016-03-20: It is indeed tough to implement. Two months after this blog comment was made, since the manual said that it would work, this flaw was reported as a bug: MDEV-5772 Granting multiple roles in single statement does not work. But two years later it was still unresolved.)
As WL#988 puts it:
Some People will say that, if Peter said CREATE ROLE Role1, Peter should automatically have GRANT Role1 privilege. The analogy is with the way that we grant for routines. This is what would happen with Oracle.
Other People will say that, no, the only way for Peter to get GRANT Role1 privileges is if somebody grants to Peter. The analogy is with the way that we grant for tables.
The implementer decided to follow the advice of Some People for this one. Good.
"GRANT role_1 TO role_2;" works. Good.
"GRANT role_1 TO role_1;" does not work -- and that's good too. This is a special instance of what WL#988 calls a "cyclical GRANT". Nobody would want that.
"GRANT CREATE ROLE ..." does not work. I regard that as a flaw.
WL#988 has quite a bit of verbiage about how there has to be a CREATE ROLE privilege, and why. MariaDB sidesteps -- the decision was that all you need is a CREATE USER privilege. Although I cannot think of a way that this could lead to security breaches, I am fairly sure that cases exist where administrators who want to allow user-creation do not want to allow role-creation, and vice versa.
"REVOKE role_1 FROM user_1;" works. Good.
"SET ROLE role_1;" works. And it only works if somebody previously said "GRANT role_1 TO user_1;" for the user that's doing the SET ROLE. Good.
"SET role 'role_1';" works, that is, the role name can be a string literal. But "SET @x='role_1'; SET ROLE @x;" does not work. I regard that as a flaw because it's a lack-of-orthogonality thing. By that I mean: if a string literal is okay in a certain place in an SQL statement, then The User On The Clapham Omnibus will expect that a string variable would be okay in the same place.
"SET ROLE role_1,role_2;" does not work. That's because MariaDB, unlike Oracle, doesn't allow assignment of multiple roles. And that's what WL#988 asks for, so no complaints here.
SET ROLE DEFAULT does not work. I regard that as a flaw. Yes, I recognize there is no non-null default, but that just means that SET ROLE DEFAULT should have the same effect as SET ROLE NONE.
The big example didn't work due to incompatible syntax, but I see that there is some evidence that testing has been done. Good.
"SELECT CURRENT_ROLE;" works. Good. As expected, it's possible to find out what the last "SET ROLE" statement did.
"SELECT CURRENT_ROLE();" also works. I regard this as a flaw but I suppose it was inevitable. After all, "SELECT CURRENT_USER();" works.
CURRENT_ROLE is not on the current reserved words list and this will cause a minor incompatibility with MySQL.
"ALTER ROLE role_1 ..." does not work. Good.
ALTER ROLE exists in Oracle for changing the IDENTIFIED BY clause, but MariaDB doesn't support the IDENTIFIED BY clause, so ALTER ROLE would have nothing to do.
"RENAME ROLE role_1 TO role_2;" does not work. Good.
Although "RENAME USER user_1 TO user_2;" works, WL#988 reminds us that renaming a role is going to have cascading effects that wouldn't exist for renaming a user.
"SHOW PRIVILEGES;" is the same as it used to be. Good.
Since MariaDB doesn't have a CREATE ROLE privilege, it doesn't have to list it.
"SET DEFAULT ROLE role_1 TO user_1;" does not work. I regard this as a flaw.
It's a good thing if, when user_1 connects, user_1 automatically gets the privileges associated with a default role. It's less good if user_1 has to do her own SET ROLE whenever she connects, probably with mysql --init_command='SET ROLE role_1'. The point of roles was supposed to be the elimination of administrator hassle, and --init-command won't help there.
The initial state when a user connects is, effectively, SET ROLE NONE. That'll have to do for now.
"SHOW GRANTS FOR user_1;" works and shows roles too. Good.
"SHOW GRANTS FOR CURRENT_ROLE;" also works. WL#988 failed to suggest this obvious addition, so maybe I should call it: better than good.
"SELECT * FROM INFORMATION_SCHEMA.ENABLED_ROLES;" works. Good.
"SELECT * FROM INFORMATION_SCHEMA.APPLICABLE_ROLES;" also works. Also good.
And it does show contained roles. That is, "If role1 is contained in role2, then both roles appear." However: It only shows the roles that have been granted for the current user, it does not show all roles. I regard that as a flaw.
[UPDATE: In an earlier version of this blog this observation was in the wrong place.] [UPDATE again: I missed what the cause was, the real problem was that I couldn't grant to public -- but that's another bug. See comments.]
"--role=role_name" doesn't work (on the command line). Good.
The way to go for setting a role name at connect time is to support defaults. Any other "solution" would just get in the way.
As WL#988 says, "The plan is to ignore replication till the last minute." I can't tell whether MariaDB is following such a plan.
Backup is simplified by the fact that users and roles come from the same table in the mysql database. I did not test it, so let's say: assumed good.
After "SET ROLE role_1; CREATE PROCEDURE p () SQL SECURITY DEFINER SELECT CURRENT_ROLE; CALL p();" the result is NULL. Good.
The idea is supposed to be that, within an SQL SECURITY DEFINER routine, an implicit SET ROLE NONE takes place temporarily. It's great that the implementer caught on to this rather obscure point.
After "CREATE ROLE 'role_5@localhost';" or "CREATE ROLE role_5;" I was not able to log in as role_5. Good.
However, "CREATE DEFINER=role_5 PROCEDURE p2() SET @x=5;" works. I regard that as a flaw. There should at least be a warning that the DEFINER clause is being used with a nonexistent user. I get the impression from MDEV-4397 that this is deliberate behaviour, so don't expect a fix.
The Oracle-style restriction for CREATE VIEW isn't there. Good.
Well, WL#988 anticipated that roles would come first, and pluggable authentication support would come later. It didn't happen that way. No problem.
It doesn't seem that the terminology differs from what's expected. Good.
When I say "CREATE ROLE role_1;" the effect is that a new row goes into the mysql.user table. The mysql.user table definition had to be adjusted to take that into account. But I don't think it's all good.
WL#988 expected that the length of a role name would be 16 characters, but mysql.user.user is now CHAR(80). Well, that was a flaw, but just an old bug that wasn't caused by introduction of roles.
The mysql.user.host column is blank (''). I'd expected it would be '%'. I suppose that this was intended as a way to disambiguate users and roles that had the same names, but that's not working well anyway. The column should be '%' because that means "any or all hosts".
There is one new column at the end, named is_role, which can be 'Y' or 'N'. I regard that as a flaw. The future-considerations way would have been to add a new column named user_type, which could be NULL or 'Role' -- or something else that somebody will dream up in a future release. The way it is now, if somebody dreams up another type later, they'll have to add yet another column in mysql.user.
The implementer picked "Alternative #2: (loosely based on a suggestion from Monty Widenius)". So there's a new table in the mysql database:
CREATE TABLE mysql.roles_mapping (Host CHAR(60), User CHAR(80), Role CHAR(80), Admin_option ENUM('N','Y'))
... And it works. Good. Although ROLE was a reserved word in SQL-99, and although WL#988 suggested an additional column and different names, it works.
However, storing the WITH ADMIN OPTION value should only be in one place and that place is mysql.user, so mysql.roles.mapping should not contain a column named Admin_option. I regard this as a flaw. [UPDATE: not it is not a flaw, see the comments.]
The early decisions of Monty Widenius were: we don't want passwords, we do want defaults, we don't want multiple current roles, and PUBLIC should not be a role. That's what ended up happening, except that there are no defaults. Good.
For all of the bugs, and for some of the flaws, there's no worry -- they'll probably disappear. The MariaDB advantage here is the release-early policy, because there's lots of advance time if there's a decision to make a few corrections or course changes.
The MariaDB disadvantage is illustrated by its worklog task for roles, MDEV-4397. I'm biased, but I think it's fair to say that MySQL's WL#988 "high level architecture" specification covered the necessary ground and MariaDB's MDEV-4397 did not. This means Oracle made the right decision when it "hid" its worklog task. Too bad it didn't work out this particular time.