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 &current_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