1 /* Copyright (c) 2018, 2020, Oracle and/or its affiliates.
2
3 This program is free software; you can redistribute it and/or modify
4 it under the terms of the GNU General Public License, version 2.0,
5 as published by the Free Software Foundation.
6
7 This program is also distributed with certain software (including
8 but not limited to OpenSSL) that is licensed under separate terms,
9 as designated in a particular file or component or in included license
10 documentation. The authors of MySQL hereby grant you an additional
11 permission to link the program and your derivative works with the
12 separately licensed software that they have included with MySQL.
13
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License, version 2.0, for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software
21 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
22
23 #include "sql/auth/acl_table_user.h" /* For user table data */
24
25 #include <stdlib.h> /* atoi */
26 #include <string.h> /* strlen, strcmp, NULL, memcmp, memcpy */
27 #include <algorithm> /* sort */
28 #include <map> /* map */
29
30 #include "field_types.h" /* MYSQL_TYPE_ENUM, MYSQL_TYPE_JSON */
31 #include "lex_string.h" /* LEX_CSTRING */
32 #include "m_ctype.h" /* my_charset_* */
33 #include "m_string.h" /* STRING_WITH_LEN */
34 #include "my_base.h" /* HA_ERR_* */
35 #include "my_dbug.h" /* DBUG macros */
36 #include "my_inttypes.h" /* MYF, uchar, longlong, ulonglong */
37 #include "my_loglevel.h" /* WARNING_LEVEL, loglevel, ERROR_LEVEL */
38 #include "my_sqlcommand.h" /* SQLCOM_ALTER_USER, SQLCOM_GRANT */
39 #include "my_sys.h" /* my_error */
40 #include "mysql/components/services/log_builtins.h" /* for LogEvent, LogErr */
41 #include "mysql/plugin.h" /* st_mysql_plugin, MYSQL_AUTHENTICATION_PLUGIN */
42 #include "mysql/plugin_auth.h" /* st_mysql_auth */
43 #include "mysql/psi/psi_base.h" /* PSI_NOT_INSTRUMENTED */
44 #include "mysql_time.h" /* MYSQL_TIME, MYSQL_TIMESTAMP_ERROR */
45 #include "mysqld_error.h" /* ER_* */
46 #include "prealloced_array.h" /* Prealloced_array */
47 #include "sql/auth/auth_acls.h" /* ACLs */
48 #include "sql/auth/auth_common.h" /* User_table_schema, ... */
49 #include "sql/auth/auth_internal.h" /* acl_print_ha_error */
50 #include "sql/auth/partial_revokes.h"
51 #include "sql/auth/sql_auth_cache.h" /* global_acl_memory */
52 #include "sql/auth/sql_authentication.h" /* Cached_authentication_plugins */
53 #include "sql/auth/sql_user_table.h" /* Acl_table_intact */
54 #include "sql/auth/user_table.h" /* replace_user_table */
55 #include "sql/field.h" /* Field, Field_json, Field_enum, TYPE_OK */
56 #include "sql/handler.h" /* handler, DB_TYPE_NDBCLUSTER, handlerton */
57 #include "sql/item_func.h" /* mqh_used */
58 #include "sql/json_dom.h"
59 #include "sql/key.h" /* key_copy, KEY */
60 #include "sql/mysqld.h" /* specialflag */
61 #include "sql/records.h"
62 #include "sql/row_iterator.h" /* RowIterator */
63 #include "sql/sql_class.h" /* THD */
64 #include "sql/sql_const.h" /* ACL_ALLOC_BLOCK_SIZE, MAX_KEY_LENGTH */
65 #include "sql/sql_lex.h" /* LEX */
66 #include "sql/sql_plugin.h" /* plugin_unlock, my_plugin_lock_by_name */
67 #include "sql/sql_plugin_ref.h" /* plugin_decl, plugin_ref */
68 #include "sql/sql_time.h" /* str_to_time_with_warn */
69 #include "sql/sql_update.h" /* compare_records */
70 #include "sql/system_variables.h" /* System_variables */
71 #include "sql/table.h" /* TABLE, TABLE_SHARE, ... */
72 #include "sql/thr_malloc.h" /* init_sql_alloc */
73 #include "sql/tztime.h" /* Time_zone */
74 #include "sql_string.h" /* String */
75 #include "template_utils.h" /* down_cast */
76 #include "typelib.h" /* TYPELIB */
77 #include "violite.h" /* SSL_* */
78
79 #define INVALID_DATE "0000-00-00 00:00:00"
80
81 namespace consts {
82 /** Initial timestamp */
83 const struct timeval BEGIN_TIMESTAMP = {0, 0};
84
85 /** Error indicating table operation error */
86 const int CRITICAL_ERROR = -1;
87
88 /** Empty string */
89 const std::string empty_string("");
90
91 /* Name of the fields in mysql.user.user_attributes */
92
93 /** For secondary password */
94 const std::string additional_password("additional_password");
95
96 /** For partial revokes */
97 const std::string Restrictions("Restrictions");
98
99 /** for password locking */
100 const std::string Password_locking("Password_locking");
101
102 /** underkeys of password locking */
103 const std::string failed_login_attempts("failed_login_attempts");
104
105 /** underkeys of password locking */
106 const std::string password_lock_time_days("password_lock_time_days");
107
108 /** metadata tag */
109 const std::string json_metadata_tag("metadata");
110
111 /** comment tag */
112 const std::string json_comment_tag("comment");
113
114 } // namespace consts
115
116 bool replace_user_metadata(THD *thd, const std::string &json_blob,
117 bool expect_text, TABLE *user_table);
118
119 namespace acl_table {
120
121 /** Keys used in mysql.user.user_attributes */
122 static std::map<const User_attribute_type, const std::string>
123 attribute_type_to_str = {
124 {User_attribute_type::ADDITIONAL_PASSWORD, consts::additional_password},
125 {User_attribute_type::RESTRICTIONS, consts::Restrictions},
126 {User_attribute_type::PASSWORD_LOCKING, consts::Password_locking},
127 {User_attribute_type::METADATA, consts::json_metadata_tag},
128 {User_attribute_type::COMMENT, consts::json_comment_tag}};
129
130 namespace {
131 /**
132 Class to handle information stored in mysql.user.user_attributes
133 */
134 class Acl_user_attributes {
135 public:
136 /**
137 Default constructor.
138 */
139 Acl_user_attributes(MEM_ROOT *mem_root, bool read_restrictions,
140 Auth_id &auth_id, ulong global_privs);
141
142 Acl_user_attributes(MEM_ROOT *mem_root, bool read_restrictions,
143 Auth_id &auth_id, Restrictions *m_restrictions);
144
145 ~Acl_user_attributes();
146
147 public:
148 /**
149 Obtain info from JSON representation of user attributes
150
151 @param [in] json_object JSON object that holds user attributes
152
153 @returns status of parsing json_object
154 @retval false Success
155 @retval true Error parsing the JSON object
156 */
157 bool deserialize(const Json_object &json_object);
158
159 /**
160 Create JSON object from user attributes
161
162 @param [out] json_object Object to store serialized user attributes
163
164 @returns status of serialization
165 @retval false Success
166 @retval true Error serializing user attributes
167 */
168 bool serialize(Json_object &json_object) const;
169
170 /**
171 Update second password for user. We replace existing one if any.
172
173 @param [in] credential Second password
174
175 @returns status of password update
176 @retval false Success
177 @retval true Error. Second password is empty
178 */
179 bool update_additional_password(std::string &credential);
180
181 /**
182 Discard second password.
183 */
184 void discard_additional_password();
185
186 /**
187 Get second password
188
189 @returns second password
190 */
191 const std::string get_additional_password() const;
192
193 /**
194 Get the restriction list for the user
195
196 @returns Restriction list
197 */
198 Restrictions get_restrictions() const;
199
200 void update_restrictions(const Restrictions &restricitions);
201
get_failed_login_attempts() const202 auto get_failed_login_attempts() const {
203 return m_password_lock.failed_login_attempts;
204 }
get_password_lock_time_days() const205 auto get_password_lock_time_days() const {
206 return m_password_lock.password_lock_time_days;
207 }
get_password_lock() const208 auto get_password_lock() const { return m_password_lock; }
set_password_lock(Password_lock password_lock)209 void set_password_lock(Password_lock password_lock) {
210 m_password_lock = password_lock;
211 }
212
213 /**
214 Take over ownership of the json pointer.
215 @return Error state
216 @retval true An error occurred
217 @retval false Success
218 */
219 bool consume_user_attributes_json(Json_dom_ptr json);
220
221 private:
222 void report_and_remove_invalid_db_restrictions(
223 DB_restrictions &db_restrictions, ulong mask, enum loglevel level,
224 ulonglong errcode);
225 bool deserialize_password_lock(const Json_object &json_object);
226
227 private:
228 /** Mem root */
229 MEM_ROOT *m_mem_root;
230 /** Operation for restrictions */
231 bool m_read_restrictions;
232 /** Auth ID */
233 Auth_id m_auth_id;
234 /** Second password for user */
235 std::string m_additional_password;
236 /** Restrictions_list on certain databases for user */
237 Restrictions m_restrictions;
238 /** Global static privileges */
239 ulong m_global_privs;
240 /** password locking */
241 Password_lock m_password_lock;
242 /** Save the original json object */
243 Json_dom_ptr m_user_attributes_json;
244 };
245
Acl_user_attributes(MEM_ROOT * mem_root,bool read_restrictions,Auth_id & auth_id,ulong global_privs)246 Acl_user_attributes::Acl_user_attributes(MEM_ROOT *mem_root,
247 bool read_restrictions,
248 Auth_id &auth_id, ulong global_privs)
249 : m_mem_root(mem_root),
250 m_read_restrictions(read_restrictions),
251 m_auth_id(auth_id),
252 m_additional_password(),
253 m_restrictions(mem_root),
254 m_global_privs(global_privs),
255 m_password_lock(),
256 m_user_attributes_json(nullptr) {}
257
Acl_user_attributes(MEM_ROOT * mem_root,bool read_restrictions,Auth_id & auth_id,Restrictions * restrictions)258 Acl_user_attributes::Acl_user_attributes(MEM_ROOT *mem_root,
259 bool read_restrictions,
260 Auth_id &auth_id,
261 Restrictions *restrictions)
262 : Acl_user_attributes(mem_root, read_restrictions, auth_id, ~NO_ACCESS) {
263 if (restrictions) m_restrictions = *restrictions;
264 }
265
~Acl_user_attributes()266 Acl_user_attributes::~Acl_user_attributes() { m_restrictions.clear_db(); }
267
consume_user_attributes_json(Json_dom_ptr json)268 bool Acl_user_attributes::consume_user_attributes_json(Json_dom_ptr json) {
269 if (!json || json->json_type() != enum_json_type::J_OBJECT) {
270 json = create_dom_ptr<Json_object>();
271 if (!json) return true;
272 }
273 Json_object *ob = down_cast<Json_object *>(json.get());
274 Json_dom *metadata =
275 ob->get(attribute_type_to_str[User_attribute_type::METADATA]);
276 if (metadata) {
277 Json_object *metadata_ob = down_cast<Json_object *>(metadata);
278 m_user_attributes_json = create_dom_ptr<Json_object>();
279 Json_object *user_attributes_ob =
280 down_cast<Json_object *>(m_user_attributes_json.get());
281 user_attributes_ob->add_clone(
282 attribute_type_to_str[User_attribute_type::METADATA], metadata_ob);
283 }
284 return false;
285 }
286
report_and_remove_invalid_db_restrictions(DB_restrictions & db_restrictions,ulong mask,enum loglevel level,ulonglong errcode)287 void Acl_user_attributes::report_and_remove_invalid_db_restrictions(
288 DB_restrictions &db_restrictions, ulong mask, enum loglevel level,
289 ulonglong errcode) {
290 for (auto &itr : db_restrictions()) {
291 ulong privs = itr.second;
292 if (privs != (privs & mask)) {
293 std::string invalid_privs;
294 std::string separator(", ");
295 bool second = false;
296 ulong filtered_privs = privs & ~mask;
297 if (filtered_privs)
298 db_restrictions.remove(itr.first.c_str(), filtered_privs);
299 while (filtered_privs != 0) {
300 std::string one_priv = get_one_priv(filtered_privs);
301 if (one_priv.length()) {
302 if (second) invalid_privs.append(separator);
303 invalid_privs.append(one_priv);
304 if (!second) second = true;
305 }
306 }
307 if (!invalid_privs.length()) invalid_privs.append("<unknown_privileges>");
308 std::string auth_id;
309 m_auth_id.auth_str(&auth_id);
310
311 LogErr(level, errcode, auth_id.c_str(), invalid_privs.c_str(),
312 itr.first.length() ? itr.first.c_str() : "<invalid_database>");
313 }
314 }
315 /*
316 Now, remove the databases with no restrictions without invalidating
317 the internal container of DB_restrictions
318 */
319 db_restrictions.remove(0);
320 }
321
deserialize_password_lock(const Json_object & json_object)322 bool Acl_user_attributes::deserialize_password_lock(
323 const Json_object &json_object) {
324 /* password locking */
325 m_password_lock.password_lock_time_days = 0;
326 m_password_lock.failed_login_attempts = 0;
327
328 const Json_dom *password_locking_dom = json_object.get(
329 attribute_type_to_str[User_attribute_type::PASSWORD_LOCKING]);
330 if (password_locking_dom) {
331 if (password_locking_dom->json_type() != enum_json_type::J_OBJECT)
332 return true;
333 const Json_object *password_locking =
334 down_cast<const Json_object *>(password_locking_dom);
335
336 const Json_dom *password_lock_time_days_dom =
337 password_locking->get(consts::password_lock_time_days);
338 if (password_lock_time_days_dom) {
339 if (password_lock_time_days_dom->json_type() != enum_json_type::J_INT)
340 return true;
341 const Json_int *password_lock_time_days =
342 down_cast<const Json_int *>(password_lock_time_days_dom);
343 longlong val = password_lock_time_days->value();
344 if (val < -1 || val > INT_MAX) return true;
345 m_password_lock.password_lock_time_days = val;
346 }
347
348 const Json_dom *failed_login_attempts_dom =
349 password_locking->get(consts::failed_login_attempts);
350 if (failed_login_attempts_dom) {
351 if (failed_login_attempts_dom->json_type() != enum_json_type::J_INT) {
352 m_password_lock.password_lock_time_days = 0;
353 return true;
354 }
355 const Json_int *failed_login_attempts =
356 down_cast<const Json_int *>(failed_login_attempts_dom);
357 longlong val = failed_login_attempts->value();
358 if (val < 0 || val > UINT_MAX) {
359 m_password_lock.password_lock_time_days = 0;
360 return true;
361 }
362 m_password_lock.failed_login_attempts = val;
363 }
364 }
365 return false;
366 }
367
deserialize(const Json_object & json_object)368 bool Acl_user_attributes::deserialize(const Json_object &json_object) {
369 {
370 /** Second password */
371 const Json_dom *additional_password_dom = json_object.get(
372 attribute_type_to_str[User_attribute_type::ADDITIONAL_PASSWORD]);
373 if (additional_password_dom) {
374 if (additional_password_dom->json_type() != enum_json_type::J_STRING)
375 return true;
376
377 const Json_string *additional_password =
378 down_cast<const Json_string *>(additional_password_dom);
379 m_additional_password = additional_password->value();
380 }
381 }
382
383 /* In cse of writes, DB restrictions are always overwritten */
384 if (m_read_restrictions) {
385 DB_restrictions db_restrictions(nullptr);
386 if (db_restrictions.add(json_object)) return true;
387 /* Filtering & warnings */
388 report_and_remove_invalid_db_restrictions(
389 db_restrictions, DB_OP_ACLS, WARNING_LEVEL,
390 ER_WARN_INCORRECT_PRIVILEGE_FOR_DB_RESTRICTIONS);
391 report_and_remove_invalid_db_restrictions(db_restrictions, m_global_privs,
392 WARNING_LEVEL,
393 ER_WARN_INVALID_DB_RESTRICTIONS);
394 m_restrictions.set_db(db_restrictions);
395 }
396
397 return deserialize_password_lock(json_object);
398 }
399
serialize(Json_object & json_object) const400 bool Acl_user_attributes::serialize(Json_object &json_object) const {
401 if (m_additional_password.length()) {
402 Json_string additional_password(m_additional_password);
403 if (json_object.add_clone(
404 attribute_type_to_str[User_attribute_type::ADDITIONAL_PASSWORD],
405 &additional_password))
406 return true;
407 } else if (m_user_attributes_json) {
408 Json_object *obj = down_cast<Json_object *>(m_user_attributes_json.get());
409 obj->remove(
410 attribute_type_to_str[User_attribute_type::ADDITIONAL_PASSWORD]);
411 }
412
413 if (m_restrictions.db().is_not_empty()) {
414 Json_array restrictions_array;
415 m_restrictions.db().get_as_json(restrictions_array);
416 if (json_object.add_clone(
417 attribute_type_to_str[User_attribute_type::RESTRICTIONS],
418 &restrictions_array))
419 return true;
420 } else if (m_user_attributes_json) {
421 Json_object *obj = down_cast<Json_object *>(m_user_attributes_json.get());
422 obj->remove(attribute_type_to_str[User_attribute_type::RESTRICTIONS]);
423 }
424
425 if (m_password_lock.password_lock_time_days ||
426 m_password_lock.failed_login_attempts) {
427 Json_object password_lock;
428 Json_int password_lock_time_days(m_password_lock.password_lock_time_days);
429 Json_int failed_login_attempts(m_password_lock.failed_login_attempts);
430 password_lock.add_clone(consts::password_lock_time_days,
431 &password_lock_time_days);
432 password_lock.add_clone(consts::failed_login_attempts,
433 &failed_login_attempts);
434 json_object.add_clone(
435 attribute_type_to_str[User_attribute_type::PASSWORD_LOCKING],
436 &password_lock);
437 } else if (m_user_attributes_json) {
438 Json_object *obj = down_cast<Json_object *>(m_user_attributes_json.get());
439 obj->remove(attribute_type_to_str[User_attribute_type::PASSWORD_LOCKING]);
440 }
441
442 if (m_user_attributes_json) {
443 Json_dom_ptr copy_attributes = m_user_attributes_json->clone();
444 Json_object_ptr tmp(down_cast<Json_object *>(copy_attributes.release()));
445 json_object.merge_patch(std::move(tmp));
446 }
447
448 return false;
449 }
450
update_additional_password(std::string & credential)451 bool Acl_user_attributes::update_additional_password(std::string &credential) {
452 if (credential.length()) {
453 m_additional_password = credential;
454 } else {
455 return true;
456 }
457 return false;
458 }
459
discard_additional_password()460 void Acl_user_attributes::discard_additional_password() {
461 m_additional_password.clear();
462 }
463
get_additional_password() const464 const std::string Acl_user_attributes::get_additional_password() const {
465 return m_additional_password;
466 }
467
get_restrictions() const468 Restrictions Acl_user_attributes::get_restrictions() const {
469 return m_restrictions;
470 }
471
update_restrictions(const Restrictions & restricitions)472 void Acl_user_attributes::update_restrictions(
473 const Restrictions &restricitions) {
474 m_restrictions = restricitions;
475 }
476
477 /**
478 Helper function to parse mysql.user.user_attributes column
479
480 @param [in] thd Thread handle
481 @param [in] table Handle to mysql.user table
482 @param [in] table_schema mysql.user schema version
483 @param [out] user_attributes Deserialized user attributes
484
485 @returns status of parsing user_attributes column
486 @retval false Success
487 @retval true Problem parsing the column
488 */
parse_user_attributes(THD * thd,TABLE * table,User_table_schema * table_schema,Acl_user_attributes & user_attributes)489 bool parse_user_attributes(THD *thd, TABLE *table,
490 User_table_schema *table_schema,
491 Acl_user_attributes &user_attributes) {
492 // Read only if the column of type JSON and it is not null.
493 if (table->field[table_schema->user_attributes_idx()]->type() ==
494 MYSQL_TYPE_JSON &&
495 !table->field[table_schema->user_attributes_idx()]->is_null()) {
496 Json_wrapper json_wrapper;
497 if ((down_cast<Field_json *>(
498 table->field[table_schema->user_attributes_idx()])
499 ->val_json(&json_wrapper)))
500 return true;
501 if (user_attributes.consume_user_attributes_json(
502 json_wrapper.clone_dom(thd)))
503 return true;
504 const Json_object *json_object =
505 down_cast<const Json_object *>(json_wrapper.to_dom(thd));
506 if (user_attributes.deserialize(*json_object)) return true;
507 }
508 return false;
509 }
510 } // namespace
511
Acl_table_user_writer_status(MEM_ROOT * mem_root)512 Acl_table_user_writer_status::Acl_table_user_writer_status(MEM_ROOT *mem_root)
513 : skip_cache_update(true),
514 updated_rights(NO_ACCESS),
515 error(consts::CRITICAL_ERROR),
516 password_change_timestamp(consts::BEGIN_TIMESTAMP),
517 second_cred(consts::empty_string),
518 restrictions(mem_root),
519 password_lock() {}
520
521 /**
522 mysql.user table writer constructor
523
524 Note: Table handle must be non-null.
525
526 @param [in] thd Thread handle
527 @param [in] table Handle to mysql.user table
528 @param [in] combo User information
529 @param [in] rights Updated global privileges
530 @param [in] revoke_grant If its REVOKE statement
531 @param [in] can_create_user Whether user has ability to create new user
532 @param [in] what_to_update Things to be updated
533 @param [in] restrictions Restrictions of the user, if there is any
534 */
Acl_table_user_writer(THD * thd,TABLE * table,LEX_USER * combo,ulong rights,bool revoke_grant,bool can_create_user,Pod_user_what_to_update what_to_update,Restrictions * restrictions=nullptr)535 Acl_table_user_writer::Acl_table_user_writer(
536 THD *thd, TABLE *table, LEX_USER *combo, ulong rights, bool revoke_grant,
537 bool can_create_user, Pod_user_what_to_update what_to_update,
538 Restrictions *restrictions = nullptr)
539 : Acl_table(thd, table, acl_table::Acl_table_operation::OP_INSERT),
540 m_has_user_application_user_metadata(false),
541 m_combo(combo),
542 m_rights(rights),
543 m_revoke_grant(revoke_grant),
544 m_can_create_user(can_create_user),
545 m_what_to_update(what_to_update),
546 m_table_schema(nullptr),
547 m_restrictions(restrictions) {
548 if (table) {
549 User_table_schema_factory user_table_schema_factory;
550 m_table_schema = user_table_schema_factory.get_user_table_schema(table);
551 }
552 }
553
554 /** Cleanup */
~Acl_table_user_writer()555 Acl_table_user_writer::~Acl_table_user_writer() {
556 if (m_table_schema) delete m_table_schema;
557 }
558
559 /**
560 Perform add/update to mysql.user table
561
562 @returns status of add/update operation. In case of success it contains
563 information that's useful for cache update.
564 */
driver()565 Acl_table_user_writer_status Acl_table_user_writer::driver() {
566 bool builtin_plugin = false;
567 bool update_password = (m_what_to_update.m_what & PLUGIN_ATTR);
568 Table_op_error_code error;
569 LEX *lex = m_thd->lex;
570 Acl_table_user_writer_status return_value(m_thd->mem_root);
571 Acl_table_user_writer_status err_return_value(m_thd->mem_root);
572
573 DBUG_TRACE;
574 DBUG_ASSERT(assert_acl_cache_write_lock(m_thd));
575
576 /* Setup the table for writing */
577 if (setup_table(error, builtin_plugin)) {
578 return_value.error = error;
579 return return_value;
580 }
581
582 if (m_operation == Acl_table_operation::OP_UPDATE) {
583 if ((lex->sql_command != SQLCOM_ALTER_USER) && !m_rights &&
584 lex->ssl_type == SSL_TYPE_NOT_SPECIFIED && !lex->mqh.specified_limits &&
585 !m_revoke_grant && (!builtin_plugin || !update_password) &&
586 !m_restrictions) {
587 DBUG_PRINT("info", ("Dynamic privileges exit path"));
588 /*
589 At this point, even though there is no error,
590 we want to skip updates to cache because it's a no-op.
591 */
592 return_value.error = 0;
593 return return_value;
594 }
595 }
596
597 std::string current_password;
598 if ((m_what_to_update.m_what & USER_ATTRIBUTES) &&
599 (m_what_to_update.m_user_attributes & USER_ATTRIBUTE_RETAIN_PASSWORD))
600 current_password = get_current_credentials();
601
602 if (update_authentication_info(return_value) ||
603 update_privileges(return_value) || update_ssl_properties() ||
604 update_user_attributes(current_password, return_value) ||
605 update_user_resources() || update_password_expiry() ||
606 update_password_history() || update_password_reuse() ||
607 update_password_require_current() || update_account_locking() ||
608 update_user_application_user_metadata()) {
609 return err_return_value;
610 }
611
612 (void)finish_operation(error);
613
614 if (!error) {
615 return_value.error = 0;
616 return_value.skip_cache_update = false;
617 }
618
619 return return_value;
620 }
621
622 /**
623 Position user table.
624
625 Try to find a row matching with given account information. If one is
626 found, set record pointer to it and set operation type as UPDATE. If no
627 record is found, then set record pointer to empty record.
628
629 Raises error in DA in various cases where sanity of table and
630 intention of operation is checked.
631
632 @param [out] error Table operation error
633 @param [out] builtin_plugin For existing record, if authentication plugin
634 is one of the builtins or not.
635
636 @returns Operation status
637 @retval false Table is positioned. In case of insert, it means no record
638 is found for given (user,host). In case of update, table
639 is set to point to existing record.
640 @retval true Error positioning table.
641 */
setup_table(int & error,bool & builtin_plugin)642 bool Acl_table_user_writer::setup_table(int &error, bool &builtin_plugin) {
643 bool update_password = (m_what_to_update.m_what & PLUGIN_ATTR);
644 switch (m_operation) {
645 case Acl_table_operation::OP_INSERT:
646 case Acl_table_operation::OP_UPDATE: {
647 uchar user_key[MAX_KEY_LENGTH];
648 Acl_table_intact table_intact(m_thd);
649 LEX_CSTRING old_plugin;
650 error = consts::CRITICAL_ERROR;
651 builtin_plugin = false;
652 if (table_intact.check(m_table, ACL_TABLES::TABLE_USER)) return true;
653
654 m_table->use_all_columns();
655 DBUG_ASSERT(m_combo->host.str != nullptr);
656 m_table->field[m_table_schema->host_idx()]->store(
657 m_combo->host.str, m_combo->host.length, system_charset_info);
658 m_table->field[m_table_schema->user_idx()]->store(
659 m_combo->user.str, m_combo->user.length, system_charset_info);
660 key_copy(user_key, m_table->record[0], m_table->key_info,
661 m_table->key_info->key_length);
662
663 error = m_table->file->ha_index_read_idx_map(
664 m_table->record[0], 0, user_key, HA_WHOLE_KEY, HA_READ_KEY_EXACT);
665 DBUG_ASSERT(m_table->file->ht->db_type == DB_TYPE_NDBCLUSTER ||
666 error != HA_ERR_LOCK_DEADLOCK);
667 DBUG_ASSERT(m_table->file->ht->db_type == DB_TYPE_NDBCLUSTER ||
668 error != HA_ERR_LOCK_WAIT_TIMEOUT);
669 DBUG_EXECUTE_IF("wl7158_replace_user_table_1",
670 error = HA_ERR_LOCK_DEADLOCK;);
671 if (error) {
672 if (error != HA_ERR_KEY_NOT_FOUND && error != HA_ERR_END_OF_FILE) {
673 acl_print_ha_error(error);
674 return true;
675 }
676 m_operation = Acl_table_operation::OP_INSERT;
677
678 /*
679 The user record wasn't found; if the intention was to revoke
680 privileges (indicated by what == 'N') then execution must fail
681 now.
682 */
683 if (m_revoke_grant) {
684 my_error(ER_NONEXISTING_GRANT, MYF(0), m_combo->user.str,
685 m_combo->host.str);
686 /*
687 Return 1 as an indication that expected error occurred during
688 handling of REVOKE statement for an unknown user.
689 */
690 error = 1;
691 return true;
692 }
693
694 if (m_thd->lex->sql_command == SQLCOM_ALTER_USER) {
695 /* Entry should have existsed since this is ALTER USER */
696 error = 1;
697 return true;
698 }
699
700 optimize_plugin_compare_by_pointer(&m_combo->plugin);
701 builtin_plugin = auth_plugin_is_built_in(m_combo->plugin.str);
702
703 /* The user record was neither present nor the intention was to
704 * create it */
705 if (!m_can_create_user) {
706 if (!update_password) {
707 /* Have come here to GRANT privilege to the non-existing user */
708 my_error(ER_CANT_CREATE_USER_WITH_GRANT, MYF(0));
709 } else {
710 /* Have come here to update the password of the non-existing
711 * user
712 */
713 my_error(ER_PASSWORD_NO_MATCH, MYF(0), m_combo->user.str,
714 m_combo->host.str);
715 }
716 error = 1;
717 return true;
718 }
719 if (m_thd->lex->sql_command == SQLCOM_GRANT) {
720 my_error(ER_PASSWORD_NO_MATCH, MYF(0), m_combo->user.str,
721 m_combo->host.str);
722 error = 1;
723 return true;
724 }
725 restore_record(m_table, s->default_values);
726 DBUG_ASSERT(m_combo->host.str != nullptr);
727 m_table->field[m_table_schema->host_idx()]->store(
728 m_combo->host.str, m_combo->host.length, system_charset_info);
729 m_table->field[m_table_schema->user_idx()]->store(
730 m_combo->user.str, m_combo->user.length, system_charset_info);
731 } else {
732 /* There is a matching user record */
733 m_operation = Acl_table_operation::OP_UPDATE;
734
735 /* Check if there is such a user in user table in memory? */
736
737 if (!find_acl_user(m_combo->host.str, m_combo->user.str, false)) {
738 my_error(ER_PASSWORD_NO_MATCH, MYF(0));
739 error = consts::CRITICAL_ERROR;
740 return true;
741 }
742
743 store_record(m_table, record[1]); // Save copy for update
744
745 /* 1. resolve plugins in the LEX_USER struct if needed */
746 /* Get old plugin value from storage. */
747 old_plugin.str = get_field(
748 m_thd->mem_root, m_table->field[m_table_schema->plugin_idx()]);
749
750 if (old_plugin.str == nullptr || *old_plugin.str == '\0') {
751 my_error(ER_PASSWORD_NO_MATCH, MYF(0));
752 error = 1;
753 return true;
754 }
755
756 /*
757 It is important not to include the trailing '\0' in the string
758 length because otherwise the plugin hash search will fail.
759 */
760 old_plugin.length = strlen(old_plugin.str);
761
762 /* Optimize for pointer comparision of built-in plugin name */
763 optimize_plugin_compare_by_pointer(&old_plugin);
764 builtin_plugin = auth_plugin_is_built_in(old_plugin.str);
765 }
766 break;
767 }
768 default:
769 return false;
770 }
771 return false;
772 }
773
774 /**
775 Finish the operation
776
777 Depending on type of operation (INSERT/UPDATE), either insert a new row
778 in mysql.user table or update an existing row using SE APIs.
779
780 @param [out] out_error Table operation error, if any
781
782 @returns status of write operation
783 */
finish_operation(Table_op_error_code & out_error)784 Acl_table_op_status Acl_table_user_writer::finish_operation(
785 Table_op_error_code &out_error) {
786 switch (m_operation) {
787 case Acl_table_operation::OP_INSERT: {
788 out_error = m_table->file->ha_write_row(m_table->record[0]); // insert
789 DBUG_ASSERT(out_error != HA_ERR_FOUND_DUPP_KEY);
790 DBUG_ASSERT(m_table->file->ht->db_type == DB_TYPE_NDBCLUSTER ||
791 out_error != HA_ERR_LOCK_DEADLOCK);
792 DBUG_ASSERT(m_table->file->ht->db_type == DB_TYPE_NDBCLUSTER ||
793 out_error != HA_ERR_LOCK_WAIT_TIMEOUT);
794 DBUG_EXECUTE_IF("wl7158_replace_user_table_3",
795 out_error = HA_ERR_LOCK_DEADLOCK;);
796 if (out_error) {
797 if (!m_table->file->is_ignorable_error(out_error)) {
798 acl_print_ha_error(out_error);
799 out_error = consts::CRITICAL_ERROR;
800 return Acl_table_op_status::OP_ERROR_CRITICAL;
801 }
802 }
803 break;
804 }
805 case Acl_table_operation::OP_UPDATE: {
806 /*
807 We should NEVER delete from the user table, as a uses can still
808 use mysqld even if he doesn't have any privileges in the user table!
809 */
810 if (compare_records(m_table)) {
811 out_error = m_table->file->ha_update_row(m_table->record[1],
812 m_table->record[0]);
813 DBUG_ASSERT(out_error != HA_ERR_FOUND_DUPP_KEY);
814 DBUG_ASSERT(m_table->file->ht->db_type == DB_TYPE_NDBCLUSTER ||
815 out_error != HA_ERR_LOCK_DEADLOCK);
816 DBUG_ASSERT(m_table->file->ht->db_type == DB_TYPE_NDBCLUSTER ||
817 out_error != HA_ERR_LOCK_WAIT_TIMEOUT);
818 DBUG_EXECUTE_IF("wl7158_replace_user_table_2",
819 out_error = HA_ERR_LOCK_DEADLOCK;);
820 if (out_error && out_error != HA_ERR_RECORD_IS_THE_SAME) {
821 acl_print_ha_error(out_error);
822 out_error = consts::CRITICAL_ERROR;
823 return Acl_table_op_status::OP_ERROR_CRITICAL;
824 } else
825 out_error = 0;
826 }
827 break;
828 }
829 default:
830 out_error = 0;
831 }
832 return Acl_table_op_status::OP_OK;
833 }
834
835 /**
836 Update user's authentication information
837
838 Raises error in DA if mysql.user table does not have following columns:
839 - plugin
840 - password_last_changed
841 - password_expired
842
843 @param [out] return_value To update password change timestamp
844
845 @returns update operation status
846 @retval false Success
847 @retval true Error storing authentication info or table is not in
848 expected format
849 */
update_authentication_info(Acl_table_user_writer_status & return_value)850 bool Acl_table_user_writer::update_authentication_info(
851 Acl_table_user_writer_status &return_value) {
852 if (m_what_to_update.m_what & PLUGIN_ATTR ||
853 (m_what_to_update.m_what & DEFAULT_AUTH_ATTR &&
854 m_operation == Acl_table_operation::OP_INSERT)) {
855 bool builtin_plugin;
856 if (m_table->s->fields >= m_table_schema->plugin_idx()) {
857 m_table->field[m_table_schema->plugin_idx()]->store(
858 m_combo->plugin.str, m_combo->plugin.length, system_charset_info);
859 m_table->field[m_table_schema->plugin_idx()]->set_notnull();
860 m_table->field[m_table_schema->authentication_string_idx()]->store(
861 m_combo->auth.str, m_combo->auth.length, &my_charset_utf8_bin);
862 m_table->field[m_table_schema->authentication_string_idx()]
863 ->set_notnull();
864 } else {
865 my_error(ER_BAD_FIELD_ERROR, MYF(0), "plugin", "mysql.user");
866 return true;
867 }
868 /* If we change user plugin then check if it is builtin plugin */
869 optimize_plugin_compare_by_pointer(&m_combo->plugin);
870 builtin_plugin = auth_plugin_is_built_in(m_combo->plugin.str);
871 /*
872 we update the password last changed field whenever there is change
873 in auth str and plugin is built in
874 */
875 if (m_table->s->fields > m_table_schema->password_last_changed_idx()) {
876 if (builtin_plugin) {
877 /*
878 Calculate time stamp up to seconds elapsed from 1 Jan 1970
879 00:00:00.
880 */
881 return_value.password_change_timestamp =
882 m_thd->query_start_timeval_trunc(0);
883 m_table->field[m_table_schema->password_last_changed_idx()]
884 ->store_timestamp(&return_value.password_change_timestamp);
885 m_table->field[m_table_schema->password_last_changed_idx()]
886 ->set_notnull();
887 }
888 } else {
889 my_error(ER_BAD_FIELD_ERROR, MYF(0), "password_last_changed",
890 "mysql.user");
891 return true;
892 }
893 /* if we have a password supplied we update the expiration field */
894 if (m_table->s->fields > m_table_schema->password_expired_idx()) {
895 m_table->field[m_table_schema->password_expired_idx()]->store(
896 "N", 1, system_charset_info);
897 } else {
898 my_error(ER_BAD_FIELD_ERROR, MYF(0), "password_expired", "mysql.user");
899 return true;
900 }
901 }
902 return false;
903 }
904
905 /**
906 Update global privileges for user
907 @param [out] return_value To store updated global privileges
908
909 @returns Update status for global privileges
910 */
update_privileges(Acl_table_user_writer_status & return_value)911 bool Acl_table_user_writer::update_privileges(
912 Acl_table_user_writer_status &return_value) {
913 if (m_what_to_update.m_what & ACCESS_RIGHTS_ATTR) {
914 /* Update table columns with new privileges */
915 char what = m_revoke_grant ? 'N' : 'Y';
916 Field **tmp_field;
917 ulong priv;
918 for (tmp_field = m_table->field + 2, priv = SELECT_ACL;
919 *tmp_field && (*tmp_field)->real_type() == MYSQL_TYPE_ENUM &&
920 ((Field_enum *)(*tmp_field))->typelib->count == 2;
921 tmp_field++, priv <<= 1) {
922 if (priv & m_rights) {
923 // set requested privileges
924 (*tmp_field)->store(&what, 1, &my_charset_latin1);
925 DBUG_PRINT("info",
926 ("Updating field %lu with privilege %c",
927 (ulong)(m_table->field + 2 - tmp_field), (char)what));
928 }
929 }
930 if (m_table->s->fields > m_table_schema->create_role_priv_idx()) {
931 if (CREATE_ROLE_ACL & m_rights) {
932 m_table->field[m_table_schema->create_role_priv_idx()]->store(
933 &what, 1, &my_charset_latin1);
934 }
935
936 if (DROP_ROLE_ACL & m_rights) {
937 m_table->field[m_table_schema->drop_role_priv_idx()]->store(
938 &what, 1, &my_charset_latin1);
939 }
940 }
941 }
942
943 return_value.updated_rights = get_user_privileges();
944 DBUG_PRINT("info",
945 ("Privileges on disk are now %lu", return_value.updated_rights));
946 DBUG_PRINT("info", ("table fields: %d", m_table->s->fields));
947
948 return false;
949 }
950
951 /**
952 Update SSL properties
953
954 @returns Update status
955 @retval false Success
956 @retval true Table is not in expected format
957 */
update_ssl_properties()958 bool Acl_table_user_writer::update_ssl_properties() {
959 if (m_what_to_update.m_what & SSL_ATTR) {
960 LEX *lex = m_thd->lex;
961 if (m_table->s->fields >= m_table_schema->x509_subject_idx()) {
962 switch (lex->ssl_type) {
963 case SSL_TYPE_ANY: {
964 m_table->field[m_table_schema->ssl_type_idx()]->store(
965 STRING_WITH_LEN("ANY"), &my_charset_latin1);
966 m_table->field[m_table_schema->ssl_cipher_idx()]->store(
967 "", 0, &my_charset_latin1);
968 m_table->field[m_table_schema->x509_issuer_idx()]->store(
969 "", 0, &my_charset_latin1);
970 m_table->field[m_table_schema->x509_subject_idx()]->store(
971 "", 0, &my_charset_latin1);
972 break;
973 }
974 case SSL_TYPE_X509: {
975 m_table->field[m_table_schema->ssl_type_idx()]->store(
976 STRING_WITH_LEN("X509"), &my_charset_latin1);
977 m_table->field[m_table_schema->ssl_cipher_idx()]->store(
978 "", 0, &my_charset_latin1);
979 m_table->field[m_table_schema->x509_issuer_idx()]->store(
980 "", 0, &my_charset_latin1);
981 m_table->field[m_table_schema->x509_subject_idx()]->store(
982 "", 0, &my_charset_latin1);
983 break;
984 }
985 case SSL_TYPE_SPECIFIED: {
986 m_table->field[m_table_schema->ssl_type_idx()]->store(
987 STRING_WITH_LEN("SPECIFIED"), &my_charset_latin1);
988 m_table->field[m_table_schema->ssl_cipher_idx()]->store(
989 "", 0, &my_charset_latin1);
990 m_table->field[m_table_schema->x509_issuer_idx()]->store(
991 "", 0, &my_charset_latin1);
992 m_table->field[m_table_schema->x509_subject_idx()]->store(
993 "", 0, &my_charset_latin1);
994 if (lex->ssl_cipher)
995 m_table->field[m_table_schema->ssl_cipher_idx()]->store(
996 lex->ssl_cipher, strlen(lex->ssl_cipher), system_charset_info);
997 if (lex->x509_issuer)
998 m_table->field[m_table_schema->x509_issuer_idx()]->store(
999 lex->x509_issuer, strlen(lex->x509_issuer),
1000 system_charset_info);
1001 if (lex->x509_subject)
1002 m_table->field[m_table_schema->x509_subject_idx()]->store(
1003 lex->x509_subject, strlen(lex->x509_subject),
1004 system_charset_info);
1005 break;
1006 }
1007 case SSL_TYPE_NOT_SPECIFIED:
1008 break;
1009 case SSL_TYPE_NONE: {
1010 m_table->field[m_table_schema->ssl_type_idx()]->store(
1011 "", 0, &my_charset_latin1);
1012 m_table->field[m_table_schema->ssl_cipher_idx()]->store(
1013 "", 0, &my_charset_latin1);
1014 m_table->field[m_table_schema->x509_issuer_idx()]->store(
1015 "", 0, &my_charset_latin1);
1016 m_table->field[m_table_schema->x509_subject_idx()]->store(
1017 "", 0, &my_charset_latin1);
1018 break;
1019 default:
1020 return true;
1021 }
1022 }
1023 } else {
1024 return true;
1025 }
1026 }
1027 return false;
1028 }
1029
1030 /**
1031 Update user resource restrictions
1032
1033 @returns status of the operation
1034 */
update_user_resources()1035 bool Acl_table_user_writer::update_user_resources() {
1036 if (m_what_to_update.m_what & RESOURCE_ATTR) {
1037 USER_RESOURCES mqh = m_thd->lex->mqh;
1038 if (mqh.specified_limits & USER_RESOURCES::QUERIES_PER_HOUR)
1039 m_table->field[m_table_schema->max_questions_idx()]->store(
1040 (longlong)mqh.questions, true);
1041 if (mqh.specified_limits & USER_RESOURCES::UPDATES_PER_HOUR)
1042 m_table->field[m_table_schema->max_updates_idx()]->store(
1043 (longlong)mqh.updates, true);
1044 if (mqh.specified_limits & USER_RESOURCES::CONNECTIONS_PER_HOUR)
1045 m_table->field[m_table_schema->max_connections_idx()]->store(
1046 (longlong)mqh.conn_per_hour, true);
1047 if (m_table->s->fields >= 36 &&
1048 (mqh.specified_limits & USER_RESOURCES::USER_CONNECTIONS))
1049 m_table->field[m_table_schema->max_user_connections_idx()]->store(
1050 (longlong)mqh.user_conn, true);
1051 }
1052 mqh_used = mqh_used || m_thd->lex->mqh.questions || m_thd->lex->mqh.updates ||
1053 m_thd->lex->mqh.conn_per_hour;
1054 return false;
1055 }
1056
1057 /**
1058 Update password expiration info
1059
1060 Raises error in DA if mysql.user table does not have password_expired
1061 column.
1062
1063 @returns status of operation
1064 @retval false Success
1065 @retval true Table is not in expected format
1066 */
update_password_expiry()1067 bool Acl_table_user_writer::update_password_expiry() {
1068 if (m_what_to_update.m_what & PASSWORD_EXPIRE_ATTR) {
1069 /*
1070 ALTER/CREATE USER <user> PASSWORD EXPIRE (or)
1071 ALTER USER <user> IDENTIFIED WITH plugin
1072 */
1073 if (m_combo->alter_status.update_password_expired_column) {
1074 if (m_table->s->fields > m_table_schema->password_expired_idx()) {
1075 m_table->field[m_table_schema->password_expired_idx()]->store(
1076 "Y", 1, system_charset_info);
1077 } else {
1078 my_error(ER_BAD_FIELD_ERROR, MYF(0), "password_expired", "mysql.user");
1079 return true;
1080 }
1081 }
1082 /*
1083 If password_expired column is not to be updated and only
1084 password_lifetime is to be updated
1085 */
1086 if (m_table->s->fields > m_table_schema->password_lifetime_idx() &&
1087 !m_combo->alter_status.update_password_expired_column) {
1088 if (!m_combo->alter_status.use_default_password_lifetime) {
1089 m_table->field[m_table_schema->password_lifetime_idx()]->store(
1090 (longlong)m_combo->alter_status.expire_after_days, true);
1091 m_table->field[m_table_schema->password_lifetime_idx()]->set_notnull();
1092 } else
1093 m_table->field[m_table_schema->password_lifetime_idx()]->set_null();
1094 }
1095 }
1096 return false;
1097 }
1098
1099 /**
1100 Update account locking info
1101
1102 Raises error in DA if mysql.user table does not have account_locked
1103 column.
1104 @returns status of the operation
1105 @retval false Success
1106 @retval true Table is not in expected format
1107 */
update_account_locking()1108 bool Acl_table_user_writer::update_account_locking() {
1109 if (m_what_to_update.m_what & ACCOUNT_LOCK_ATTR) {
1110 if (m_operation == Acl_table_operation::OP_INSERT ||
1111 (m_operation == Acl_table_operation::OP_UPDATE &&
1112 m_combo->alter_status.update_account_locked_column)) {
1113 if (m_table->s->fields > m_table_schema->account_locked_idx()) {
1114 /*
1115 Update the field for a new row and for the row that exists and the
1116 update was enforced (ACCOUNT [UNLOCK|LOCK]).
1117 */
1118 m_table->field[m_table_schema->account_locked_idx()]->store(
1119 m_combo->alter_status.account_locked ? "Y" : "N", 1,
1120 system_charset_info);
1121 } else {
1122 my_error(ER_BAD_FIELD_ERROR, MYF(0), "account_locked", "mysql.user");
1123 return true;
1124 }
1125 }
1126 }
1127 return false;
1128 }
1129
1130 /**
1131 Password history updates
1132
1133 Raises error in DA if mysql.user table does not have
1134 password_reuse_history column.
1135
1136 @returns status of the operation
1137 @retval false Success
1138 @retval true Table is not in expected format
1139 */
update_password_history()1140 bool Acl_table_user_writer::update_password_history() {
1141 if (m_combo->alter_status.update_password_history) {
1142 /* ALTER USER .. PASSWORD HISTORY */
1143 if (m_table->s->fields > m_table_schema->password_reuse_history_idx()) {
1144 Field *fld_history =
1145 m_table->field[m_table_schema->password_reuse_history_idx()];
1146 if (m_combo->alter_status.use_default_password_history)
1147 fld_history->set_null();
1148 else {
1149 fld_history->store(m_combo->alter_status.password_history_length);
1150 fld_history->set_notnull();
1151 }
1152 } else {
1153 my_error(ER_BAD_FIELD_ERROR, MYF(0), "password_reuse_history",
1154 "mysql.user");
1155 return true;
1156 }
1157 }
1158 return false;
1159 }
1160
1161 /**
1162 Password reuse time updates
1163
1164 Raises error in DA if mysql.user table does not have
1165 password_reuse_time column.
1166
1167 @returns status of the operation
1168 @retval false Success
1169 @retval true Table is not in expected format
1170 */
update_password_reuse()1171 bool Acl_table_user_writer::update_password_reuse() {
1172 if (m_combo->alter_status.update_password_reuse_interval) {
1173 /* ALTER USER .. PASSWORD REUSE INTERVAL */
1174 if (m_table->s->fields > m_table_schema->password_reuse_time_idx()) {
1175 Field *fld = m_table->field[m_table_schema->password_reuse_time_idx()];
1176 if (m_combo->alter_status.use_default_password_reuse_interval)
1177 fld->set_null();
1178 else {
1179 fld->store(m_combo->alter_status.password_reuse_interval);
1180 fld->set_notnull();
1181 }
1182 } else {
1183 my_error(ER_BAD_FIELD_ERROR, MYF(0), "password_reuse_time", "mysql.user");
1184 return true;
1185 }
1186 }
1187 return false;
1188 }
1189
1190 /**
1191 Whether current password is required to update exisitng one
1192
1193 Raises error in DA if mysql.user table does not have
1194 password_require_current column.
1195
1196 @returns status of the operation
1197 @retval false Success
1198 @retval true Table is not in expected format
1199 */
update_password_require_current()1200 bool Acl_table_user_writer::update_password_require_current() {
1201 /* ALTER USER .. PASSWORD REQUIRE CURRENT */
1202 if (m_table->s->fields > m_table_schema->password_require_current_idx()) {
1203 Field *fld = m_table->field[m_table_schema->password_require_current_idx()];
1204 switch (m_combo->alter_status.update_password_require_current) {
1205 case Lex_acl_attrib_udyn::DEFAULT:
1206 fld->set_null();
1207 break;
1208 case Lex_acl_attrib_udyn::NO:
1209 fld->store("N", 1, system_charset_info);
1210 fld->set_notnull();
1211 break;
1212 case Lex_acl_attrib_udyn::YES:
1213 fld->store("Y", 1, system_charset_info);
1214 fld->set_notnull();
1215 break;
1216 case Lex_acl_attrib_udyn::UNCHANGED:
1217 if (m_operation == Acl_table_operation::OP_INSERT) fld->set_null();
1218 break;
1219 default:
1220 DBUG_ASSERT(false);
1221 }
1222 } else {
1223 my_error(ER_BAD_FIELD_ERROR, MYF(0), "password_require_current",
1224 "mysql.user");
1225 return true;
1226 }
1227 return false;
1228 }
1229
1230 /**
1231 User_attributes updates
1232
1233 Raises error in DA if mysql.user table does not have
1234 user_attributes column.
1235
1236 @returns status of the operation
1237 @retval false Success
1238 @retval true Table/Column is not in expected format
1239 */
update_user_attributes(std::string & current_password,Acl_table_user_writer_status & return_value)1240 bool Acl_table_user_writer::update_user_attributes(
1241 std::string ¤t_password, Acl_table_user_writer_status &return_value) {
1242 if (m_what_to_update.m_what & USER_ATTRIBUTES) {
1243 if (m_table->s->fields >= m_table_schema->user_attributes_idx()) {
1244 Auth_id auth_id(m_combo->user, m_combo->host);
1245 Acl_user_attributes user_attributes(
1246 m_thd->mem_root,
1247 !(m_what_to_update.m_user_attributes & USER_ATTRIBUTE_RESTRICTIONS),
1248 auth_id, m_restrictions);
1249 if (m_operation == Acl_table_operation::OP_UPDATE &&
1250 parse_user_attributes(m_thd, m_table, m_table_schema,
1251 user_attributes))
1252 return true;
1253
1254 /* Update additional password */
1255 if (m_what_to_update.m_user_attributes & USER_ATTRIBUTE_RETAIN_PASSWORD) {
1256 if (user_attributes.update_additional_password(current_password))
1257 return true;
1258 else {
1259 return_value.second_cred = current_password;
1260 }
1261 }
1262
1263 /* Remove additional password */
1264 if (m_what_to_update.m_user_attributes &
1265 USER_ATTRIBUTE_DISCARD_PASSWORD) {
1266 /* We don't care if element was present or not */
1267 user_attributes.discard_additional_password();
1268 return_value.second_cred = consts::empty_string;
1269 }
1270
1271 /* Update restrictions */
1272 if (m_what_to_update.m_user_attributes & USER_ATTRIBUTE_RESTRICTIONS) {
1273 user_attributes.update_restrictions(*m_restrictions);
1274 }
1275
1276 /*
1277 Update password lock or default to
1278 the current values if not specified
1279 */
1280 return_value.password_lock = user_attributes.get_password_lock();
1281 if (m_what_to_update.m_user_attributes &
1282 USER_ATTRIBUTE_FAILED_LOGIN_ATTEMPTS)
1283 return_value.password_lock.failed_login_attempts =
1284 this->m_combo->alter_status.failed_login_attempts;
1285 if (m_what_to_update.m_user_attributes &
1286 USER_ATTRIBUTE_PASSWORD_LOCK_TIME)
1287 return_value.password_lock.password_lock_time_days =
1288 m_combo->alter_status.password_lock_time;
1289 user_attributes.set_password_lock(return_value.password_lock);
1290
1291 /* Update the column */
1292 {
1293 Json_object out_json_object;
1294 user_attributes.serialize(out_json_object);
1295 Json_wrapper json_wrapper(&out_json_object);
1296 json_wrapper.set_alias();
1297 Field_json *json_field = down_cast<Field_json *>(
1298 m_table->field[m_table_schema->user_attributes_idx()]);
1299 if (json_field->store_json(&json_wrapper) != TYPE_OK) return true;
1300 m_table->field[m_table_schema->user_attributes_idx()]->set_notnull();
1301 }
1302 return_value.restrictions = user_attributes.get_restrictions();
1303 } else {
1304 my_error(ER_BAD_FIELD_ERROR, MYF(0), "user_attributes", "mysql.user");
1305 return true;
1306 }
1307 } else {
1308 if (m_operation == Acl_table_operation::OP_INSERT) {
1309 m_table->field[m_table_schema->user_attributes_idx()]->set_null();
1310 }
1311 }
1312 return false;
1313 }
1314
1315 /**
1316 Send the function for updating the user metadata JSON code
1317 to the table processor.
1318 @param update The function expression used for updating the JSON
1319
1320 */
replace_user_application_user_metadata(std::function<bool (TABLE * table)> const & update)1321 void Acl_table_user_writer::replace_user_application_user_metadata(
1322 std::function<bool(TABLE *table)> const &update) {
1323 m_user_application_user_metadata = update;
1324 m_has_user_application_user_metadata = true;
1325 }
1326
1327 /**
1328 Helper function for updating the user metadata JSON
1329 */
update_user_application_user_metadata()1330 bool Acl_table_user_writer::update_user_application_user_metadata() {
1331 if (m_has_user_application_user_metadata)
1332 return m_user_application_user_metadata(m_table);
1333 return false;
1334 }
1335
1336 /**
1337 Helper function to get global privileges from mysql.user table
1338
1339 @returns Bitmask representing global privileges granted to given account
1340 */
get_user_privileges()1341 ulong Acl_table_user_writer::get_user_privileges() {
1342 uint next_field;
1343 char *priv_str;
1344 ulong rights =
1345 get_access(m_table, m_table_schema->select_priv_idx(), &next_field);
1346 if (m_table->s->fields > m_table_schema->drop_role_priv_idx()) {
1347 priv_str =
1348 get_field(&global_acl_memory,
1349 m_table->field[m_table_schema->create_role_priv_idx()]);
1350 if (priv_str && (*priv_str == 'Y' || *priv_str == 'y')) {
1351 rights |= CREATE_ROLE_ACL;
1352 }
1353 priv_str = get_field(&global_acl_memory,
1354 m_table->field[m_table_schema->drop_role_priv_idx()]);
1355 if (priv_str && (*priv_str == 'Y' || *priv_str == 'y')) {
1356 rights |= DROP_ROLE_ACL;
1357 }
1358 }
1359 return rights;
1360 }
1361
1362 /**
1363 Get current password from mysql.user.authentication_string
1364
1365 @returns value from mysql.user.authentication_string
1366 */
get_current_credentials()1367 std::string Acl_table_user_writer::get_current_credentials() {
1368 const char *current_password =
1369 get_field(m_thd->mem_root,
1370 m_table->field[m_table_schema->authentication_string_idx()]);
1371 std::string retval(current_password ? current_password : "",
1372 current_password ? strlen(current_password) : 0);
1373 return retval;
1374 }
1375
1376 /**
1377 mysql.user table reader constructor.
1378
1379 @param [in] thd Handle to THD object. Must be non-null
1380 @param [in] table mysql.user table handle. Must be non-null
1381 */
Acl_table_user_reader(THD * thd,TABLE * table)1382 Acl_table_user_reader::Acl_table_user_reader(THD *thd, TABLE *table)
1383 : Acl_table(thd, table, acl_table::Acl_table_operation::OP_READ) {
1384 init_sql_alloc(PSI_NOT_INSTRUMENTED, &m_mem_root, ACL_ALLOC_BLOCK_SIZE, 0);
1385 m_restrictions = new Restrictions(&m_mem_root);
1386 }
1387
1388 /**
1389 Free resources before we destroy.
1390 */
~Acl_table_user_reader()1391 Acl_table_user_reader::~Acl_table_user_reader() {
1392 if (m_table_schema) delete m_table_schema;
1393 if (m_restrictions) delete m_restrictions;
1394 free_root(&m_mem_root, MYF(0));
1395 }
1396
1397 /**
1398 Finish mysql.user table read operation
1399
1400 @param [in] error Table operation error.
1401
1402 @returns OK status, always.
1403 */
finish_operation(Table_op_error_code & error)1404 Acl_table_op_status Acl_table_user_reader::finish_operation(
1405 Table_op_error_code &error) {
1406 // not used
1407 error = 0;
1408 return Acl_table_op_status::OP_OK;
1409 }
1410
1411 /**
1412 Make table ready to read
1413
1414 @param [out] is_old_db_layout To see if this is a case of running new
1415 binary with old data directory without
1416 running mysql_upgrade.
1417
1418 @returns status of initialization
1419 @retval false Success
1420 @retval true Error initializing table
1421 */
setup_table(bool & is_old_db_layout)1422 bool Acl_table_user_reader::setup_table(bool &is_old_db_layout) {
1423 DBUG_TRACE;
1424 m_iterator = init_table_iterator(m_thd, m_table, nullptr, false,
1425 /*ignore_not_found_rows=*/false);
1426 if (m_iterator == nullptr) return true;
1427 m_table->use_all_columns();
1428 clean_user_cache();
1429
1430 User_table_schema_factory user_table_schema_factory;
1431 /*
1432 We need to check whether we are working with old database layout. This
1433 might be the case for instance when we are running mysql_upgrade.
1434 */
1435 m_table_schema = user_table_schema_factory.get_user_table_schema(m_table);
1436 is_old_db_layout =
1437 user_table_schema_factory.is_old_user_table_schema(m_table);
1438
1439 return false;
1440 }
1441
1442 /**
1443 Scrub ACL_USER.
1444
1445 @param [out] user ACL_USER to be updated
1446 */
reset_acl_user(ACL_USER & user)1447 void Acl_table_user_reader::reset_acl_user(ACL_USER &user) {
1448 /*
1449 All accounts can authenticate per default. This will change when
1450 we add a new field to the user table.
1451
1452 Currently this flag is only set to false when authentication is
1453 attempted using an unknown user name.
1454 */
1455 user.can_authenticate = true;
1456
1457 /* Account is unlocked by default. */
1458 user.account_locked = false;
1459
1460 /*
1461 The authorization id isn't a part of the role-graph per default.
1462 This is true even if CREATE ROLE is used.
1463 */
1464 user.is_role = false;
1465 }
1466
1467 /**
1468 Get user and host information for the account.
1469
1470 @param [out] user ACL_USER structure
1471 */
read_account_name(ACL_USER & user)1472 void Acl_table_user_reader::read_account_name(ACL_USER &user) {
1473 bool check_no_resolve = specialflag & SPECIAL_NO_RESOLVE;
1474 user.host.update_hostname(
1475 get_field(&m_mem_root, m_table->field[m_table_schema->host_idx()]));
1476 user.user =
1477 get_field(&m_mem_root, m_table->field[m_table_schema->user_idx()]);
1478 if (check_no_resolve && hostname_requires_resolving(user.host.get_host()) &&
1479 strcmp(user.host.get_host(), "localhost") != 0) {
1480 LogErr(WARNING_LEVEL, ER_AUTHCACHE_USER_SKIPPED_NEEDS_RESOLVE,
1481 user.user ? user.user : "",
1482 user.host.get_host() ? user.host.get_host() : "");
1483 }
1484 user.sort = get_sort(2, user.host.get_host(), user.user);
1485 }
1486
1487 /**
1488 Read authentication string for the account. We do verification later.
1489
1490 @param [out] user ACL_USER structure
1491
1492 @returns Status of reading authentication data.
1493 @retval false Success
1494 @retval true Error reading the field. Skip user.
1495 */
read_authentication_string(ACL_USER & user)1496 bool Acl_table_user_reader::read_authentication_string(ACL_USER &user) {
1497 /* Read password from authentication_string field */
1498 if (m_table->s->fields > m_table_schema->authentication_string_idx()) {
1499 user.credentials[PRIMARY_CRED].m_auth_string.str =
1500 get_field(&m_mem_root,
1501 m_table->field[m_table_schema->authentication_string_idx()]);
1502 } else {
1503 LogErr(ERROR_LEVEL, ER_AUTHCACHE_USER_TABLE_DODGY);
1504 return true;
1505 }
1506 if (user.credentials[PRIMARY_CRED].m_auth_string.str) {
1507 user.credentials[PRIMARY_CRED].m_auth_string.length =
1508 strlen(user.credentials[PRIMARY_CRED].m_auth_string.str);
1509 } else {
1510 user.credentials[PRIMARY_CRED].m_auth_string = EMPTY_CSTR;
1511 }
1512
1513 return false;
1514 }
1515
1516 /**
1517 Get global privilege information
1518
1519 @param [out] user ACL_USER structure
1520 */
read_privileges(ACL_USER & user)1521 void Acl_table_user_reader::read_privileges(ACL_USER &user) {
1522 uint next_field;
1523 user.access =
1524 get_access(m_table, m_table_schema->select_priv_idx(), &next_field) &
1525 GLOBAL_ACLS;
1526 /*
1527 if it is pre 5.0.1 privilege table then map CREATE privilege on
1528 CREATE VIEW & SHOW VIEW privileges
1529 */
1530 if (m_table->s->fields <= m_table_schema->show_view_priv_idx() &&
1531 (user.access & CREATE_ACL))
1532 user.access |= (CREATE_VIEW_ACL | SHOW_VIEW_ACL);
1533
1534 /*
1535 if it is pre 5.0.2 privilege table then map CREATE/ALTER privilege on
1536 CREATE PROCEDURE & ALTER PROCEDURE privileges
1537 */
1538 if (m_table->s->fields <= m_table_schema->create_routine_priv_idx() &&
1539 (user.access & CREATE_ACL))
1540 user.access |= CREATE_PROC_ACL;
1541 if (m_table->s->fields <= m_table_schema->alter_routine_priv_idx() &&
1542 (user.access & ALTER_ACL))
1543 user.access |= ALTER_PROC_ACL;
1544
1545 /* pre 5.0.3 did not have CREATE_USER_ACL */
1546 if (m_table->s->fields <= m_table_schema->create_user_priv_idx() &&
1547 (user.access & GRANT_ACL))
1548 user.access |= CREATE_USER_ACL;
1549
1550 /*
1551 if it is pre 5.1.6 privilege table then map CREATE privilege on
1552 CREATE|ALTER|DROP|EXECUTE EVENT
1553 */
1554 if (m_table->s->fields <= m_table_schema->event_priv_idx() &&
1555 (user.access & SUPER_ACL))
1556 user.access |= EVENT_ACL;
1557
1558 /* if it is pre 5.1.6 privilege then map TRIGGER privilege on CREATE. */
1559 if (m_table->s->fields <= m_table_schema->trigger_priv_idx() &&
1560 (user.access & SUPER_ACL))
1561 user.access |= TRIGGER_ACL;
1562
1563 if (m_table->s->fields > m_table_schema->drop_role_priv_idx()) {
1564 char *priv = get_field(
1565 &m_mem_root, m_table->field[m_table_schema->create_role_priv_idx()]);
1566
1567 if (priv && (*priv == 'Y' || *priv == 'y')) {
1568 user.access |= CREATE_ROLE_ACL;
1569 }
1570
1571 priv = get_field(&m_mem_root,
1572 m_table->field[m_table_schema->drop_role_priv_idx()]);
1573
1574 if (priv && (*priv == 'Y' || *priv == 'y')) {
1575 user.access |= DROP_ROLE_ACL;
1576 }
1577 }
1578
1579 if (m_table->s->fields <= m_table_schema->grant_priv_idx()) {
1580 // Without grant
1581 if (user.access & CREATE_ACL)
1582 user.access |= REFERENCES_ACL | INDEX_ACL | ALTER_ACL;
1583 }
1584
1585 if (m_table->s->fields <= m_table_schema->ssl_type_idx()) {
1586 /* Convert old privileges */
1587 user.access |= LOCK_TABLES_ACL | CREATE_TMP_ACL | SHOW_DB_ACL;
1588 if (user.access & FILE_ACL) user.access |= REPL_CLIENT_ACL | REPL_SLAVE_ACL;
1589 if (user.access & PROCESS_ACL) user.access |= SUPER_ACL | EXECUTE_ACL;
1590 }
1591 }
1592
1593 /**
1594 Read SSL restrictions
1595
1596 @param [out] user ACL_USER structure
1597 */
read_ssl_fields(ACL_USER & user)1598 void Acl_table_user_reader::read_ssl_fields(ACL_USER &user) {
1599 /* Starting from 4.0.2 we have more fields */
1600 if (m_table->s->fields >= m_table_schema->x509_subject_idx()) {
1601 char *ssl_type =
1602 get_field(&m_mem_root, m_table->field[m_table_schema->ssl_type_idx()]);
1603 if (!ssl_type)
1604 user.ssl_type = SSL_TYPE_NONE;
1605 else if (!strcmp(ssl_type, "ANY"))
1606 user.ssl_type = SSL_TYPE_ANY;
1607 else if (!strcmp(ssl_type, "X509"))
1608 user.ssl_type = SSL_TYPE_X509;
1609 else /* !strcmp(ssl_type, "SPECIFIED") */
1610 user.ssl_type = SSL_TYPE_SPECIFIED;
1611
1612 user.ssl_cipher = get_field(
1613 &m_mem_root, m_table->field[m_table_schema->ssl_cipher_idx()]);
1614 user.x509_issuer = get_field(
1615 &m_mem_root, m_table->field[m_table_schema->x509_issuer_idx()]);
1616 user.x509_subject = get_field(
1617 &m_mem_root, m_table->field[m_table_schema->x509_subject_idx()]);
1618 } else {
1619 user.ssl_type = SSL_TYPE_NONE;
1620 }
1621 }
1622
1623 /**
1624 Read user resource restrictions
1625
1626 @param [out] user ACL_USER structure
1627 */
read_user_resources(ACL_USER & user)1628 void Acl_table_user_reader::read_user_resources(ACL_USER &user) {
1629 if (m_table->s->fields >= m_table_schema->max_user_connections_idx()) {
1630 char *ptr = get_field(&m_mem_root,
1631 m_table->field[m_table_schema->max_questions_idx()]);
1632 user.user_resource.questions = ptr ? atoi(ptr) : 0;
1633 ptr = get_field(&m_mem_root,
1634 m_table->field[m_table_schema->max_updates_idx()]);
1635 user.user_resource.updates = ptr ? atoi(ptr) : 0;
1636 ptr = get_field(&m_mem_root,
1637 m_table->field[m_table_schema->max_connections_idx()]);
1638 user.user_resource.conn_per_hour = ptr ? atoi(ptr) : 0;
1639 if (user.user_resource.questions || user.user_resource.updates ||
1640 user.user_resource.conn_per_hour)
1641 mqh_used = true;
1642
1643 if (m_table->s->fields > m_table_schema->max_user_connections_idx()) {
1644 /* Starting from 5.0.3 we have max_user_connections field */
1645 ptr =
1646 get_field(&m_mem_root,
1647 m_table->field[m_table_schema->max_user_connections_idx()]);
1648 user.user_resource.user_conn = ptr ? atoi(ptr) : 0;
1649 }
1650 }
1651 }
1652
1653 /**
1654 Read plugin information
1655
1656 If it is old layout read accordingly. Also, validate authentication string
1657 against expecte format for the plugin.
1658
1659 @param [out] user ACL_USER structure
1660 @param [out] super_users_with_empty_plugin User has SUPER privilege or
1661 not
1662 @param [in] is_old_db_layout We are reading from old table
1663
1664 @returns status of reading plugin information
1665 @retval false Success
1666 @retval true Error. Skip user.
1667 */
read_plugin_info(ACL_USER & user,bool & super_users_with_empty_plugin,bool & is_old_db_layout)1668 bool Acl_table_user_reader::read_plugin_info(
1669 ACL_USER &user, bool &super_users_with_empty_plugin,
1670 bool &is_old_db_layout) {
1671 if (m_table->s->fields >= m_table_schema->plugin_idx()) {
1672 /* We may have plugin & auth_String fields */
1673 const char *tmpstr =
1674 get_field(&m_mem_root, m_table->field[m_table_schema->plugin_idx()]);
1675 user.plugin.str = tmpstr ? tmpstr : "";
1676 user.plugin.length = strlen(user.plugin.str);
1677
1678 /*
1679 In case we are working with 5.6 db layout we need to make server
1680 aware of Password field and that the plugin column can be null.
1681 In case when plugin column is null we use native password plugin
1682 if we can.
1683 */
1684 if (is_old_db_layout && (user.plugin.length == 0 ||
1685 Cached_authentication_plugins::compare_plugin(
1686 PLUGIN_MYSQL_NATIVE_PASSWORD, user.plugin))) {
1687 char *password = get_field(
1688 &m_mem_root, m_table->field[m_table_schema->password_idx()]);
1689
1690 // We do not support pre 4.1 hashes
1691 plugin_ref native_plugin =
1692 g_cached_authentication_plugins->get_cached_plugin_ref(
1693 PLUGIN_MYSQL_NATIVE_PASSWORD);
1694 if (native_plugin) {
1695 uint password_len = password ? strlen(password) : 0;
1696 st_mysql_auth *auth = (st_mysql_auth *)plugin_decl(native_plugin)->info;
1697 if (auth->validate_authentication_string(password, password_len) == 0) {
1698 // auth_string takes precedence over password
1699 if (user.credentials[PRIMARY_CRED].m_auth_string.length == 0) {
1700 user.credentials[PRIMARY_CRED].m_auth_string.str = password;
1701 user.credentials[PRIMARY_CRED].m_auth_string.length = password_len;
1702 }
1703 if (user.plugin.length == 0) {
1704 user.plugin.str = Cached_authentication_plugins::get_plugin_name(
1705 PLUGIN_MYSQL_NATIVE_PASSWORD);
1706 user.plugin.length = strlen(user.plugin.str);
1707 }
1708 } else {
1709 if ((user.access & SUPER_ACL) && !super_users_with_empty_plugin &&
1710 (user.plugin.length == 0))
1711 super_users_with_empty_plugin = true;
1712
1713 LogErr(WARNING_LEVEL, ER_AUTHCACHE_USER_IGNORED_DEPRECATED_PASSWORD,
1714 user.user ? user.user : "",
1715 user.host.get_host() ? user.host.get_host() : "");
1716 return true;
1717 }
1718 }
1719 }
1720
1721 /*
1722 Check if the plugin string is blank or null.
1723 If it is, the user will be skipped.
1724 */
1725 if (user.plugin.length == 0) {
1726 if ((user.access & SUPER_ACL) && !super_users_with_empty_plugin)
1727 super_users_with_empty_plugin = true;
1728 LogErr(WARNING_LEVEL, ER_AUTHCACHE_USER_IGNORED_NEEDS_PLUGIN,
1729 user.user ? user.user : "",
1730 user.host.get_host() ? user.host.get_host() : "");
1731 return true;
1732 }
1733 /*
1734 By comparing the plugin with the built in plugins it is possible
1735 to optimize the string allocation and comparision.
1736 */
1737 optimize_plugin_compare_by_pointer(&user.plugin);
1738 }
1739 /* Validate the hash string. */
1740 plugin_ref plugin = nullptr;
1741 plugin =
1742 my_plugin_lock_by_name(nullptr, user.plugin, MYSQL_AUTHENTICATION_PLUGIN);
1743 if (plugin) {
1744 st_mysql_auth *auth = (st_mysql_auth *)plugin_decl(plugin)->info;
1745 if (auth->validate_authentication_string(
1746 const_cast<char *>(
1747 user.credentials[PRIMARY_CRED].m_auth_string.str),
1748 user.credentials[PRIMARY_CRED].m_auth_string.length)) {
1749 LogErr(WARNING_LEVEL, ER_AUTHCACHE_USER_IGNORED_INVALID_PASSWORD,
1750 user.user ? user.user : "",
1751 user.host.get_host() ? user.host.get_host() : "");
1752 plugin_unlock(nullptr, plugin);
1753 return true;
1754 }
1755 plugin_unlock(nullptr, plugin);
1756 }
1757 return false;
1758 }
1759
1760 /**
1761 Read password expiry field
1762
1763 @param [out] user ACL_USER structure
1764 @param [out] password_expired Whether password is expired or not
1765
1766 @returns Status of reading password expiry value
1767 @retval false Success
1768 @retval true Password expiry was set to TRUE for a plugin that does not
1769 support password expiration. Skip user.
1770 */
read_password_expiry(ACL_USER & user,bool & password_expired)1771 bool Acl_table_user_reader::read_password_expiry(ACL_USER &user,
1772 bool &password_expired) {
1773 if (m_table->s->fields > m_table_schema->password_expired_idx()) {
1774 char *tmpstr = get_field(
1775 &m_mem_root, m_table->field[m_table_schema->password_expired_idx()]);
1776 if (tmpstr && (*tmpstr == 'Y' || *tmpstr == 'y')) {
1777 user.password_expired = true;
1778
1779 if (!auth_plugin_supports_expiration(user.plugin.str)) {
1780 LogErr(WARNING_LEVEL, ER_AUTHCACHE_EXPIRED_PASSWORD_UNSUPPORTED,
1781 user.user ? user.user : "",
1782 user.host.get_host() ? user.host.get_host() : "");
1783 return true;
1784 }
1785 password_expired = true;
1786 }
1787 }
1788 return false;
1789 }
1790
1791 /**
1792 Determine if user account is locked
1793
1794 @param [out] user ACL_USER structure
1795 */
read_password_locked(ACL_USER & user)1796 void Acl_table_user_reader::read_password_locked(ACL_USER &user) {
1797 if (m_table->s->fields > m_table_schema->account_locked_idx()) {
1798 char *locked = get_field(
1799 &m_mem_root, m_table->field[m_table_schema->account_locked_idx()]);
1800
1801 if (locked && (*locked == 'Y' || *locked == 'y')) {
1802 user.account_locked = true;
1803 }
1804 }
1805 }
1806
1807 /**
1808 Get password change time
1809
1810 @param [out] user ACL_USER structure
1811 */
read_password_last_changed(ACL_USER & user)1812 void Acl_table_user_reader::read_password_last_changed(ACL_USER &user) {
1813 /*
1814 Initalize the values of timestamp and expire after day
1815 to error and true respectively.
1816 */
1817 user.password_last_changed.time_type = MYSQL_TIMESTAMP_ERROR;
1818
1819 if (m_table->s->fields > m_table_schema->password_last_changed_idx()) {
1820 if (!m_table->field[m_table_schema->password_last_changed_idx()]
1821 ->is_null()) {
1822 char *password_last_changed = get_field(
1823 &m_mem_root,
1824 m_table->field[m_table_schema->password_last_changed_idx()]);
1825
1826 if (password_last_changed &&
1827 memcmp(password_last_changed, INVALID_DATE, sizeof(INVALID_DATE))) {
1828 String str(password_last_changed, &my_charset_bin);
1829 str_to_time_with_warn(&str, &(user.password_last_changed));
1830 }
1831 }
1832 }
1833 }
1834
1835 /**
1836 Get password expiry policy infomration
1837
1838 @param [out] user ACL_USER structure
1839 */
read_password_lifetime(ACL_USER & user)1840 void Acl_table_user_reader::read_password_lifetime(ACL_USER &user) {
1841 user.use_default_password_lifetime = true;
1842 user.password_lifetime = 0;
1843 if (m_table->s->fields > m_table_schema->password_lifetime_idx()) {
1844 if (!m_table->field[m_table_schema->password_lifetime_idx()]->is_null()) {
1845 char *ptr = get_field(
1846 &m_mem_root, m_table->field[m_table_schema->password_lifetime_idx()]);
1847 user.password_lifetime = ptr ? atoi(ptr) : 0;
1848 user.use_default_password_lifetime = false;
1849 }
1850 }
1851 }
1852
1853 /**
1854 Get password history restriction
1855
1856 @param [out] user ACL_USER structure
1857 */
read_password_history_fields(ACL_USER & user)1858 void Acl_table_user_reader::read_password_history_fields(ACL_USER &user) {
1859 if (m_table->s->fields > m_table_schema->password_reuse_history_idx()) {
1860 if (m_table->field[m_table_schema->password_reuse_history_idx()]->is_null(
1861 0))
1862 user.use_default_password_history = true;
1863 else {
1864 char *ptr = get_field(
1865 &m_mem_root,
1866 m_table->field[m_table_schema->password_reuse_history_idx()]);
1867 /* ptr is NULL in case of DB NULL. Take the default in that case */
1868 user.password_history_length = ptr ? atoi(ptr) : 0;
1869 user.use_default_password_history = ptr == nullptr;
1870 }
1871 }
1872 }
1873
1874 /**
1875 Get password reuse time restriction
1876
1877 @param [out] user ACL_USER structure
1878 */
read_password_reuse_time_fields(ACL_USER & user)1879 void Acl_table_user_reader::read_password_reuse_time_fields(ACL_USER &user) {
1880 if (m_table->s->fields > m_table_schema->password_reuse_time_idx()) {
1881 if (m_table->field[m_table_schema->password_reuse_time_idx()]->is_null(0))
1882 user.use_default_password_reuse_interval = true;
1883 else {
1884 char *ptr =
1885 get_field(&m_mem_root,
1886 m_table->field[m_table_schema->password_reuse_time_idx()]);
1887 /* ptr is NULL in case of DB NULL. Take the default in that case */
1888 user.password_reuse_interval = ptr ? atoi(ptr) : 0;
1889 user.use_default_password_reuse_interval = ptr == nullptr;
1890 }
1891 }
1892 }
1893
1894 /**
1895 Get information about requiring current password while changing password
1896
1897 @param [out] user ACL_USER structure
1898 */
read_password_require_current(ACL_USER & user)1899 void Acl_table_user_reader::read_password_require_current(ACL_USER &user) {
1900 /* Read password_require_current field */
1901 if (m_table->s->fields > m_table_schema->password_require_current_idx()) {
1902 char *value = get_field(
1903 &m_mem_root,
1904 m_table->field[m_table_schema->password_require_current_idx()]);
1905 if (value == nullptr)
1906 user.password_require_current = Lex_acl_attrib_udyn::DEFAULT;
1907 else if (value[0] == 'Y')
1908 user.password_require_current = Lex_acl_attrib_udyn::YES;
1909 else if (value[0] == 'N')
1910 user.password_require_current = Lex_acl_attrib_udyn::NO;
1911 }
1912 }
1913
1914 /**
1915 Read user attributes
1916
1917 @param [out] user ACL_USER structure
1918
1919 @returns status of reading user_attributes column
1920 @retval false Success
1921 @retval true Error reading user_attributes column
1922 */
read_user_attributes(ACL_USER & user)1923 bool Acl_table_user_reader::read_user_attributes(ACL_USER &user) {
1924 /* Read user_attributes field */
1925 if (m_table->s->fields > m_table_schema->user_attributes_idx()) {
1926 Auth_id auth_id(user.user ? user.user : "", user.get_username_length(),
1927 user.host.get_host() ? user.host.get_host() : "",
1928 user.host.get_host() ? strlen(user.host.get_host()) : 0);
1929 Acl_user_attributes user_attributes(&m_mem_root, true, auth_id,
1930 user.access);
1931 if (!m_table->field[m_table_schema->user_attributes_idx()]->is_null()) {
1932 if (parse_user_attributes(m_thd, m_table, m_table_schema,
1933 user_attributes)) {
1934 LogErr(WARNING_LEVEL, ER_WARNING_AUTHCACHE_INVALID_USER_ATTRIBUTES,
1935 user.user ? user.user : "",
1936 user.host.get_host() ? user.host.get_host() : "");
1937 return true;
1938 }
1939
1940 // 1. Read additional password
1941 std::string additional_password =
1942 user_attributes.get_additional_password();
1943 if (additional_password.length()) {
1944 user.credentials[SECOND_CRED].m_auth_string.length =
1945 additional_password.length();
1946 char *auth_string = static_cast<char *>(m_mem_root.Alloc(
1947 user.credentials[SECOND_CRED].m_auth_string.length + 1));
1948 memcpy(auth_string, additional_password.c_str(),
1949 user.credentials[SECOND_CRED].m_auth_string.length);
1950 auth_string[user.credentials[SECOND_CRED].m_auth_string.length] = 0;
1951 user.credentials[SECOND_CRED].m_auth_string.str = auth_string;
1952 } else {
1953 user.credentials[SECOND_CRED].m_auth_string = EMPTY_CSTR;
1954 }
1955
1956 /* Validate the hash string. */
1957 plugin_ref plugin = nullptr;
1958 plugin = my_plugin_lock_by_name(nullptr, user.plugin,
1959 MYSQL_AUTHENTICATION_PLUGIN);
1960 if (plugin) {
1961 st_mysql_auth *auth = (st_mysql_auth *)plugin_decl(plugin)->info;
1962 if (auth->validate_authentication_string(
1963 const_cast<char *>(
1964 user.credentials[SECOND_CRED].m_auth_string.str),
1965 user.credentials[SECOND_CRED].m_auth_string.length)) {
1966 LogErr(WARNING_LEVEL, ER_AUTHCACHE_USER_IGNORED_INVALID_PASSWORD,
1967 user.user ? user.user : "",
1968 user.host.get_host() ? user.host.get_host() : "");
1969 plugin_unlock(nullptr, plugin);
1970 return true;
1971 }
1972 plugin_unlock(nullptr, plugin);
1973 }
1974 } else {
1975 // user_attributes column is NULL. So use suitable defaults.
1976 user.credentials[SECOND_CRED].m_auth_string = EMPTY_CSTR;
1977 }
1978 *m_restrictions = user_attributes.get_restrictions();
1979 user.password_locked_state.set_parameters(
1980 user_attributes.get_password_lock_time_days(),
1981 user_attributes.get_failed_login_attempts());
1982 } else {
1983 user.credentials[SECOND_CRED].m_auth_string = EMPTY_CSTR;
1984 }
1985 return false;
1986 }
1987
1988 /**
1989 Add a recently read row in acl_users
1990
1991 @param [in] user ACL_USER structure
1992 */
add_row_to_acl_users(ACL_USER & user)1993 void Acl_table_user_reader::add_row_to_acl_users(ACL_USER &user) {
1994 LEX_CSTRING auth = {user.credentials[PRIMARY_CRED].m_auth_string.str,
1995 user.credentials[PRIMARY_CRED].m_auth_string.length};
1996 LEX_CSTRING second_auth = {
1997 user.credentials[SECOND_CRED].m_auth_string.str,
1998 user.credentials[SECOND_CRED].m_auth_string.length};
1999 LEX_ALTER password_life;
2000 password_life.update_password_expired_column = user.password_expired;
2001 password_life.expire_after_days = user.password_lifetime;
2002 password_life.use_default_password_lifetime =
2003 user.use_default_password_lifetime;
2004 password_life.account_locked = user.account_locked;
2005 password_life.use_default_password_history =
2006 user.password_history_length ? true : false;
2007 password_life.password_history_length =
2008 user.use_default_password_history ? 0 : user.password_history_length;
2009 password_life.use_default_password_history =
2010 user.use_default_password_history;
2011 password_life.use_default_password_reuse_interval =
2012 user.password_reuse_interval ? true : false;
2013 password_life.password_reuse_interval =
2014 user.use_default_password_reuse_interval ? 0
2015 : user.password_reuse_interval;
2016 password_life.use_default_password_reuse_interval =
2017 user.use_default_password_reuse_interval;
2018 password_life.update_password_require_current = user.password_require_current;
2019 password_life.failed_login_attempts =
2020 user.password_locked_state.get_failed_login_attempts();
2021 password_life.update_failed_login_attempts = true;
2022 password_life.password_lock_time =
2023 user.password_locked_state.get_password_lock_time_days();
2024 password_life.update_password_lock_time = true;
2025 acl_users_add_one(user.user, user.host.get_host(), user.ssl_type,
2026 user.ssl_cipher, user.x509_issuer, user.x509_subject,
2027 &user.user_resource, user.access, user.plugin, auth,
2028 second_auth, user.password_last_changed, password_life,
2029 false, *m_restrictions, password_life.failed_login_attempts,
2030 password_life.password_lock_time, m_thd);
2031 }
2032
2033 /**
2034 Read a row from mysql.user table and add it to in-memory structure
2035
2036 @param [in] is_old_db_layout mysql.user table is in old
2037 format
2038 @param [in] super_users_with_empty_plugin User has SUPER privilege
2039
2040 @returns Status of reading a row
2041 @retval false Success
2042 @retval true Error reading the row. Unless critical, keep reading
2043 further.
2044 */
read_row(bool & is_old_db_layout,bool & super_users_with_empty_plugin)2045 bool Acl_table_user_reader::read_row(bool &is_old_db_layout,
2046 bool &super_users_with_empty_plugin) {
2047 bool password_expired = false;
2048 DBUG_TRACE;
2049 /* Reading record from mysql.user */
2050 ACL_USER user;
2051 reset_acl_user(user);
2052 read_account_name(user);
2053 if (read_authentication_string(user)) return true;
2054 read_privileges(user);
2055 read_ssl_fields(user);
2056 read_user_resources(user);
2057 if (read_plugin_info(user, super_users_with_empty_plugin, is_old_db_layout))
2058 return false;
2059 read_password_expiry(user, password_expired);
2060 read_password_locked(user);
2061 read_password_last_changed(user);
2062 read_password_lifetime(user);
2063 read_password_history_fields(user);
2064 read_password_reuse_time_fields(user);
2065 read_password_require_current(user);
2066 if (read_user_attributes(user)) return false;
2067
2068 set_user_salt(&user);
2069 user.password_expired = password_expired;
2070
2071 add_row_to_acl_users(user);
2072
2073 return false;
2074 }
2075
2076 /**
2077 Driver function for mysql.user reader
2078
2079 Reads rows from table. If a row is valid, adds corresponding information
2080 to in-memory structure.
2081
2082 @returns status of reading mysql.user table
2083 @retval false Success
2084 @retval true Error reading the table. Probably corrupt.
2085 */
driver()2086 bool Acl_table_user_reader::driver() {
2087 DBUG_TRACE;
2088 bool is_old_db_layout;
2089 bool super_users_with_empty_plugin = false;
2090 if (setup_table(is_old_db_layout)) return true;
2091 allow_all_hosts = false;
2092 int read_rec_errcode;
2093 while (!(read_rec_errcode = m_iterator->Read())) {
2094 if (read_row(is_old_db_layout, super_users_with_empty_plugin)) return true;
2095 }
2096
2097 m_iterator.reset();
2098 if (read_rec_errcode > 0) return true;
2099 std::sort(acl_users->begin(), acl_users->end(), ACL_compare());
2100 acl_users->shrink_to_fit();
2101 rebuild_cached_acl_users_for_name();
2102
2103 if (super_users_with_empty_plugin) {
2104 LogErr(WARNING_LEVEL, ER_NO_SUPER_WITHOUT_USER_PLUGIN);
2105 }
2106
2107 return false;
2108 }
2109
Password_lock()2110 Password_lock::Password_lock()
2111 : password_lock_time_days(0), failed_login_attempts(0) {}
2112
operator =(const Password_lock & other)2113 Password_lock &Password_lock::operator=(const Password_lock &other) {
2114 if (this != &other) {
2115 password_lock_time_days = other.password_lock_time_days;
2116 failed_login_attempts = other.failed_login_attempts;
2117 }
2118 return *this;
2119 }
2120
operator =(Password_lock && other)2121 Password_lock &Password_lock::operator=(Password_lock &&other) {
2122 if (this != &other) {
2123 std::swap(password_lock_time_days, other.password_lock_time_days);
2124 std::swap(failed_login_attempts, other.failed_login_attempts);
2125 }
2126 return *this;
2127 }
2128
Password_lock(const Password_lock & other)2129 Password_lock::Password_lock(const Password_lock &other) {
2130 password_lock_time_days = other.password_lock_time_days;
2131 failed_login_attempts = other.failed_login_attempts;
2132 }
2133
Password_lock(Password_lock && other)2134 Password_lock::Password_lock(Password_lock &&other) {
2135 std::swap(password_lock_time_days, other.password_lock_time_days);
2136 std::swap(failed_login_attempts, other.failed_login_attempts);
2137 }
2138 } // namespace acl_table
2139
2140 /**
2141 Search and create/update a record for the user requested.
2142
2143 @param [in] thd The current thread.
2144 @param [in] table Pointer to a TABLE object of mysql.user table
2145 @param [in] combo User information
2146 @param [in] rights Rights requested
2147 @param [in] revoke_grant Set to true if a REVOKE command is executed
2148 @param [in] can_create_user Set true if it's allowed to create user
2149 @param [in] what_to_update Bitmap indicating which attributes need to be
2150 updated.
2151 @param [in] restrictions Restrictions handle if there is any
2152
2153 @return Operation result
2154 @retval 0 OK.
2155 @retval < 0 System error or storage engine error happen
2156 @retval > 0 Error in handling current user entry but still can continue
2157 processing subsequent user specified in the ACL statement.
2158 */
2159
replace_user_table(THD * thd,TABLE * table,LEX_USER * combo,ulong rights,bool revoke_grant,bool can_create_user,acl_table::Pod_user_what_to_update & what_to_update,Restrictions * restrictions)2160 int replace_user_table(THD *thd, TABLE *table, LEX_USER *combo, ulong rights,
2161 bool revoke_grant, bool can_create_user,
2162 acl_table::Pod_user_what_to_update &what_to_update,
2163 Restrictions *restrictions /*= nullptr*/) {
2164 acl_table::Acl_table_user_writer user_table(thd, table, combo, rights,
2165 revoke_grant, can_create_user,
2166 what_to_update, restrictions);
2167 LEX *lex = thd->lex;
2168 if (lex->alter_user_attribute !=
2169 enum_alter_user_attribute::ALTER_USER_COMMENT_NOT_USED) {
2170 std::string json_blob;
2171 json_blob.append(lex->alter_user_comment_text.str,
2172 lex->alter_user_comment_text.length);
2173
2174 user_table.replace_user_application_user_metadata([=](TABLE *table_inner) {
2175 if (replace_user_metadata(thd, json_blob,
2176 lex->alter_user_attribute ==
2177 enum_alter_user_attribute::
2178 ALTER_USER_COMMENT /* expect text */,
2179 table_inner)) {
2180 return true; // An error occurred and DA was set.
2181 // Stop transaction.
2182 }
2183 return false;
2184 });
2185 }
2186 acl_table::Acl_table_user_writer_status return_value(thd->mem_root);
2187
2188 DBUG_TRACE;
2189 DBUG_ASSERT(assert_acl_cache_write_lock(thd));
2190
2191 return_value = user_table.driver();
2192
2193 if (!(return_value.error || return_value.skip_cache_update)) {
2194 bool old_row_exists = (user_table.get_operation_mode() ==
2195 acl_table::Acl_table_operation::OP_UPDATE);
2196 bool builtin_plugin = auth_plugin_is_built_in(combo->plugin.str);
2197 bool update_password = (what_to_update.m_what & PLUGIN_ATTR);
2198
2199 /*
2200 Convert the time when the password was changed from timeval
2201 structure to MYSQL_TIME format, to store it in cache.
2202 */
2203 MYSQL_TIME password_change_time;
2204
2205 if (builtin_plugin && (update_password || !old_row_exists))
2206 thd->variables.time_zone->gmt_sec_to_TIME(
2207 &password_change_time,
2208 (my_time_t)return_value.password_change_timestamp.tv_sec);
2209 else
2210 password_change_time.time_type = MYSQL_TIMESTAMP_ERROR;
2211 clear_and_init_db_cache(); /* Clear privilege cache */
2212 if (old_row_exists) {
2213 acl_update_user(combo->user.str, combo->host.str, lex->ssl_type,
2214 lex->ssl_cipher, lex->x509_issuer, lex->x509_subject,
2215 &lex->mqh, return_value.updated_rights, combo->plugin,
2216 combo->auth, return_value.second_cred,
2217 password_change_time, combo->alter_status,
2218 return_value.restrictions, what_to_update,
2219 return_value.password_lock.failed_login_attempts,
2220 return_value.password_lock.password_lock_time_days);
2221 } else
2222 acl_insert_user(thd, combo->user.str, combo->host.str, lex->ssl_type,
2223 lex->ssl_cipher, lex->x509_issuer, lex->x509_subject,
2224 &lex->mqh, return_value.updated_rights, combo->plugin,
2225 combo->auth, password_change_time, combo->alter_status,
2226 return_value.restrictions,
2227 return_value.password_lock.failed_login_attempts,
2228 return_value.password_lock.password_lock_time_days);
2229 }
2230 return return_value.error;
2231 }
2232
2233 /**
2234 Read data from user table and fill in-memory caches
2235
2236 @param [in] thd THD handle
2237 @param [in] table mysql.user table handle
2238
2239 @returns status of reading data from table
2240 @retval true Error reading data. Don't trust it.
2241 @retval false All well.
2242 */
read_user_table(THD * thd,TABLE * table)2243 bool read_user_table(THD *thd, TABLE *table) {
2244 acl_table::Acl_table_user_reader acl_table_user_reader(thd, table);
2245 DBUG_TRACE;
2246
2247 if (acl_table_user_reader.driver()) return true;
2248
2249 return false;
2250 }
2251
2252 /**
2253 Replace or merge the user attributes of a given user. This function is called
2254 from Acl_table_user_writer::driver() but initialized in replace_user_table
2255 through a lambda expression. It's assumed that the user table has been
2256 opened and the matching row for the target user is in record[0]
2257
2258 @param thd The thread context
2259 @param json_blob Either a plain text comment or a JSON object depending on
2260 @param expect_text if expect_text is true then json_blob is plain text
2261 @param user_table A cursor to the open mysql.user table.
2262
2263 @note In case of failure this function sets the DA
2264
2265 @return false if the operation succeeded
2266 @retval false success
2267 @retval true failure
2268 */
replace_user_metadata(THD * thd,const std::string & json_blob,bool expect_text,TABLE * user_table)2269 bool replace_user_metadata(THD *thd, const std::string &json_blob,
2270 bool expect_text, TABLE *user_table) {
2271 DBUG_ASSERT(!thd->is_error());
2272 Json_dom_ptr json_dom;
2273 Json_wrapper json_wrapper;
2274 if (user_table->field[MYSQL_USER_FIELD_USER_ATTRIBUTES]->type() !=
2275 MYSQL_TYPE_JSON) {
2276 my_error(ER_INVALID_USER_ATTRIBUTE_JSON, MYF(0));
2277 return true;
2278 }
2279
2280 if (user_table->field[MYSQL_USER_FIELD_USER_ATTRIBUTES]->is_null()) {
2281 // If the field is a NULL value we create an empty json object.
2282 json_dom =
2283 create_dom_ptr<Json_object>(); // smart pointer will clean itself up.
2284 } else {
2285 // Get the current content of the field and varify that it's a valid JSON
2286 // object.
2287 if ((down_cast<Field_json *>(
2288 user_table->field[MYSQL_USER_FIELD_USER_ATTRIBUTES])
2289 ->val_json(&json_wrapper))) {
2290 // DA is already set
2291 return true;
2292 }
2293 if (json_wrapper.type() != enum_json_type::J_OBJECT) {
2294 // If it's not a valid JSON object the field is corrupt and we stop here.
2295 my_error(ER_INVALID_USER_ATTRIBUTE_JSON, MYF(0));
2296 return true;
2297 }
2298 json_dom = json_wrapper.clone_dom(thd);
2299 } // end else
2300 Json_object *json_ob = down_cast<Json_object *>(json_dom.get());
2301 Json_dom *metadata_dom =
2302 json_ob->get(acl_table::attribute_type_to_str
2303 [acl_table::User_attribute_type::METADATA]);
2304 if (!metadata_dom || metadata_dom->json_type() != enum_json_type::J_OBJECT) {
2305 json_ob->add_alias(acl_table::attribute_type_to_str
2306 [acl_table::User_attribute_type::METADATA],
2307 new (std::nothrow) Json_object());
2308 metadata_dom = json_ob->get(acl_table::attribute_type_to_str
2309 [acl_table::User_attribute_type::METADATA]);
2310 }
2311 Json_object *metadata = down_cast<Json_object *>(metadata_dom);
2312 Field_json *json_field = down_cast<Field_json *>(
2313 user_table->field[MYSQL_USER_FIELD_USER_ATTRIBUTES]);
2314 if (expect_text) {
2315 // ALTER USER x COMMENT y
2316 metadata->remove(acl_table::attribute_type_to_str
2317 [acl_table::User_attribute_type::COMMENT]);
2318 metadata->add_alias(acl_table::attribute_type_to_str
2319 [acl_table::User_attribute_type::COMMENT],
2320 new (std::nothrow) Json_string(json_blob));
2321 } else {
2322 // ALTER USER x ATTRIBUTE y
2323 const char *errmsg;
2324 size_t offset;
2325 auto metadata_patch = Json_dom::parse(json_blob.c_str(), json_blob.length(),
2326 &errmsg, &offset);
2327 if (metadata_patch == nullptr ||
2328 metadata_patch->json_type() != enum_json_type::J_OBJECT) {
2329 my_error(ER_INVALID_USER_ATTRIBUTE_JSON, MYF(0));
2330 return true;
2331 }
2332 Json_object_ptr patch_obj(
2333 down_cast<Json_object *>(metadata_patch.release()));
2334 if (metadata->cardinality() == 0)
2335 metadata->consume(std::move(patch_obj));
2336 else
2337 metadata->merge_patch(std::move(patch_obj));
2338 }
2339 Json_wrapper jw(json_dom.get(), true); // alias == don't take ownership
2340 json_field->set_notnull();
2341 if (json_field->store_json(&jw) != TYPE_OK) {
2342 my_error(ER_INVALID_USER_ATTRIBUTE_JSON, MYF(0));
2343 return true;
2344 }
2345
2346 return false;
2347 }
2348
2349 /**
2350 Helper function which heals with how JSON quoting rules change depending
2351 on the NO_BACKSLAH_ESCAPES sql mode.
2352 @param str The string which needs quoting
2353
2354 @sa read_user_application_user_metadata_from_table
2355
2356 */
double_the_backslash(String * str)2357 void double_the_backslash(String *str) {
2358 String escaped;
2359 str->print(&escaped);
2360 str->takeover(escaped);
2361 }
2362
2363 /**
2364 Helper function for recreating the CREATE USER statement when an SHOW CREATE
2365 USER statement is issued.
2366
2367 @param user The user name from which to read the metadata
2368 @param host The host name part of the user from which to read the metadata
2369 @param [out] metadata_str A buffer of text which will contain the CREATE USER
2370 .. ATTRIBUTE data. If the JSON object is null the metadata_str will be empty.
2371 @param table An open TABLE handle to the mysql.user table.
2372 @param mode_no_backslash_escapes The SQL_MODE determines how JSON is quoted
2373
2374 @sa mysql_show_create_user
2375
2376 @returns error state
2377 @retval false Success
2378 @retval true An error occurred and DA was set.
2379 */
read_user_application_user_metadata_from_table(const LEX_CSTRING user,const LEX_CSTRING host,String * metadata_str,TABLE * table,bool mode_no_backslash_escapes)2380 bool read_user_application_user_metadata_from_table(
2381 const LEX_CSTRING user, const LEX_CSTRING host, String *metadata_str,
2382 TABLE *table, bool mode_no_backslash_escapes) {
2383 MEM_ROOT tmp_mem;
2384 char null_token[5] = {'n', 'u', 'l', 'l', '\0'};
2385 uchar user_key[MAX_KEY_LENGTH];
2386 init_alloc_root(PSI_NOT_INSTRUMENTED, &tmp_mem, 256, 0);
2387 if (table->file->ha_index_init(0, false)) {
2388 my_error(ER_TABLE_CORRUPT, MYF(0), table->s->db.str,
2389 table->s->table_name.str);
2390 return true;
2391 }
2392 table->use_all_columns();
2393 table->field[MYSQL_USER_FIELD_HOST]->store(host.str, host.length,
2394 system_charset_info);
2395 table->field[MYSQL_USER_FIELD_USER]->store(user.str, user.length,
2396 system_charset_info);
2397 key_copy(user_key, table->record[0], table->key_info,
2398 table->key_info->key_length);
2399 if (table->file->ha_index_read_idx_map(table->record[0], 0, user_key,
2400 HA_WHOLE_KEY, HA_READ_KEY_EXACT)) {
2401 table->file->ha_index_end();
2402 return false; // technically we fail, but result should be an empty out
2403 // string
2404 }
2405 char *attributes_field =
2406 get_field(&tmp_mem, table->field[MYSQL_USER_FIELD_USER_ATTRIBUTES]);
2407 /*
2408 If the attribute field is empty we return empty string. An alternative is to
2409 return an empty JSON object, but we don't want to show the ATTRIBUTE field
2410 at all if there's no attribute.
2411 */
2412 if (strcmp(attributes_field, null_token) == 0 ||
2413 attributes_field == nullptr) {
2414 table->file->ha_index_end();
2415 return false;
2416 }
2417 const char *errmsg;
2418 size_t offset;
2419 auto attributes_dom = Json_dom::parse(
2420 attributes_field, strlen(attributes_field), &errmsg, &offset);
2421 table->file->ha_index_end();
2422 if (attributes_dom == nullptr ||
2423 attributes_dom->json_type() != enum_json_type::J_OBJECT) {
2424 my_error(ER_INVALID_USER_ATTRIBUTE_JSON, MYF(0));
2425 return true; // fail and DA is set
2426 }
2427 Json_object *json_ob = down_cast<Json_object *>(attributes_dom.get());
2428 Json_dom *metadata_dom =
2429 json_ob->get(acl_table::attribute_type_to_str
2430 [acl_table::User_attribute_type::METADATA]);
2431 if (metadata_dom == nullptr) return false; // success but out string is empty
2432 Json_wrapper wr(metadata_dom, true);
2433 wr.to_string(metadata_str, true, __FUNCTION__);
2434 if (!mode_no_backslash_escapes) double_the_backslash(metadata_str);
2435 return false;
2436 }
2437