7424 lines
260 KiB
C++
7424 lines
260 KiB
C++
/* Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved.
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License, version 2.0,
|
|
as published by the Free Software Foundation.
|
|
|
|
This program is also distributed with certain software (including
|
|
but not limited to OpenSSL) that is licensed under separate terms,
|
|
as designated in a particular file or component or in included license
|
|
documentation. The authors of MySQL hereby grant you an additional
|
|
permission to link the program and your derivative works with the
|
|
separately licensed software that they have included with MySQL.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License, version 2.0, for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
|
|
|
|
#include "sql/auth/sql_authorization.h"
|
|
|
|
#include <limits.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <sys/types.h>
|
|
#include <algorithm>
|
|
#include <boost/concept/usage.hpp>
|
|
#include <boost/function.hpp>
|
|
#include <boost/graph/adjacency_iterator.hpp>
|
|
#include <boost/graph/adjacency_list.hpp>
|
|
#include <boost/graph/breadth_first_search.hpp>
|
|
#include <boost/graph/filtered_graph.hpp>
|
|
#include <boost/graph/graph_traits.hpp>
|
|
#include <boost/graph/graphml.hpp>
|
|
#include <boost/graph/named_function_params.hpp>
|
|
#include <boost/graph/properties.hpp>
|
|
#include <boost/iterator/iterator_facade.hpp>
|
|
#include <boost/move/utility_core.hpp>
|
|
#include <boost/property_map/dynamic_property_map.hpp>
|
|
#include <boost/property_map/property_map.hpp>
|
|
#include <boost/range/irange.hpp>
|
|
#include <boost/smart_ptr/make_shared_object.hpp>
|
|
#include <boost/smart_ptr/shared_ptr.hpp>
|
|
#include <boost/tuple/tuple.hpp>
|
|
#include <cstdlib>
|
|
#include <iterator>
|
|
#include <map>
|
|
#include <memory>
|
|
#include <set>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <tuple>
|
|
#include <type_traits>
|
|
#include <unordered_map>
|
|
#include <unordered_set>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "lex_string.h"
|
|
#include "m_ctype.h"
|
|
#include "m_string.h"
|
|
#include "map_helpers.h"
|
|
#include "mf_wcomp.h"
|
|
#include "my_alloc.h"
|
|
#include "my_compiler.h"
|
|
#include "my_dbug.h"
|
|
#include "my_inttypes.h"
|
|
#include "my_loglevel.h"
|
|
#include "my_macros.h"
|
|
#include "my_sqlcommand.h"
|
|
#include "my_sys.h"
|
|
#include "mysql/components/services/log_builtins.h"
|
|
#include "mysql/components/services/log_shared.h"
|
|
#include "mysql/mysql_lex_string.h"
|
|
#include "mysql/plugin_audit.h"
|
|
#include "mysql/psi/mysql_mutex.h"
|
|
#include "mysql/service_mysql_alloc.h"
|
|
#include "mysql_com.h"
|
|
#include "mysqld_error.h"
|
|
#include "prealloced_array.h"
|
|
#include "sql/auth/auth_acls.h"
|
|
#include "sql/auth/auth_common.h"
|
|
#include "sql/auth/auth_internal.h"
|
|
#include "sql/auth/auth_utility.h"
|
|
#include "sql/auth/dynamic_privilege_table.h"
|
|
#include "sql/auth/partial_revokes.h"
|
|
#include "sql/auth/role_tables.h"
|
|
#include "sql/auth/roles.h"
|
|
#include "sql/auth/sql_auth_cache.h"
|
|
#include "sql/auth/sql_security_ctx.h"
|
|
#include "sql/auth/sql_user_table.h"
|
|
#include "sql/current_thd.h"
|
|
#include "sql/dd/dd_table.h" // dd::table_exists
|
|
#include "sql/debug_sync.h"
|
|
#include "sql/derror.h" /* ER_THD */
|
|
#include "sql/error_handler.h" /* error_handler */
|
|
#include "sql/field.h"
|
|
#include "sql/handler.h"
|
|
#include "sql/item.h"
|
|
#include "sql/key_spec.h" /* Key_spec */
|
|
#include "sql/mdl.h"
|
|
#include "sql/mysqld.h" /* lower_case_table_names */
|
|
#include "sql/nested_join.h"
|
|
#include "sql/protocol.h"
|
|
#include "sql/sp.h" /* sp_exist_routines */
|
|
#include "sql/sql_admin.h" // enum role_enum
|
|
#include "sql/sql_alter.h"
|
|
#include "sql/sql_audit.h"
|
|
#include "sql/sql_base.h" /* open_and_lock_tables */
|
|
#include "sql/sql_class.h" /* THD */
|
|
#include "sql/sql_connect.h"
|
|
#include "sql/sql_error.h"
|
|
#include "sql/sql_lex.h"
|
|
#include "sql/sql_list.h"
|
|
#include "sql/sql_parse.h" /* get_current_user */
|
|
#include "sql/sql_rewrite.h" /* Grant_params */
|
|
#include "sql/sql_show.h" /* append_identifier */
|
|
#include "sql/sql_view.h" /* VIEW_ANY_ACL */
|
|
#include "sql/strfunc.h"
|
|
#include "sql/system_variables.h"
|
|
#include "sql/table.h"
|
|
#include "sql/thd_raii.h"
|
|
#include "sql_string.h"
|
|
#include "template_utils.h"
|
|
#include "thr_lock.h"
|
|
#include "violite.h"
|
|
|
|
/**
|
|
@file sql_authorization.cc
|
|
|
|
AUTHORIZATION CODE
|
|
|
|
*/
|
|
|
|
/**
|
|
@page AUTHORIZATION_PAGE Authorization IDs, roles and users
|
|
|
|
@section AUTHORIZATION_ID Authentication ID
|
|
@subsection AUTH_ID_DEFINITION Definition
|
|
Each row in the mysql.user table is identified by a user and host tuple. This
|
|
tuple is the authorization ID.
|
|
A client can authenticate with an authorization ID and a password. The ID is
|
|
then referred to as a user or user name.
|
|
|
|
@section AUTHORIZATION_PRIVILEGES Privileges ID
|
|
@subsection AUTH_PRIV_DEFINITION Definition
|
|
A privilege ID is a named token which can be granted to an authorization ID.
|
|
|
|
A privilege can either be effective or not effective. An effective privilege is
|
|
a privilege which used in a session to evaluate if a particular operation is
|
|
allowed or not. All effective privileges are granted or inherited but not all
|
|
privileges are effective.
|
|
|
|
@section AUTHORIZATION_ROLES Roles
|
|
@subsection AUTH_ROLES_DEFINITION Definition
|
|
A role is an authorization ID which can be granted to another authorization ID
|
|
by forming an directed edge between them in the role graph where every vertex
|
|
is a unique authorization ID. When the effective privilege is calculated, all
|
|
connected roles are visited according to their edge direction and their
|
|
corresponding granted privileges are aggregated.
|
|
|
|
@subsection ACTIVE_ROLE Active roles
|
|
A role can either be active or inactive. Active roles are kept in a thread
|
|
local list which exists solely for the lifetime of a client session. Granted
|
|
roles can be made active by
|
|
1) a SET ROLE statement,
|
|
2) after authentication if the role is a default role,
|
|
3) after authentication if the global variable
|
|
opt_always_activate_roles_on_login is set to true.
|
|
|
|
Example: To set the grated role ``team``\@``%`` as an active role, after
|
|
authentication, execute: SET ROLE team
|
|
|
|
@subsection DEFAULT_ROLE Default roles
|
|
Each authorization ID has a list of default roles. Default roles belonging to
|
|
an authorization ID are made into active roles after authentication iff they
|
|
are granted to this ID. If the list of default roles is empty then no roles are
|
|
made active after authentication unless the client sets a
|
|
SET ROLE statement.
|
|
|
|
@subsection MANDATORY_ROLE Mandatory roles
|
|
A mandatory role is an authorization ID which is implicitly granted to every
|
|
other authorization ID which has authenticated, regardless if this role has
|
|
been previously granted or not. Mandatory roles are specified in a global
|
|
variable. It's not required that the specified list maps to any existing
|
|
authorization ID but if there's no previous authorization ID then no mandatory
|
|
role can be granted. Mandatory roles are processed sequentially as any other
|
|
granted role when the effective privilege of an authorization ID needs to be
|
|
calculated iff they are active.
|
|
|
|
@section AUTHORIZATION_CACHE The effective privilege cache
|
|
@subsection OVERVIEW Overview
|
|
To avoid recalculating the effective privilege at every step the result is
|
|
saved into a cache (See Acl_cache ). The key to this cache is
|
|
formed by concatenating authorization ID, the active roles and the version ID
|
|
of the cache.
|
|
|
|
The cache is a lockless hash storage and each element is assembled using
|
|
read-only operations on the shared role graph.
|
|
@see get_privilege_access_maps
|
|
|
|
@section AUTHORIZATION_SHOW_GRANTS SHOW GRANTS
|
|
|
|
The statements @code SHOW GRANT @endcode shows all effective privileges using
|
|
the currently active roles for the current user.
|
|
|
|
The statement @code SHOW GRANT FOR x USING y @endcode is used for listing the
|
|
effective privilege for x given y as active roles. If If y isn't specified then
|
|
no roles are used. If x isn't specified then the current_user() is used.
|
|
Mandatory roles are always excluded from the list of granted roles when this
|
|
statement is used.
|
|
|
|
Example: To show the privilege for a role using no roles:
|
|
@code SHOW GRANTS FOR x. @endcode
|
|
|
|
SHOW-statements does not use the privilege cache and the effective privilege is
|
|
recalculated on every execution.
|
|
@see mysql_show_grants
|
|
|
|
To show the role graph use @code SELECT roles_graphml() @endcode
|
|
|
|
To investigate the role graph use the built in XML functions or the
|
|
mysql.role_edges table.
|
|
|
|
*/
|
|
|
|
namespace {
|
|
/**
|
|
Class to handle sanity checks for GRANT ... AS ... statement
|
|
*/
|
|
|
|
class Grant_validator {
|
|
public:
|
|
explicit Grant_validator(THD *thd, const char *db,
|
|
const List<LEX_USER> &user_list, ulong rights,
|
|
bool revoke,
|
|
const List<LEX_CSTRING> &dynamic_privilege,
|
|
bool grant_all, LEX_GRANT_AS *grant_as,
|
|
TABLE *dynamic_priv_table)
|
|
: m_thd(thd),
|
|
m_db(db),
|
|
m_user_list(user_list),
|
|
m_rights(rights),
|
|
m_revoke(revoke),
|
|
m_dynamic_privilege(dynamic_privilege),
|
|
m_grant_all(grant_all),
|
|
m_grant_as(grant_as),
|
|
m_dynamic_priv_table(dynamic_priv_table),
|
|
m_restore(false),
|
|
m_backup(nullptr) {}
|
|
~Grant_validator();
|
|
bool validate();
|
|
|
|
private:
|
|
bool mask_and_return_error();
|
|
bool validate_system_user_privileges();
|
|
bool validate_dynamic_privileges();
|
|
bool validate_and_process_grant_as();
|
|
|
|
private:
|
|
THD *m_thd;
|
|
const char *m_db;
|
|
const List<LEX_USER> &m_user_list;
|
|
ulong m_rights;
|
|
bool m_revoke;
|
|
const List<LEX_CSTRING> &m_dynamic_privilege;
|
|
bool m_grant_all;
|
|
LEX_GRANT_AS *m_grant_as;
|
|
TABLE *m_dynamic_priv_table;
|
|
bool m_restore;
|
|
Security_context *m_backup;
|
|
Security_context m_security_context;
|
|
};
|
|
|
|
/*
|
|
Destructor. Restores original security context.
|
|
*/
|
|
Grant_validator::~Grant_validator() {
|
|
if (m_restore)
|
|
m_thd->security_context()->restore_security_context(m_thd, m_backup);
|
|
}
|
|
|
|
/**
|
|
Helper function to mask specific error with generic one.
|
|
|
|
@returns true always.
|
|
*/
|
|
bool Grant_validator::mask_and_return_error() {
|
|
DBUG_TRACE;
|
|
/* Restore security context */
|
|
if (m_restore)
|
|
m_thd->security_context()->restore_security_context(m_thd, m_backup);
|
|
m_restore = false;
|
|
/*
|
|
Any error set before this point may potentially give away
|
|
information about user and/or role. So, clear any error
|
|
that may have been raised and replace it with a generic error.
|
|
*/
|
|
m_thd->get_stmt_da()->reset_diagnostics_area();
|
|
my_error(ER_UKNOWN_AUTH_ID_OR_ACCESS_DENIED_FOR_GRANT_AS, MYF(0));
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
Perform sanity checks for GRANT ... AS ...
|
|
|
|
@returns status of checks
|
|
@retval false Success. Security context may have been changed
|
|
@retval true Failure. Error has been raised.
|
|
*/
|
|
bool Grant_validator::validate_and_process_grant_as() {
|
|
DBUG_TRACE;
|
|
|
|
if (m_grant_as == nullptr || !m_grant_as->grant_as_used) return false;
|
|
|
|
LEX_USER *user = get_current_user(m_thd, m_grant_as->user);
|
|
if (user == nullptr) return mask_and_return_error();
|
|
|
|
/* Change security context */
|
|
if (m_security_context.change_security_context(m_thd, user->user, user->host,
|
|
nullptr, &m_backup, true))
|
|
return mask_and_return_error();
|
|
|
|
m_restore = true;
|
|
|
|
Roles::Role_activation role_activation(m_thd, m_thd->security_context(),
|
|
m_grant_as->role_type,
|
|
m_grant_as->role_list, false);
|
|
|
|
if (role_activation.activate()) return mask_and_return_error();
|
|
|
|
/* Compare restrictions */
|
|
Restrictions this_restrictions = m_thd->security_context()->restrictions();
|
|
Restrictions other_restrictions = m_backup->restrictions();
|
|
if (this_restrictions.has_more_db_restrictions(other_restrictions, m_rights))
|
|
return mask_and_return_error();
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
Validate that if grantee has SYSTEM_USER privileges, current user has it too.
|
|
|
|
@returns status of the check
|
|
@retval false Success
|
|
@retval true Current user lacks SYSTEM_USER privilege
|
|
*/
|
|
bool Grant_validator::validate_system_user_privileges() {
|
|
DBUG_TRACE;
|
|
if (check_system_user_privilege(m_thd, m_user_list)) return true;
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
Permission and sanity checks for dynamic privileges.
|
|
|
|
We check:
|
|
1. Dynamic privilege is granted at *.* level
|
|
2. Current user's ability to grant dynamic privilege
|
|
3. SYSTEM_USER is not granted to mandatory roles
|
|
|
|
@returns status of checks
|
|
@retval false Success
|
|
@retval true Error validating dynamic privileges
|
|
*/
|
|
bool Grant_validator::validate_dynamic_privileges() {
|
|
DBUG_TRACE;
|
|
|
|
/* Dynamic privileges are allowed only for global grants */
|
|
if (m_db && m_db != any_db && m_dynamic_privilege.elements > 0) {
|
|
String privs;
|
|
bool comma = false;
|
|
for (const LEX_CSTRING &priv : m_dynamic_privilege) {
|
|
if (comma) privs.append(",");
|
|
privs.append(priv.str, priv.length);
|
|
comma = true;
|
|
}
|
|
my_error(ER_ILLEGAL_PRIVILEGE_LEVEL, MYF(0), privs.c_ptr());
|
|
return true;
|
|
}
|
|
|
|
/* Sanity checks for dynamic privileges */
|
|
if (!m_db && (m_dynamic_privilege.elements > 0 || m_grant_all)) {
|
|
LEX_CSTRING *priv;
|
|
Update_dynamic_privilege_table update_table(m_thd, m_dynamic_priv_table);
|
|
List<LEX_CSTRING> *privileges_to_check;
|
|
if (m_grant_all) {
|
|
/*
|
|
Copy all currently available dynamic privileges to the list of
|
|
dynamic privileges to grant.
|
|
*/
|
|
privileges_to_check = new (m_thd->mem_root) List<LEX_CSTRING>;
|
|
iterate_all_dynamic_privileges(m_thd, [&](const char *str) {
|
|
LEX_CSTRING *new_str = (LEX_CSTRING *)m_thd->alloc(sizeof(LEX_CSTRING));
|
|
new_str->str = str;
|
|
new_str->length = strlen(str);
|
|
privileges_to_check->push_back(new_str);
|
|
return false;
|
|
});
|
|
} else
|
|
privileges_to_check =
|
|
&const_cast<List<LEX_CSTRING> &>(m_dynamic_privilege);
|
|
List_iterator<LEX_CSTRING> priv_it(*privileges_to_check);
|
|
bool error = false;
|
|
Security_context *sctx = m_thd->security_context();
|
|
while ((priv = priv_it++) && !error) {
|
|
/*
|
|
Privilege to grant dynamic privilege to others is granted if the user
|
|
either has super user privileges (currently UPDATE_ACL on mysql.*) or
|
|
if the user has a GRANT_OPTION on the specific dynamic privilege he
|
|
wants to grant.
|
|
Note that this is different than the rules which apply for other
|
|
privileges since for them the GRANT OPTION applies on a privilege
|
|
scope level (ie global, db or table level).
|
|
From a user POV it might appear confusing that some privileges are
|
|
more strictly associated with GRANT OPTION than others, but this
|
|
choice is made to preserve back compatibility while also paving way
|
|
for future improvements where all privileges objects have their own
|
|
grant option.
|
|
*/
|
|
if (check_access(m_thd, UPDATE_ACL, consts::mysql.c_str(), NULL, NULL, 1,
|
|
1) &&
|
|
!sctx->has_global_grant(priv->str, priv->length).second) {
|
|
my_error(ER_SPECIFIC_ACCESS_DENIED_ERROR, MYF(0), "GRANT OPTION");
|
|
return true;
|
|
}
|
|
|
|
if (!m_revoke) {
|
|
// Do not grant SYSTEM_USER privilege to a mandatory role
|
|
if (consts::system_user.compare(priv->str) == 0) {
|
|
std::vector<Role_id> mandatory_roles;
|
|
get_mandatory_roles(&mandatory_roles);
|
|
List_iterator<LEX_USER> str_list(
|
|
const_cast<List<LEX_USER> &>(m_user_list));
|
|
LEX_USER *user, *target_user;
|
|
while ((target_user = str_list++)) {
|
|
if (!(user = get_current_user(m_thd, target_user))) {
|
|
my_error(ER_OUTOFMEMORY, MYF(0), sizeof(LEX_USER));
|
|
return true;
|
|
}
|
|
Auth_id_ref auth_id = create_authid_from(user);
|
|
for (const auto &rid : mandatory_roles) {
|
|
if (rid == auth_id) {
|
|
my_error(ER_CANNOT_GRANT_SYSTEM_PRIV_TO_MANDATORY_ROLE, MYF(0),
|
|
auth_id.first.str, auth_id.second.str, priv->str);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
Umbrella method to perform validation
|
|
|
|
A possible side effect of this method is that active security context of the
|
|
session may have been changed. This is true if GRANT ... AS ... is used.
|
|
|
|
@returns status of checks
|
|
@retval false Success
|
|
@retval true Error found during validation
|
|
*/
|
|
bool Grant_validator::validate() {
|
|
DBUG_TRACE;
|
|
if (validate_system_user_privileges()) return true;
|
|
if (validate_dynamic_privileges()) return true;
|
|
|
|
/*
|
|
This must be the last check because it may change
|
|
the active security context of a thread.
|
|
*/
|
|
if (validate_and_process_grant_as()) return true;
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
The dynamic privilege is probed in the global map that keeps track of
|
|
dynamic privileges registered with server. The policy is that
|
|
- Plugin/Component may register a privilege ID
|
|
- Any privilege ID that exist in mysql.global_grants is a valid privilege ID
|
|
|
|
This method assumes that caller must have acquired the necessory ACL_LOCK.
|
|
|
|
@param [in] privilege Privilege to be checked in the dynamic privilege map
|
|
|
|
@return
|
|
@retval true Privilege is registered
|
|
@retval false Otherwise
|
|
*/
|
|
bool is_dynamic_privilege_registered(const std::string &privilege) {
|
|
if (get_dynamic_privilege_register()->find(privilege) !=
|
|
get_dynamic_privilege_register()->end()) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
} // namespace
|
|
|
|
Granted_roles_graph *g_granted_roles = 0;
|
|
Role_index_map *g_authid_to_vertex = 0;
|
|
static char g_active_dummy_user[] = "active dummy user";
|
|
extern bool initialized;
|
|
extern Default_roles *g_default_roles;
|
|
typedef boost::graph_traits<Granted_roles_graph>::adjacency_iterator
|
|
Role_adjacency_iterator;
|
|
User_to_dynamic_privileges_map *g_dynamic_privileges_map = 0;
|
|
const char *any_db = "*any*"; // Special symbol for check_access
|
|
|
|
static bool check_routine_level_acl(THD *thd, const char *db, const char *name,
|
|
bool is_proc);
|
|
void get_granted_roles(Role_vertex_descriptor &v,
|
|
List_of_granted_roles *granted_roles);
|
|
|
|
/**
|
|
This utility function is used by revoke_role() and remove_all_granted_roles()
|
|
for removing a specific edge from the role graph.
|
|
@param thd Thread handler
|
|
@param authid_role The role which should be revoked
|
|
@param authid_user The user who will get its role revoked
|
|
@param [out] user_vert The vertex descriptor of the user
|
|
@param [out] role_vert The vertex descriptor of the role
|
|
|
|
@return Success state
|
|
@retval true No such user
|
|
@retval false User was removed
|
|
*/
|
|
|
|
bool revoke_role_helper(THD *thd MY_ATTRIBUTE((unused)),
|
|
std::string &authid_role, std::string &authid_user,
|
|
Role_vertex_descriptor *user_vert,
|
|
Role_vertex_descriptor *role_vert) {
|
|
DBUG_TRACE;
|
|
DBUG_ASSERT(assert_acl_cache_write_lock(thd));
|
|
|
|
Role_index_map::iterator it = g_authid_to_vertex->find(authid_user);
|
|
if (it == g_authid_to_vertex->end()) {
|
|
// No such user
|
|
return true;
|
|
} else
|
|
*user_vert = it->second;
|
|
|
|
it = g_authid_to_vertex->find(authid_role);
|
|
if (it == g_authid_to_vertex->end()) {
|
|
// No such role
|
|
return true;
|
|
} else
|
|
*role_vert = it->second;
|
|
|
|
boost::remove_edge(*user_vert, *role_vert, *g_granted_roles);
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
This utility function checks for the connecting vertices of the role
|
|
descriptor(authid node) and updates the role flag of the corresponding
|
|
ACL user. If there are no incoming edges to this authid node then this
|
|
is not a role id anymore. It assumes that acl user and role descriptor
|
|
are, valid and passed correctly.
|
|
|
|
@param [in] role_vert The role vertex descriptor
|
|
@param [in,out] acl_user The acl role
|
|
|
|
*/
|
|
void static update_role_flag_of_acl_user(
|
|
const Role_vertex_descriptor &role_vert, ACL_USER *acl_user) {
|
|
degree_s_t count = boost::in_degree(role_vert, *g_granted_roles);
|
|
acl_user->is_role = (count > 0) ? true : false;
|
|
}
|
|
|
|
/**
|
|
Used by mysql_revoke_role() for revoking a specified role from a specified
|
|
user.
|
|
|
|
@param thd Thread handler
|
|
@param role The role which will be revoked
|
|
@param user The user who will get its role revoked
|
|
|
|
*/
|
|
void revoke_role(THD *thd, ACL_USER *role, ACL_USER *user) {
|
|
std::string authid_role = create_authid_str_from(role);
|
|
std::string authid_user = create_authid_str_from(user);
|
|
Role_vertex_descriptor user_vert;
|
|
Role_vertex_descriptor role_vert;
|
|
if (!revoke_role_helper(thd, authid_role, authid_user, &user_vert,
|
|
&role_vert)) {
|
|
update_role_flag_of_acl_user(role_vert, role);
|
|
}
|
|
}
|
|
|
|
/**
|
|
Since the gap in the vertex vector was removed all the vertex descriptors
|
|
has changed. As a consequence we now need to rebuild the authid_to_vertex
|
|
index.
|
|
*/
|
|
void rebuild_vertex_index(THD *thd MY_ATTRIBUTE((unused))) {
|
|
DBUG_ASSERT(assert_acl_cache_write_lock(thd));
|
|
for (auto &acl_user : *acl_users) {
|
|
create_role_vertex(&acl_user);
|
|
}
|
|
g_authid_to_vertex->clear();
|
|
boost::graph_traits<Granted_roles_graph>::vertex_iterator vert_it, vert_end;
|
|
boost::tie(vert_it, vert_end) = boost::vertices(*g_granted_roles);
|
|
for (; vert_it != vert_end; ++vert_it) {
|
|
ACL_USER acl_user =
|
|
boost::get(boost::vertex_acl_user_t(),
|
|
*g_granted_roles)[boost::vertex(*vert_it, *g_granted_roles)];
|
|
if (acl_user.user == g_active_dummy_user) {
|
|
(*g_authid_to_vertex)["root"] = *vert_it;
|
|
} else {
|
|
std::string authid = create_authid_str_from(&acl_user);
|
|
(*g_authid_to_vertex)[authid] = *vert_it;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool drop_role(THD *thd, TABLE *edge_table, TABLE *defaults_table,
|
|
const Auth_id_ref &authid_user) {
|
|
DBUG_TRACE;
|
|
bool error = false;
|
|
std::vector<ACL_USER> users;
|
|
DBUG_ASSERT(assert_acl_cache_write_lock(thd));
|
|
std::string authid_user_str = create_authid_str_from(authid_user);
|
|
Role_index_map::iterator it;
|
|
|
|
if ((it = g_authid_to_vertex->find(authid_user_str)) !=
|
|
g_authid_to_vertex->end()) {
|
|
/* Fetch source vertex details */
|
|
ACL_USER source_acl_user = boost::get(
|
|
boost::vertex_acl_user_t(),
|
|
*g_granted_roles)[boost::vertex(it->second, *g_granted_roles)];
|
|
Auth_id_ref source_user = create_authid_from(&source_acl_user);
|
|
|
|
/*
|
|
Lambda function that drops all adjacent edges(if exists) from the
|
|
source_user present in the role_edges table and, keep track of
|
|
target acl user.
|
|
It assumes all the paramaters and captures, are valid and sane.
|
|
*/
|
|
auto modify_role_edges = [&thd, &edge_table, &error,
|
|
&source_user](const ACL_USER &target_acl_user) {
|
|
Auth_id_ref target_user = create_authid_from(&target_acl_user);
|
|
error = modify_role_edges_in_table(thd, edge_table, source_user,
|
|
target_user, false, true);
|
|
error |= modify_role_edges_in_table(thd, edge_table, target_user,
|
|
source_user, false, true);
|
|
};
|
|
|
|
/* Fetch the neighboring vertices from the outgoing edges */
|
|
out_edge_itr_t oute_itr, oute_end;
|
|
boost::tie(oute_itr, oute_end) =
|
|
boost::out_edges(it->second, *g_granted_roles);
|
|
for (; oute_itr != oute_end; ++oute_itr) {
|
|
ACL_USER target_acl_user = boost::get(
|
|
boost::vertex_acl_user_t(),
|
|
*g_granted_roles)[boost::target(*oute_itr, *g_granted_roles)];
|
|
modify_role_edges(target_acl_user);
|
|
users.push_back(target_acl_user);
|
|
}
|
|
|
|
/* Fetch the neighboring vertices from the incoming edges */
|
|
in_edge_itr_t ine_itr, ine_end;
|
|
boost::tie(ine_itr, ine_end) =
|
|
boost::in_edges(it->second, *g_granted_roles);
|
|
for (; ine_itr != ine_end; ++ine_itr) {
|
|
ACL_USER target_acl_user = boost::get(
|
|
boost::vertex_acl_user_t(),
|
|
*g_granted_roles)[boost::source(*ine_itr, *g_granted_roles)];
|
|
modify_role_edges(target_acl_user);
|
|
}
|
|
|
|
/* Remove this vertex from the graph (along with its edges) */
|
|
DBUG_PRINT("info", ("Removing %s from graph and rebuild the index.",
|
|
authid_user_str.c_str()));
|
|
/*
|
|
We clear all edges connecting this vertex but we avoid removing it
|
|
from the graph at this time as it would invalidate the vertex
|
|
descriptors and we would have to rebuild all indexes. For now it is
|
|
enough to remove the index entry. As the roles are reloaded from the
|
|
tables the dropped roles will disappear.
|
|
*/
|
|
boost::clear_vertex(it->second, *g_granted_roles);
|
|
|
|
/*
|
|
If the role authid does not have any incoming edges then update
|
|
the role flag of corresponding ACL role.
|
|
*/
|
|
for (auto &&user_itr : users) {
|
|
Role_index_map::iterator role_it =
|
|
g_authid_to_vertex->find(create_authid_str_from(&user_itr));
|
|
if (role_it != g_authid_to_vertex->end()) {
|
|
ACL_USER *acl_role =
|
|
find_acl_user(user_itr.host.get_host(), user_itr.user, true);
|
|
DBUG_ASSERT(acl_role != nullptr);
|
|
update_role_flag_of_acl_user(role_it->second, acl_role);
|
|
}
|
|
}
|
|
}
|
|
// Remove all default role policies assigned to this authid.
|
|
clear_default_roles(thd, defaults_table, authid_user, 0);
|
|
// Remove all default role policies in which this authid is a default role.
|
|
std::vector<Default_roles::iterator> delete_policies;
|
|
for (auto policy = g_default_roles->begin(); policy != g_default_roles->end();
|
|
++policy) {
|
|
if (policy->second == authid_user) {
|
|
delete_policies.push_back(policy);
|
|
}
|
|
}
|
|
for (auto &&policy : delete_policies) {
|
|
modify_default_roles_in_table(thd, defaults_table,
|
|
create_authid_from(policy->first),
|
|
create_authid_from(policy->second), true);
|
|
g_default_roles->erase(policy);
|
|
}
|
|
return error;
|
|
}
|
|
|
|
/**
|
|
Used by @ref mysql_drop_user. Will drop all
|
|
@param thd
|
|
@param edge_table
|
|
@param defaults_table
|
|
@param user_name
|
|
|
|
@returns
|
|
@retval true An error occurred
|
|
@retval false Success
|
|
*/
|
|
bool revoke_all_roles_from_user(THD *thd, TABLE *edge_table,
|
|
TABLE *defaults_table, LEX_USER *user_name) {
|
|
List_of_granted_roles granted_roles;
|
|
get_granted_roles(user_name, &granted_roles);
|
|
Auth_id_ref user_name_authid = create_authid_from(user_name);
|
|
bool error = drop_role(thd, edge_table, defaults_table, user_name_authid);
|
|
return error;
|
|
}
|
|
|
|
/**
|
|
If possible, it will revoke all roles and default roles from user_from and
|
|
set them for user_to instead.
|
|
|
|
@param thd Thread handle
|
|
@param table A table handler
|
|
@param user_from The name of the ACL_USER which will be renamed.
|
|
@param [out] granted_roles A list of roles that were successfully revoked.
|
|
|
|
@return success state
|
|
@retval true En error occurred
|
|
@retval false Successful
|
|
*/
|
|
bool revoke_all_granted_roles(THD *thd, TABLE *table, LEX_USER *user_from,
|
|
List_of_granted_roles *granted_roles) {
|
|
DBUG_TRACE;
|
|
std::string authid_user = create_authid_str_from(user_from);
|
|
Role_index_map::iterator it;
|
|
if ((it = g_authid_to_vertex->find(authid_user)) ==
|
|
g_authid_to_vertex->end()) {
|
|
/* The user from wasn't in the role graph index; nothing to do. */
|
|
return true;
|
|
}
|
|
|
|
get_granted_roles(it->second, granted_roles);
|
|
Role_vertex_descriptor user_vert;
|
|
Role_vertex_descriptor role_vert;
|
|
bool errors = false;
|
|
for (auto &&ref : *granted_roles) {
|
|
std::string role_id_str;
|
|
ref.first.auth_str(&role_id_str);
|
|
std::string user_from_str = create_authid_str_from(user_from);
|
|
Auth_id_ref role_id = create_authid_from(ref.first);
|
|
errors = modify_role_edges_in_table(thd, table, role_id,
|
|
{user_from->user, user_from->host},
|
|
ref.second, true);
|
|
if (errors) break;
|
|
/*
|
|
If the role is revoked then update the flag in the
|
|
corresponding ACL authid.
|
|
*/
|
|
if (!revoke_role_helper(thd, role_id_str, user_from_str, &user_vert,
|
|
&role_vert)) {
|
|
ACL_USER *acl_role = find_acl_user(ref.first.host().c_str(),
|
|
ref.first.user().c_str(), ref.second);
|
|
DBUG_ASSERT(acl_role != nullptr);
|
|
update_role_flag_of_acl_user(role_vert, acl_role);
|
|
}
|
|
}
|
|
return errors;
|
|
}
|
|
|
|
bool is_role_id(LEX_USER *authid) {
|
|
ACL_USER *acl_user = find_acl_user(authid->host.str, authid->user.str, true);
|
|
if (acl_user == 0) return false;
|
|
return acl_user->is_role;
|
|
}
|
|
|
|
/**
|
|
Grants a single role to a single user. The change is made to the in-memory
|
|
roles graph and not persistent.
|
|
|
|
@see mysql_grant_role
|
|
|
|
@param role A pointer to the role to be granted
|
|
@param user A pointer to the user which will be granted
|
|
@param with_admin_opt True if the user should have the ability to pass on the
|
|
granted role to another authorization id.
|
|
|
|
@return
|
|
*/
|
|
void grant_role(ACL_USER *role, const ACL_USER *user, bool with_admin_opt) {
|
|
DBUG_TRACE;
|
|
bool is_added;
|
|
std::string authid_role = create_authid_str_from(role);
|
|
std::string authid_user = create_authid_str_from(user);
|
|
Role_vertex_descriptor user_vert, role_vert;
|
|
Role_index_map::iterator it;
|
|
|
|
if ((it = g_authid_to_vertex->find(authid_user)) ==
|
|
g_authid_to_vertex->end()) {
|
|
user_vert = boost::add_vertex(*g_granted_roles);
|
|
g_authid_to_vertex->insert(make_pair(authid_user, user_vert));
|
|
} else
|
|
user_vert = it->second;
|
|
|
|
if ((it = g_authid_to_vertex->find(authid_role)) ==
|
|
g_authid_to_vertex->end()) {
|
|
role_vert = boost::add_vertex(*g_granted_roles);
|
|
g_authid_to_vertex->insert(make_pair(authid_role, role_vert));
|
|
} else
|
|
role_vert = it->second;
|
|
|
|
boost::property_map<Granted_roles_graph, boost::vertex_name_t>::type
|
|
user_pname,
|
|
role_pname;
|
|
user_pname = boost::get(boost::vertex_name_t(), *g_granted_roles);
|
|
boost::put(user_pname, user_vert, authid_user);
|
|
role_pname = boost::get(boost::vertex_name_t(), *g_granted_roles);
|
|
boost::put(role_pname, role_vert, authid_role);
|
|
|
|
boost::property_map<Granted_roles_graph, boost::vertex_acl_user_t>::type
|
|
user_pacl_user,
|
|
role_pacl_user;
|
|
user_pacl_user = boost::get(boost::vertex_acl_user_t(), *g_granted_roles);
|
|
boost::put(user_pacl_user, user_vert, *user);
|
|
role_pacl_user = boost::get(boost::vertex_acl_user_t(), *g_granted_roles);
|
|
boost::put(role_pacl_user, role_vert, *role);
|
|
|
|
Role_edge_descriptor edge;
|
|
tie(edge, is_added) = add_edge(user_vert, role_vert, *g_granted_roles);
|
|
|
|
boost::property_map<Granted_roles_graph, boost::edge_capacity_t>::type
|
|
edge_colors;
|
|
edge_colors = boost::get(boost::edge_capacity_t(), *g_granted_roles);
|
|
boost::put(edge_colors, edge, (with_admin_opt ? 1 : 0));
|
|
role->is_role = true;
|
|
}
|
|
|
|
/**
|
|
Helper function for create_roles_vertices. Creates a vertex in the role
|
|
graph and associate it with an ACL_USER. If the ACL_USER already exists in
|
|
the vertex-to-acl-user index then we ignore this request.
|
|
|
|
@param role_acl_user The acial user to be mapped to a vertex.
|
|
*/
|
|
void create_role_vertex(ACL_USER *role_acl_user) {
|
|
Role_vertex_descriptor role_vertex;
|
|
Role_index_map::iterator it;
|
|
std::string key = create_authid_str_from(role_acl_user);
|
|
if ((it = g_authid_to_vertex->find(key)) == g_authid_to_vertex->end()) {
|
|
role_vertex = boost::add_vertex(*g_granted_roles);
|
|
boost::property_map<Granted_roles_graph, boost::vertex_acl_user_t>::type
|
|
root_prop;
|
|
root_prop = boost::get(boost::vertex_acl_user_t(), *g_granted_roles);
|
|
boost::put(root_prop, role_vertex, *role_acl_user);
|
|
boost::property_map<Granted_roles_graph, boost::vertex_name_t>::type
|
|
role_pname;
|
|
role_pname = boost::get(boost::vertex_name_t(), *g_granted_roles);
|
|
boost::put(role_pname, role_vertex, key);
|
|
g_authid_to_vertex->insert(std::make_pair(key, role_vertex));
|
|
}
|
|
}
|
|
|
|
/**
|
|
Renames a user in the mysql.role_edge and the mysql.default_roles
|
|
tables. user_to must already exist in the acl_user cache, but user_from
|
|
may not as long as it exist in the role graph.
|
|
|
|
@param thd Thread handler
|
|
@param edge_table An open table handle for mysql.edge_mysql
|
|
@param defaults_table An open table handle for mysql.default_roles
|
|
@param user_from The user to rename
|
|
@param user_to The target user name
|
|
|
|
@see mysql_rename_user
|
|
|
|
@return
|
|
@retval true An error occurred
|
|
@retval false Success
|
|
*/
|
|
|
|
bool roles_rename_authid(THD *thd, TABLE *edge_table, TABLE *defaults_table,
|
|
LEX_USER *user_from, LEX_USER *user_to) {
|
|
DBUG_ASSERT(assert_acl_cache_write_lock(thd));
|
|
ACL_USER *acl_user_to =
|
|
find_acl_user(user_to->host.str, user_to->user.str, true);
|
|
if (acl_user_to == 0) {
|
|
/* The target user doesn't exist yet? */
|
|
return true;
|
|
}
|
|
/* Update default roles */
|
|
std::vector<Role_id> old_roles;
|
|
Auth_id_ref authid_user_from = create_authid_from(user_from);
|
|
clear_default_roles(thd, defaults_table, authid_user_from, &old_roles);
|
|
List_of_auth_id_refs new_default_role_ref;
|
|
for (auto &&role : old_roles) {
|
|
Auth_id_ref authid = create_authid_from(role);
|
|
new_default_role_ref.push_back(authid);
|
|
}
|
|
bool ret = alter_user_set_default_roles(thd, defaults_table, user_to,
|
|
new_default_role_ref);
|
|
|
|
if (ret) {
|
|
String warning;
|
|
append_identifier(thd, &warning, user_from->user.str,
|
|
user_from->user.length);
|
|
append_identifier(thd, &warning, user_from->host.str,
|
|
user_from->host.length);
|
|
LogErr(WARNING_LEVEL, ER_SQL_AUTHOR_DEFAULT_ROLES_FAIL, warning.c_ptr());
|
|
ret = false;
|
|
}
|
|
|
|
List_of_granted_roles granted_roles;
|
|
ret = revoke_all_granted_roles(thd, edge_table, user_from, &granted_roles);
|
|
if (!ret) {
|
|
for (auto &&ref : granted_roles) {
|
|
ACL_USER *acl_role = find_acl_user(ref.first.host().c_str(),
|
|
ref.first.user().c_str(), ref.second);
|
|
if (acl_role == 0) {
|
|
/* An invalid reference was encountered; just ignore it. */
|
|
continue;
|
|
}
|
|
grant_role(acl_role, acl_user_to, ref.second);
|
|
Auth_id_ref authid_role = create_authid_from(acl_role);
|
|
Auth_id_ref authid_user = create_authid_from(acl_user_to);
|
|
ret = modify_role_edges_in_table(thd, edge_table, authid_role,
|
|
authid_user, ref.second, false);
|
|
if (ret) break;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
Maps a global ACL to a string representation.
|
|
|
|
@param thd Thread handler
|
|
@param want_access An ACL
|
|
@param acl_user The associated user which carries the ACL
|
|
@param [out] global The resulting string
|
|
|
|
*/
|
|
|
|
void make_global_privilege_statement(THD *thd, ulong want_access,
|
|
ACL_USER *acl_user, String *global) {
|
|
DBUG_ASSERT(assert_acl_cache_read_lock(thd));
|
|
global->length(0);
|
|
global->append(STRING_WITH_LEN("GRANT "));
|
|
|
|
if (!(want_access & ~GRANT_ACL))
|
|
global->append(STRING_WITH_LEN("USAGE"));
|
|
else {
|
|
bool found = 0;
|
|
ulong test_access = want_access & ~GRANT_ACL;
|
|
int counter = 0;
|
|
ulong j = SELECT_ACL;
|
|
for (; j <= GLOBAL_ACLS; counter++, j <<= 1) {
|
|
if (test_access & j) {
|
|
if (found) global->append(STRING_WITH_LEN(", "));
|
|
found = 1;
|
|
global->append(global_acls_vector[counter].c_str(),
|
|
global_acls_vector[counter].length());
|
|
}
|
|
}
|
|
}
|
|
global->append(STRING_WITH_LEN(" ON *.* TO "));
|
|
size_t len = acl_user->user == 0 ? 0 : strlen(acl_user->user);
|
|
append_identifier(thd, global, acl_user->user, len);
|
|
global->append('@');
|
|
append_identifier(thd, global, acl_user->host.get_host(),
|
|
acl_user->host.get_host_len());
|
|
if (want_access & GRANT_ACL)
|
|
global->append(STRING_WITH_LEN(" WITH GRANT OPTION"));
|
|
}
|
|
|
|
/**
|
|
Maps a set of database level ACLs to string representations and sends them
|
|
through the client protocol.
|
|
|
|
@param thd The thread handler
|
|
@param role The authid associated with the ACLs
|
|
@param protocol A handler used for sending data to the client
|
|
@param db_map A list of database level ACLs
|
|
@param db_wild_map A list of database level ACLs which use pattern matching
|
|
@param restrictions List of databases on which there exists different
|
|
restrictions for the ACL_USER.
|
|
*/
|
|
|
|
void make_database_privilege_statement(THD *thd, ACL_USER *role,
|
|
Protocol *protocol,
|
|
const Db_access_map &db_map,
|
|
const Db_access_map &db_wild_map,
|
|
const DB_restrictions &restrictions) {
|
|
DBUG_ASSERT(assert_acl_cache_read_lock(thd));
|
|
|
|
auto make_grant_stmts = [](THD *thd, ACL_USER *role, Protocol *protocol,
|
|
const Db_access_map &db_map) {
|
|
Db_access_map::const_iterator it = db_map.begin();
|
|
for (; it != db_map.end(); ++it) {
|
|
ulong want_access = it->second;
|
|
std::string db_name = it->first;
|
|
|
|
String db;
|
|
db.length(0);
|
|
db.append(STRING_WITH_LEN("GRANT "));
|
|
|
|
if (test_all_bits(want_access, (DB_OP_ACLS)))
|
|
db.append(STRING_WITH_LEN("ALL PRIVILEGES"));
|
|
else if (!(want_access & ~GRANT_ACL))
|
|
db.append(STRING_WITH_LEN("USAGE"));
|
|
else {
|
|
int found = 0, cnt;
|
|
ulong j, test_access = want_access & ~GRANT_ACL;
|
|
for (cnt = 0, j = SELECT_ACL; j <= DB_OP_ACLS; cnt++, j <<= 1) {
|
|
if (test_access & j) {
|
|
if (found) db.append(STRING_WITH_LEN(", "));
|
|
found = 1;
|
|
db.append(global_acls_vector[cnt].c_str(),
|
|
global_acls_vector[cnt].length());
|
|
}
|
|
}
|
|
}
|
|
db.append(STRING_WITH_LEN(" ON "));
|
|
append_identifier(thd, &db, db_name.c_str(), db_name.length());
|
|
db.append(STRING_WITH_LEN(".* TO "));
|
|
append_identifier(thd, &db, role->user,
|
|
role->user ? strlen(role->user) : 0);
|
|
db.append('@');
|
|
// host and lex_user->host are equal except for case
|
|
append_identifier(thd, &db, role->host.get_host(),
|
|
role->host.get_host_len());
|
|
if (want_access & GRANT_ACL)
|
|
db.append(STRING_WITH_LEN(" WITH GRANT OPTION"));
|
|
protocol->start_row();
|
|
protocol->store_string(db.ptr(), db.length(), db.charset());
|
|
protocol->end_row();
|
|
}
|
|
};
|
|
auto make_partial_db_revoke_stmts = [](THD *thd, ACL_USER *acl_user,
|
|
Protocol *protocol,
|
|
const DB_restrictions &restrictions) {
|
|
if (mysqld_partial_revokes()) {
|
|
/*
|
|
Copy the unordered restrictions into an array.
|
|
Send the sorted partial revokes to the client.
|
|
*/
|
|
Mem_root_array<std::pair<std::string, ulong>> restrictions_array(
|
|
thd->mem_root);
|
|
for (const auto rl_itr : restrictions.get()) {
|
|
restrictions_array.push_back({rl_itr.first, rl_itr.second});
|
|
}
|
|
std::sort(restrictions_array.begin(), restrictions_array.end(),
|
|
[](const auto &p1, const auto &p2) -> bool {
|
|
return (p1.first.compare(p2.first) <= 0);
|
|
});
|
|
for (const auto rl_itr : restrictions_array) {
|
|
String db;
|
|
db.length(0);
|
|
db.append(STRING_WITH_LEN("REVOKE "));
|
|
int found = 0, cnt;
|
|
ulong j, test_access = rl_itr.second & ~GRANT_ACL;
|
|
for (cnt = 0, j = SELECT_ACL; j <= DB_ACLS; cnt++, j <<= 1) {
|
|
if (test_access & j) {
|
|
if (found) db.append(STRING_WITH_LEN(", "));
|
|
found = 1;
|
|
db.append(global_acls_vector[cnt].c_str(),
|
|
global_acls_vector[cnt].length());
|
|
}
|
|
}
|
|
|
|
db.append(STRING_WITH_LEN(" ON "));
|
|
append_identifier(thd, &db, rl_itr.first.c_str(),
|
|
rl_itr.first.length());
|
|
db.append(STRING_WITH_LEN(".* FROM "));
|
|
append_identifier(thd, &db, acl_user->user,
|
|
acl_user->user ? strlen(acl_user->user) : 0);
|
|
db.append('@');
|
|
// host and lex_user->host are equal except for case
|
|
append_identifier(thd, &db, acl_user->host.get_host(),
|
|
acl_user->host.get_host_len());
|
|
protocol->start_row();
|
|
protocol->store_string(db.ptr(), db.length(), db.charset());
|
|
protocol->end_row();
|
|
}
|
|
}
|
|
};
|
|
|
|
make_grant_stmts(thd, role, protocol, db_map);
|
|
make_grant_stmts(thd, role, protocol, db_wild_map);
|
|
make_partial_db_revoke_stmts(thd, role, protocol, restrictions);
|
|
}
|
|
|
|
/**
|
|
Maps a set of global level proxy ACLs to string representations and sends them
|
|
through the client protocol.
|
|
|
|
@param thd The thread handler
|
|
@param user The authid associated with the proxy ACLs.
|
|
@param protocol The handler used for sending data through the client protocol
|
|
|
|
*/
|
|
|
|
void make_proxy_privilege_statement(THD *thd MY_ATTRIBUTE((unused)),
|
|
ACL_USER *user, Protocol *protocol) {
|
|
DBUG_ASSERT(assert_acl_cache_read_lock(thd));
|
|
for (ACL_PROXY_USER *proxy = acl_proxy_users->begin();
|
|
proxy != acl_proxy_users->end(); ++proxy) {
|
|
if (proxy->granted_on(user->host.get_host(), user->user)) {
|
|
String global;
|
|
proxy->print_grant(&global);
|
|
protocol->start_row();
|
|
protocol->store_string(global.ptr(), global.length(), global.charset());
|
|
protocol->end_row();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
Maps a set of database level ACLs for stored programs to string
|
|
representations and sends them through the client protocol.
|
|
|
|
@param thd A thread handler
|
|
@param role The authid associated with the ACLs
|
|
@param protocol The handler used for sending data through the client protocol
|
|
@param sp_map The ACLs granted to role
|
|
@param type Either 0 for procedures or 1 for functions
|
|
|
|
*/
|
|
|
|
void make_sp_privilege_statement(THD *thd, ACL_USER *role, Protocol *protocol,
|
|
SP_access_map &sp_map, int type) {
|
|
DBUG_ASSERT(assert_acl_cache_read_lock(thd));
|
|
SP_access_map::iterator it = sp_map.begin();
|
|
for (; it != sp_map.end(); ++it) {
|
|
ulong want_access = it->second;
|
|
std::string sp_name = it->first;
|
|
|
|
String db;
|
|
db.length(0);
|
|
db.append(STRING_WITH_LEN("GRANT "));
|
|
|
|
if (test_all_bits(want_access, (DB_OP_ACLS)))
|
|
db.append(STRING_WITH_LEN("ALL PRIVILEGES"));
|
|
else if (!(want_access & ~GRANT_ACL))
|
|
db.append(STRING_WITH_LEN("USAGE"));
|
|
else {
|
|
int found = 0, cnt;
|
|
ulong j, test_access = want_access & ~GRANT_ACL;
|
|
for (cnt = 0, j = SELECT_ACL; j <= DB_OP_ACLS; cnt++, j <<= 1) {
|
|
if (test_access & j) {
|
|
if (found) db.append(STRING_WITH_LEN(", "));
|
|
found = 1;
|
|
db.append(global_acls_vector[cnt].c_str(),
|
|
global_acls_vector[cnt].length());
|
|
}
|
|
}
|
|
}
|
|
db.append(STRING_WITH_LEN(" ON "));
|
|
if (type == 0)
|
|
db.append(STRING_WITH_LEN("PROCEDURE "));
|
|
else
|
|
db.append(STRING_WITH_LEN("FUNCTION "));
|
|
db.append(sp_name.c_str(), sp_name.length());
|
|
db.append(STRING_WITH_LEN(" TO "));
|
|
append_identifier(thd, &db, role->user,
|
|
role->user ? strlen(role->user) : 0);
|
|
db.append(STRING_WITH_LEN("@"));
|
|
// host and lex_user->host are equal except for case
|
|
append_identifier(thd, &db, role->host.get_host(),
|
|
role->host.get_host_len());
|
|
if (want_access & GRANT_ACL)
|
|
db.append(STRING_WITH_LEN(" WITH GRANT OPTION"));
|
|
protocol->start_row();
|
|
protocol->store_string(db.ptr(), db.length(), db.charset());
|
|
protocol->end_row();
|
|
}
|
|
}
|
|
|
|
bool is_granted_role_with_admin(const std::string &authid,
|
|
const List_of_granted_roles &granted_roles) {
|
|
for (auto &role : granted_roles) {
|
|
std::string granted_role_str;
|
|
role.first.auth_str(&granted_role_str);
|
|
if (role.second && (authid == granted_role_str)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void make_with_admin_privilege_statement(
|
|
THD *thd, ACL_USER *acl_user, Protocol *protocol,
|
|
const Grant_acl_set &with_admin_acl,
|
|
const List_of_granted_roles &granted_roles) {
|
|
DBUG_ASSERT(assert_acl_cache_read_lock(thd));
|
|
if (granted_roles.size() == 0) return;
|
|
std::set<std::string> sorted_copy_of_granted_role_str;
|
|
for (auto &rid : granted_roles) {
|
|
if (!rid.second) continue; // this is not granted WITH ADMIN
|
|
std::string key;
|
|
rid.first.auth_str(&key);
|
|
sorted_copy_of_granted_role_str.insert(key);
|
|
}
|
|
for (auto &s : with_admin_acl) {
|
|
sorted_copy_of_granted_role_str.insert(s);
|
|
}
|
|
|
|
if (sorted_copy_of_granted_role_str.size() == 0) return;
|
|
std::set<std::string>::iterator it = sorted_copy_of_granted_role_str.begin();
|
|
String global;
|
|
global.append(STRING_WITH_LEN("GRANT "));
|
|
bool found = false;
|
|
for (; it != sorted_copy_of_granted_role_str.end(); ++it) {
|
|
if (it != sorted_copy_of_granted_role_str.begin()) global.append(',');
|
|
global.append(it->c_str(), it->length());
|
|
found = true;
|
|
}
|
|
if (found) {
|
|
global.append(STRING_WITH_LEN(" TO "));
|
|
append_identifier(thd, &global, acl_user->user, strlen(acl_user->user));
|
|
global.append('@');
|
|
append_identifier(thd, &global, acl_user->host.get_host(),
|
|
acl_user->host.get_host_len());
|
|
global.append(" WITH ADMIN OPTION");
|
|
|
|
protocol->start_row();
|
|
protocol->store_string(global.ptr(), global.length(), global.charset());
|
|
protocol->end_row();
|
|
}
|
|
}
|
|
|
|
void make_dynamic_privilege_statement(THD *thd, ACL_USER *role,
|
|
Protocol *protocol,
|
|
const Dynamic_privileges &dyn_priv) {
|
|
DBUG_ASSERT(assert_acl_cache_read_lock(thd));
|
|
bool found = false;
|
|
/*
|
|
On first iteration create a statement out of all the grants which don't
|
|
have a grant option.
|
|
On second iteration process all privileges with a grant option.
|
|
*/
|
|
for (int grant_option = 0; grant_option < 2; ++grant_option) {
|
|
String global;
|
|
global.append(STRING_WITH_LEN("GRANT "));
|
|
for (auto &&priv : dyn_priv) {
|
|
if (grant_option == 0 && priv.second) continue;
|
|
if (grant_option == 1 && !priv.second) continue;
|
|
if (found) global.append(',');
|
|
global.append(priv.first.c_str(), priv.first.length());
|
|
found = true;
|
|
}
|
|
if (found) {
|
|
/* Dynamic privileges are always applied on global level */
|
|
global.append(STRING_WITH_LEN(" ON *.* TO "));
|
|
if (role->user != nullptr)
|
|
append_identifier(thd, &global, role->user, strlen(role->user));
|
|
else
|
|
global.append(STRING_WITH_LEN("''"));
|
|
global.append('@');
|
|
append_identifier(thd, &global, role->host.get_host(),
|
|
role->host.get_host_len());
|
|
if (grant_option) global.append(" WITH GRANT OPTION");
|
|
protocol->start_row();
|
|
protocol->store_string(global.ptr(), global.length(), global.charset());
|
|
protocol->end_row();
|
|
}
|
|
found = false;
|
|
} // end for
|
|
}
|
|
|
|
void make_roles_privilege_statement(THD *thd, ACL_USER *role,
|
|
Protocol *protocol,
|
|
List_of_granted_roles &granted_roles,
|
|
bool show_mandatory_roles) {
|
|
DBUG_ASSERT(assert_acl_cache_read_lock(thd));
|
|
String global;
|
|
global.append(STRING_WITH_LEN("GRANT "));
|
|
|
|
bool found = false;
|
|
std::vector<Role_id> mandatory_roles;
|
|
/*
|
|
Because the output of SHOW GRANTS is used by tools like mysqldump we
|
|
cannot include mandatory roles if the FOR clause is used.
|
|
*/
|
|
if (show_mandatory_roles) {
|
|
get_mandatory_roles(&mandatory_roles);
|
|
}
|
|
if (granted_roles.size() == 0 && mandatory_roles.size() == 0) return;
|
|
/* First list granted roles which doesn't have WITH ADMIN */
|
|
std::sort(granted_roles.begin(), granted_roles.end());
|
|
List_of_granted_roles::iterator it = granted_roles.begin();
|
|
std::vector<Role_id>::iterator it2 = mandatory_roles.begin();
|
|
bool got_more_mandatory_roles = (it2 != mandatory_roles.end());
|
|
bool got_more_granted_roles = (it != granted_roles.end());
|
|
while (got_more_mandatory_roles || got_more_granted_roles) {
|
|
if (got_more_granted_roles && !it->second &&
|
|
!(got_more_mandatory_roles && *it2 < it->first)) {
|
|
if (found) global.append(',');
|
|
append_identifier(thd, &global, it->first.user().c_str(),
|
|
it->first.user().length());
|
|
global.append('@');
|
|
append_identifier(thd, &global, it->first.host().c_str(),
|
|
it->first.host().length());
|
|
found = true;
|
|
if (got_more_mandatory_roles && it->first == *it2) ++it2;
|
|
++it;
|
|
} else if (got_more_mandatory_roles) {
|
|
if (found) global.append(',');
|
|
append_identifier(thd, &global, it2->user().c_str(),
|
|
it2->user().length());
|
|
global.append('@');
|
|
append_identifier(thd, &global, it2->host().c_str(),
|
|
it2->host().length());
|
|
found = true;
|
|
++it2;
|
|
} else
|
|
++it;
|
|
got_more_mandatory_roles = (it2 != mandatory_roles.end());
|
|
got_more_granted_roles = (it != granted_roles.end());
|
|
} // end while
|
|
if (found) {
|
|
global.append(STRING_WITH_LEN(" TO "));
|
|
append_identifier(thd, &global, role->user,
|
|
role->user ? strlen(role->user) : 0);
|
|
global.append('@');
|
|
append_identifier(thd, &global, role->host.get_host(),
|
|
role->host.get_host_len());
|
|
protocol->start_row();
|
|
protocol->store_string(global.ptr(), global.length(), global.charset());
|
|
protocol->end_row();
|
|
}
|
|
}
|
|
|
|
void make_table_privilege_statement(THD *thd, ACL_USER *role,
|
|
Protocol *protocol,
|
|
Table_access_map &table_map) {
|
|
DBUG_ASSERT(assert_acl_cache_read_lock(thd));
|
|
Table_access_map::iterator it = table_map.begin();
|
|
for (; it != table_map.end(); ++it) {
|
|
std::string qualified_table_name = it->first;
|
|
Grant_table_aggregate agg = it->second;
|
|
String global;
|
|
ulong test_access = (agg.table_access | agg.cols) & ~GRANT_ACL;
|
|
|
|
global.length(0);
|
|
global.append(STRING_WITH_LEN("GRANT "));
|
|
|
|
if (test_all_bits(agg.table_access, (TABLE_OP_ACLS)))
|
|
global.append(STRING_WITH_LEN("ALL PRIVILEGES"));
|
|
else if (!test_access)
|
|
global.append(STRING_WITH_LEN("USAGE"));
|
|
else {
|
|
/* Add specific column access */
|
|
int found = 0;
|
|
ulong j;
|
|
ulong counter;
|
|
for (counter = 0, j = SELECT_ACL; j <= TABLE_OP_ACLS;
|
|
counter++, j <<= 1) {
|
|
if (test_access & j) {
|
|
if (found) global.append(STRING_WITH_LEN(", "));
|
|
found = 1;
|
|
global.append(global_acls_vector[counter].c_str(),
|
|
global_acls_vector[counter].length());
|
|
|
|
if (agg.cols) {
|
|
uint found_col = 0;
|
|
Column_map::iterator col_it = agg.columns.begin();
|
|
for (; col_it != agg.columns.end(); ++col_it) {
|
|
if (col_it->second & j) {
|
|
if (!found_col) {
|
|
found_col = 1;
|
|
/*
|
|
If we have a duplicated table level privilege, we
|
|
must write the access privilege name again.
|
|
*/
|
|
if (agg.table_access & j) {
|
|
global.append(STRING_WITH_LEN(", "));
|
|
global.append(global_acls_vector[counter].c_str(),
|
|
global_acls_vector[counter].length());
|
|
}
|
|
global.append(STRING_WITH_LEN(" ("));
|
|
} else
|
|
global.append(STRING_WITH_LEN(", "));
|
|
append_identifier(thd, &global, col_it->first.c_str(),
|
|
col_it->first.length());
|
|
}
|
|
}
|
|
if (found_col) global.append(')');
|
|
}
|
|
}
|
|
}
|
|
}
|
|
global.append(STRING_WITH_LEN(" ON "));
|
|
global.append(qualified_table_name.c_str(), qualified_table_name.length());
|
|
global.append(STRING_WITH_LEN(" TO "));
|
|
append_identifier(thd, &global, role->user, strlen(role->user));
|
|
global.append('@');
|
|
// host and lex_user->host are equal except for case
|
|
append_identifier(thd, &global, role->host.get_host(),
|
|
role->host.get_host_len());
|
|
if (agg.table_access & GRANT_ACL)
|
|
global.append(STRING_WITH_LEN(" WITH GRANT OPTION"));
|
|
protocol->start_row();
|
|
protocol->store_string(global.ptr(), global.length(), global.charset());
|
|
protocol->end_row();
|
|
}
|
|
}
|
|
|
|
void get_sp_access_map(
|
|
ACL_USER *acl_user, SP_access_map *sp_map,
|
|
malloc_unordered_multimap<std::string, unique_ptr_destroy_only<GRANT_NAME>>
|
|
*hash) {
|
|
DBUG_ASSERT(assert_acl_cache_read_lock(current_thd));
|
|
/* Add routine access */
|
|
for (const auto &key_and_value : *hash) {
|
|
GRANT_NAME *grant_proc = key_and_value.second.get();
|
|
const char *user, *host;
|
|
if (!(user = grant_proc->user)) user = "";
|
|
if (!(host = grant_proc->host.get_host())) host = "";
|
|
|
|
/*
|
|
We do not make SHOW GRANTS case-sensitive here (like REVOKE),
|
|
but make it case-insensitive because that's the way they are
|
|
actually applied, and showing fewer privileges than are applied
|
|
would be wrong from a security point of view.
|
|
*/
|
|
|
|
if (!strcmp(acl_user->user, user) &&
|
|
!my_strcasecmp(system_charset_info, acl_user->host.get_host(), host)) {
|
|
ulong proc_access = grant_proc->privs;
|
|
if (proc_access != 0) {
|
|
String key;
|
|
append_identifier(&key, grant_proc->db, strlen(grant_proc->db));
|
|
key.append(".");
|
|
append_identifier(&key, grant_proc->tname, strlen(grant_proc->tname));
|
|
(*sp_map)[std::string(key.c_ptr())] |= proc_access;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void get_table_access_map(ACL_USER *acl_user, Table_access_map *table_map) {
|
|
DBUG_TRACE;
|
|
DBUG_ASSERT(assert_acl_cache_read_lock(current_thd));
|
|
for (const auto &key_and_value : *column_priv_hash) {
|
|
GRANT_TABLE *grant_table = key_and_value.second.get();
|
|
const char *user, *host;
|
|
|
|
if (!(user = grant_table->user)) user = "";
|
|
if (!(host = grant_table->host.get_host())) host = "";
|
|
const char *acl_user_host, *acl_user_user;
|
|
if (!(acl_user_host = acl_user->host.get_host())) acl_user_host = "";
|
|
if (!(acl_user_user = acl_user->user)) acl_user_user = "";
|
|
|
|
/*
|
|
We do not make SHOW GRANTS case-sensitive here (like REVOKE),
|
|
but make it case-insensitive because that's the way they are
|
|
actually applied, and showing fewer privileges than are applied
|
|
would be wrong from a security point of view.
|
|
*/
|
|
if (!strcmp(acl_user_user, user) &&
|
|
!my_strcasecmp(system_charset_info, acl_user_host, host)) {
|
|
ulong table_access = grant_table->privs;
|
|
if ((table_access | grant_table->cols) != 0) {
|
|
String q_name;
|
|
const THD *thd = table_map->get_thd();
|
|
append_identifier(thd, &q_name, grant_table->db,
|
|
strlen(grant_table->db));
|
|
q_name.append(".");
|
|
append_identifier(thd, &q_name, grant_table->tname,
|
|
strlen(grant_table->tname));
|
|
Grant_table_aggregate agg = (*table_map)[std::string(q_name.c_ptr())];
|
|
// cols is an ACL of all privileges found as column privileges in the
|
|
// table given any column in that table. Before a hash look up
|
|
// you can check in this column if the column exist in the first place
|
|
// for the required privilege
|
|
agg.cols |= grant_table->cols;
|
|
agg.table_access |= grant_table->privs;
|
|
if (grant_table->cols) {
|
|
DBUG_PRINT("info", ("Collecting column privileges for %s@%s",
|
|
acl_user->user, acl_user->host.get_host()));
|
|
// Iterate over all column ACLs for this table.
|
|
for (const auto &key_and_value : grant_table->hash_columns) {
|
|
String q_col_name;
|
|
GRANT_COLUMN *col = key_and_value.second.get();
|
|
// TODO why can this be 0x0 ?!
|
|
if (col) {
|
|
std::string str_column_name(col->column);
|
|
ulong col_access = agg.columns[str_column_name];
|
|
col_access |= col->rights;
|
|
agg.columns[str_column_name] = col_access;
|
|
DBUG_PRINT("info", ("Found privilege %lu on %s.%s", col_access,
|
|
q_name.c_ptr(), q_col_name.c_ptr()));
|
|
}
|
|
}
|
|
}
|
|
(*table_map)[std::string(q_name.c_ptr())] = agg;
|
|
}
|
|
}
|
|
} // end for
|
|
}
|
|
|
|
void get_dynamic_privileges(ACL_USER *acl_user, Dynamic_privileges *acl) {
|
|
Role_id key(create_authid_from(acl_user));
|
|
User_to_dynamic_privileges_map::iterator it, it_end;
|
|
|
|
std::tie(it, it_end) = g_dynamic_privileges_map->equal_range(key);
|
|
for (; it != it_end; ++it) {
|
|
auto aggr = acl->find(it->second.first);
|
|
if (aggr != acl->end() && aggr->second != it->second.second) {
|
|
/*
|
|
If this privID was already in the aggregate we make sure that the
|
|
grant option take precedence; any GRANT OPTION will be sticky through
|
|
out role privilege aggregation.
|
|
*/
|
|
aggr->second = true;
|
|
} else
|
|
acl->insert(it->second);
|
|
}
|
|
}
|
|
|
|
bool has_wildcards_in_db_grant(const std::string &db_string) {
|
|
size_t wild_one_pos = db_string.find(wild_one);
|
|
size_t wild_many_pos = db_string.find(wild_many);
|
|
|
|
// No escape character
|
|
if (wild_one_pos == 0 || wild_many_pos == 0) return true;
|
|
// No wildcard characters
|
|
if (wild_one_pos == std::string::npos && wild_many_pos == std::string::npos)
|
|
return false;
|
|
|
|
// Find first wild_one that's not prefixed by wild_prefix
|
|
while (wild_one_pos != std::string::npos) {
|
|
if (db_string[wild_one_pos - 1] == wild_prefix)
|
|
wild_one_pos = db_string.find(wild_one, wild_one_pos + 1);
|
|
else
|
|
return true;
|
|
}
|
|
|
|
// Find first wild_many that's not prefixed by wild_prefix
|
|
while (wild_many_pos != std::string::npos) {
|
|
if (db_string[wild_many_pos - 1] == wild_prefix)
|
|
wild_many_pos = db_string.find(wild_many, wild_many_pos + 1);
|
|
else
|
|
return true;
|
|
}
|
|
|
|
// All wild_one and wild_many are prefixed with wild_prefix
|
|
return false;
|
|
}
|
|
|
|
bool has_wildcard_characters(const LEX_CSTRING &db) {
|
|
return (memchr(db.str, wild_one, db.length) != NULL ||
|
|
memchr(db.str, wild_many, db.length) != NULL);
|
|
}
|
|
|
|
void get_database_access_map(ACL_USER *acl_user, Db_access_map *db_map,
|
|
Db_access_map *db_wild_map) {
|
|
ACL_DB *acl_db;
|
|
DBUG_ASSERT(assert_acl_cache_read_lock(current_thd));
|
|
for (acl_db = acl_dbs->begin(); acl_db != acl_dbs->end(); ++acl_db) {
|
|
const char *acl_db_user, *acl_db_host;
|
|
if (!(acl_db_user = acl_db->user)) acl_db_user = "";
|
|
if (!(acl_db_host = acl_db->host.get_host())) acl_db_host = "";
|
|
const char *acl_user_host, *acl_user_user;
|
|
if (!(acl_user_host = acl_user->host.get_host())) acl_user_host = "";
|
|
if (!(acl_user_user = acl_user->user)) acl_user_user = "";
|
|
|
|
/*
|
|
We do not make SHOW GRANTS case-sensitive here (like REVOKE),
|
|
but make it case-insensitive because that's the way they are
|
|
actually applied, and showing fewer privileges than are applied
|
|
would be wrong from a security point of view.
|
|
*/
|
|
|
|
if (!strcmp(acl_user_user, acl_db_user) &&
|
|
!my_strcasecmp(system_charset_info, acl_user_host, acl_db_host)) {
|
|
ulong want_access = acl_db->access;
|
|
if (want_access) {
|
|
if (has_wildcard_characters({acl_db->db, strlen(acl_db->db)})) {
|
|
(*db_wild_map)[std::string(acl_db->db)] |= want_access;
|
|
} else {
|
|
(*db_map)[std::string(acl_db->db)] |= want_access;
|
|
}
|
|
DBUG_PRINT("info", ("Role: %s db: %s acl: %lu", acl_user_user,
|
|
acl_db->db, want_access));
|
|
} // end if access
|
|
}
|
|
} // end for
|
|
}
|
|
|
|
/**
|
|
A graph visitor used for doing breadth-first traversal of the global role
|
|
graph. The visitor takes a set of access maps and aggregate all discovered
|
|
privileges into these maps.
|
|
*/
|
|
class Get_access_maps : public boost::default_bfs_visitor {
|
|
public:
|
|
Get_access_maps(ACL_USER *acl_user, ulong *access, Db_access_map *db_map,
|
|
Db_access_map *db_wild_map, Table_access_map *table_map,
|
|
SP_access_map *sp_map, SP_access_map *func_map,
|
|
Grant_acl_set *with_admin_acl, Dynamic_privileges *dyn_acl,
|
|
Restrictions *restrictions)
|
|
: m_access(access),
|
|
m_db_map(db_map),
|
|
m_db_wild_map(db_wild_map),
|
|
m_table_map(table_map),
|
|
m_sp_map(sp_map),
|
|
m_func_map(func_map),
|
|
m_with_admin_acl(with_admin_acl),
|
|
m_dynamic_acl(dyn_acl),
|
|
m_restrictions(restrictions),
|
|
m_grantee{acl_user->user, strlen(acl_user->user),
|
|
acl_user->host.get_host(), acl_user->host.get_host_len()} {}
|
|
template <typename Vertex, typename Graph>
|
|
void discover_vertex(Vertex u, const Graph &) const {
|
|
ACL_USER acl_user = get(boost::vertex_acl_user_t(), *g_granted_roles)[u];
|
|
if (acl_user.user == g_active_dummy_user) return; // skip root node
|
|
DBUG_PRINT("info",
|
|
("Role visitor in %s@%s, adding global access %lu\n",
|
|
acl_user.user, acl_user.host.get_host(), acl_user.access));
|
|
/* Add database access */
|
|
get_database_access_map(&acl_user, m_db_map, m_db_wild_map);
|
|
|
|
/* Add restrictions */
|
|
{
|
|
/* DB Restrictions */
|
|
const Auth_id granter(&acl_user);
|
|
Restrictions restrictions =
|
|
acl_restrictions->find_restrictions(&acl_user);
|
|
std::unique_ptr<Restrictions_aggregator> aggregator =
|
|
Restrictions_aggregator_factory::create(
|
|
granter, m_grantee, acl_user.access & DB_ACLS,
|
|
*m_access & DB_ACLS, restrictions.db(), m_restrictions->db(),
|
|
acl_user.access & DB_ACLS, m_db_map);
|
|
if (aggregator) {
|
|
DB_restrictions db_restrictions(nullptr);
|
|
if (aggregator->generate(db_restrictions)) return;
|
|
m_restrictions->set_db(db_restrictions);
|
|
}
|
|
}
|
|
/* Add global access */
|
|
/*
|
|
Up-cast to base class to avoid gcc 7.1.1 warning:
|
|
dereferencing type-punned pointer will break strict-aliasing rules
|
|
*/
|
|
*m_access |= implicit_cast<ACL_ACCESS *>(&acl_user)->access;
|
|
|
|
/* Add table access */
|
|
get_table_access_map(&acl_user, m_table_map);
|
|
|
|
/* Add stored procedure access */
|
|
get_sp_access_map(&acl_user, m_sp_map, proc_priv_hash.get());
|
|
|
|
/* Add user function access */
|
|
get_sp_access_map(&acl_user, m_sp_map, func_priv_hash.get());
|
|
|
|
/* Add dynamic privileges */
|
|
get_dynamic_privileges(&acl_user, m_dynamic_acl);
|
|
}
|
|
|
|
template <typename Edge, typename Graph>
|
|
void examine_edge(const Edge &edge, Graph &granted_roles) {
|
|
ACL_USER to_user =
|
|
boost::get(boost::vertex_acl_user_t(),
|
|
granted_roles)[boost::target(edge, granted_roles)];
|
|
int with_admin_opt =
|
|
boost::get(boost::edge_capacity_t(), granted_roles)[edge];
|
|
if (with_admin_opt) {
|
|
String qname;
|
|
append_identifier(&qname, to_user.user, strlen(to_user.user));
|
|
qname.append('@');
|
|
/* Up-cast to base class, see above. */
|
|
append_identifier(
|
|
&qname, implicit_cast<ACL_ACCESS *>(&to_user)->host.get_host(),
|
|
implicit_cast<ACL_ACCESS *>(&to_user)->host.get_host_len());
|
|
/* We save the granted role in the Acl_map of the granted user */
|
|
m_with_admin_acl->insert(
|
|
std::string(qname.c_ptr_quick(), qname.length()));
|
|
}
|
|
}
|
|
|
|
private:
|
|
ulong *m_access;
|
|
Db_access_map *m_db_map;
|
|
Db_access_map *m_db_wild_map;
|
|
Table_access_map *m_table_map;
|
|
SP_access_map *m_sp_map;
|
|
SP_access_map *m_func_map;
|
|
Grant_acl_set *m_with_admin_acl;
|
|
Dynamic_privileges *m_dynamic_acl;
|
|
Restrictions *m_restrictions;
|
|
Auth_id m_grantee;
|
|
};
|
|
|
|
/**
|
|
Get a cached internal schema access.
|
|
@param grant_internal_info the cache
|
|
@param schema_name the name of the internal schema
|
|
*/
|
|
const ACL_internal_schema_access *get_cached_schema_access(
|
|
GRANT_INTERNAL_INFO *grant_internal_info, const char *schema_name) {
|
|
if (grant_internal_info) {
|
|
if (!grant_internal_info->m_schema_lookup_done) {
|
|
grant_internal_info->m_schema_access =
|
|
ACL_internal_schema_registry::lookup(schema_name);
|
|
grant_internal_info->m_schema_lookup_done = true;
|
|
}
|
|
return grant_internal_info->m_schema_access;
|
|
}
|
|
return ACL_internal_schema_registry::lookup(schema_name);
|
|
}
|
|
|
|
/**
|
|
Get a cached internal table access.
|
|
@param grant_internal_info the cache
|
|
@param schema_name the name of the internal schema
|
|
@param table_name the name of the internal table
|
|
*/
|
|
const ACL_internal_table_access *get_cached_table_access(
|
|
GRANT_INTERNAL_INFO *grant_internal_info, const char *schema_name,
|
|
const char *table_name) {
|
|
DBUG_ASSERT(grant_internal_info);
|
|
if (!grant_internal_info->m_table_lookup_done) {
|
|
const ACL_internal_schema_access *schema_access;
|
|
schema_access = get_cached_schema_access(grant_internal_info, schema_name);
|
|
if (schema_access)
|
|
grant_internal_info->m_table_access = schema_access->lookup(table_name);
|
|
grant_internal_info->m_table_lookup_done = true;
|
|
}
|
|
return grant_internal_info->m_table_access;
|
|
}
|
|
|
|
ACL_internal_access_result IS_internal_schema_access::check(
|
|
ulong want_access, ulong *save_priv) const {
|
|
want_access &= ~SELECT_ACL;
|
|
|
|
/*
|
|
We don't allow any simple privileges but SELECT_ACL on
|
|
the information_schema database.
|
|
*/
|
|
if (unlikely(want_access & DB_ACLS)) return ACL_INTERNAL_ACCESS_DENIED;
|
|
|
|
/* Always grant SELECT for the information schema. */
|
|
*save_priv |= SELECT_ACL;
|
|
|
|
return want_access ? ACL_INTERNAL_ACCESS_CHECK_GRANT
|
|
: ACL_INTERNAL_ACCESS_GRANTED;
|
|
}
|
|
|
|
const ACL_internal_table_access *IS_internal_schema_access::lookup(
|
|
const char *) const {
|
|
/* There are no per table rules for the information schema. */
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
Check privileges for LOCK TABLES statement.
|
|
|
|
@param thd Thread context.
|
|
@param tables List of tables to be locked.
|
|
|
|
@retval false - Success.
|
|
@retval true - Failure.
|
|
*/
|
|
|
|
bool lock_tables_precheck(THD *thd, TABLE_LIST *tables) {
|
|
TABLE_LIST *first_not_own_table = thd->lex->first_not_own_table();
|
|
|
|
for (TABLE_LIST *table = tables; table != first_not_own_table && table;
|
|
table = table->next_global) {
|
|
if (is_temporary_table(table)) continue;
|
|
|
|
if (check_table_access(thd, LOCK_TABLES_ACL | SELECT_ACL, table, false, 1,
|
|
false))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
CREATE TABLE query pre-check.
|
|
|
|
@param thd Thread handler
|
|
@param tables Global table list
|
|
@param create_table Table which will be created
|
|
|
|
@retval
|
|
false OK
|
|
@retval
|
|
true Error
|
|
*/
|
|
|
|
bool create_table_precheck(THD *thd, TABLE_LIST *tables,
|
|
TABLE_LIST *create_table) {
|
|
LEX *lex = thd->lex;
|
|
SELECT_LEX *select_lex = lex->select_lex;
|
|
ulong want_priv;
|
|
bool error = true; // Error message is given
|
|
DBUG_TRACE;
|
|
|
|
/*
|
|
Require CREATE [TEMPORARY] privilege on new table; for
|
|
CREATE TABLE ... SELECT, also require INSERT.
|
|
*/
|
|
|
|
want_priv =
|
|
(lex->create_info->options & HA_LEX_CREATE_TMP_TABLE)
|
|
? CREATE_TMP_ACL
|
|
: (CREATE_ACL | (select_lex->item_list.elements ? INSERT_ACL : 0));
|
|
|
|
if (check_access(thd, want_priv, create_table->db,
|
|
&create_table->grant.privilege,
|
|
&create_table->grant.m_internal, 0, 0))
|
|
goto err;
|
|
|
|
/* If it is a merge table, check privileges for merge children. */
|
|
if (lex->create_info->merge_list.first) {
|
|
/*
|
|
The user must have (SELECT_ACL | UPDATE_ACL | DELETE_ACL) on the
|
|
underlying base tables, even if there are temporary tables with the same
|
|
names.
|
|
|
|
From user's point of view, it might look as if the user must have these
|
|
privileges on temporary tables to create a merge table over them. This is
|
|
one of two cases when a set of privileges is required for operations on
|
|
temporary tables (see also CREATE TABLE).
|
|
|
|
The reason for this behavior stems from the following facts:
|
|
|
|
- For merge tables, the underlying table privileges are checked only
|
|
at CREATE TABLE / ALTER TABLE time.
|
|
|
|
In other words, once a merge table is created, the privileges of
|
|
the underlying tables can be revoked, but the user will still have
|
|
access to the merge table (provided that the user has privileges on
|
|
the merge table itself).
|
|
|
|
- Temporary tables shadow base tables.
|
|
|
|
I.e. there might be temporary and base tables with the same name, and
|
|
the temporary table takes the precedence in all operations.
|
|
|
|
- For temporary MERGE tables we do not track if their child tables are
|
|
base or temporary. As result we can't guarantee that privilege check
|
|
which was done in presence of temporary child will stay relevant later
|
|
as this temporary table might be removed.
|
|
|
|
If SELECT_ACL | UPDATE_ACL | DELETE_ACL privileges were not checked for
|
|
the underlying *base* tables, it would create a security breach as in
|
|
Bug#12771903.
|
|
*/
|
|
|
|
if (check_table_access(thd, SELECT_ACL | UPDATE_ACL | DELETE_ACL,
|
|
lex->create_info->merge_list.first, false, UINT_MAX,
|
|
false))
|
|
goto err;
|
|
}
|
|
|
|
if (want_priv != CREATE_TMP_ACL &&
|
|
check_grant(thd, want_priv, create_table, false, 1, false))
|
|
goto err;
|
|
|
|
if (select_lex->item_list.elements) {
|
|
/* Check permissions for used tables in CREATE TABLE ... SELECT */
|
|
if (tables &&
|
|
check_table_access(thd, SELECT_ACL, tables, false, UINT_MAX, false))
|
|
goto err;
|
|
} else if (lex->create_info->options & HA_LEX_CREATE_TABLE_LIKE) {
|
|
if (check_table_access(thd, SELECT_ACL, tables, false, UINT_MAX, false))
|
|
goto err;
|
|
}
|
|
|
|
if (check_fk_parent_table_access(thd, lex->create_info, lex->alter_info))
|
|
goto err;
|
|
|
|
error = false;
|
|
|
|
err:
|
|
return error;
|
|
}
|
|
|
|
/**
|
|
@brief Performs standardized check whether to prohibit (true)
|
|
or allow (false) operations based on read_only and super_read_only
|
|
state.
|
|
@param thd Thread handler
|
|
@param err_if_readonly Boolean indicating whether or not
|
|
to add the error to the thread context if read-only is
|
|
violated.
|
|
|
|
@returns Status code
|
|
@retval true The operation should be prohibited.
|
|
@ retval false The operation should be allowed.
|
|
*/
|
|
bool check_readonly(THD *thd, bool err_if_readonly) {
|
|
DBUG_TRACE;
|
|
|
|
/* read_only=OFF, do not prohibit operation: */
|
|
if (!opt_readonly) return false;
|
|
|
|
/*
|
|
Thread is replication slave or skip_read_only check is enabled for the
|
|
command, do not prohibit operation.
|
|
*/
|
|
if (thd->slave_thread || thd->is_cmd_skip_readonly()) return false;
|
|
|
|
/* Allowed recycle scheduler thread to purge recycled table */
|
|
if (thd->system_thread == SYSTEM_THREAD_RECYCLE_SCHEDULER) return false;
|
|
|
|
Security_context *sctx = thd->security_context();
|
|
bool is_super =
|
|
sctx->check_access(SUPER_ACL) ||
|
|
sctx->has_global_grant(STRING_WITH_LEN("CONNECTION_ADMIN")).first;
|
|
|
|
/* super_read_only=OFF and user has SUPER privilege,
|
|
do not prohibit operation:
|
|
*/
|
|
if (is_super && !opt_super_readonly) return false;
|
|
|
|
/* throw error in standardized way if requested: */
|
|
if (err_if_readonly) err_readonly(thd);
|
|
|
|
/* in all other cases, prohibit operation: */
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
@brief Generates appropriate error messages for read-only state
|
|
depending on whether user has SUPER privilege or not.
|
|
|
|
@param thd Thread handler
|
|
|
|
*/
|
|
void err_readonly(THD *thd) {
|
|
my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0),
|
|
thd->security_context()->check_access(SUPER_ACL) ||
|
|
thd->security_context()
|
|
->has_global_grant(STRING_WITH_LEN("CONNECTION_ADMIN"))
|
|
.first
|
|
? "--super-read-only"
|
|
: "--read-only");
|
|
}
|
|
|
|
/**
|
|
Check grants for commands which work only with one table and all other
|
|
tables belonging to subselects or implicitly opened tables.
|
|
|
|
@param thd Thread handler
|
|
@param privilege requested privilege
|
|
@param all_tables global table list of query
|
|
|
|
@returns false on success, true on access denied error
|
|
*/
|
|
|
|
bool check_one_table_access(THD *thd, ulong privilege, TABLE_LIST *all_tables) {
|
|
if (check_single_table_access(thd, privilege, all_tables, false)) return true;
|
|
|
|
// Check privileges on tables from subqueries and implicitly opened tables
|
|
TABLE_LIST *subquery_table;
|
|
TABLE_LIST *const view = all_tables->is_view() ? all_tables : NULL;
|
|
|
|
if ((subquery_table = all_tables->next_global)) {
|
|
/*
|
|
Access rights asked for the first table of a view should be the same
|
|
as for the view
|
|
*/
|
|
if (view && subquery_table->belong_to_view == view) {
|
|
if (check_single_table_access(thd, privilege, subquery_table, false))
|
|
return true; /* purecov: inspected */
|
|
subquery_table = subquery_table->next_global;
|
|
}
|
|
if (subquery_table && check_table_access(thd, SELECT_ACL, subquery_table,
|
|
false, UINT_MAX, false))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
Check grants for commands which work only with one table.
|
|
|
|
@param thd Thread handler
|
|
@param privilege requested privilege
|
|
@param all_tables global table list of query
|
|
@param no_errors false/true - report/don't report error to
|
|
the client (using my_error() call).
|
|
|
|
@retval
|
|
0 OK
|
|
@retval
|
|
1 access denied, error is sent to client
|
|
*/
|
|
|
|
bool check_single_table_access(THD *thd, ulong privilege,
|
|
TABLE_LIST *all_tables, bool no_errors) {
|
|
if (all_tables->is_internal()) {
|
|
// Optimizer internal tables does not need any privilege checking.
|
|
all_tables->set_privileges(privilege);
|
|
return false;
|
|
}
|
|
|
|
Security_context *backup_ctx = thd->security_context();
|
|
|
|
/* we need to switch to the saved context (if any) */
|
|
if (all_tables->security_ctx)
|
|
thd->set_security_context(all_tables->security_ctx);
|
|
|
|
const char *db_name;
|
|
if ((all_tables->is_view() || all_tables->field_translation) &&
|
|
!all_tables->schema_table)
|
|
db_name = all_tables->view_db.str;
|
|
else
|
|
db_name = all_tables->db;
|
|
|
|
if (check_access(thd, privilege, db_name, &all_tables->grant.privilege,
|
|
&all_tables->grant.m_internal, 0, no_errors))
|
|
goto deny;
|
|
|
|
/* Show only 1 table for check_grant */
|
|
if (!(all_tables->belong_to_view &&
|
|
(thd->lex->sql_command == SQLCOM_SHOW_FIELDS)) &&
|
|
check_grant(thd, privilege, all_tables, false, 1, no_errors))
|
|
goto deny;
|
|
|
|
thd->set_security_context(backup_ctx);
|
|
return 0;
|
|
|
|
deny:
|
|
thd->set_security_context(backup_ctx);
|
|
return 1;
|
|
}
|
|
|
|
bool check_routine_access(THD *thd, ulong want_access, const char *db,
|
|
char *name, bool is_proc, bool no_errors) {
|
|
DBUG_TRACE;
|
|
TABLE_LIST tables[1];
|
|
|
|
new (&tables[0]) TABLE_LIST();
|
|
tables->db = db;
|
|
tables->db_length = strlen(db);
|
|
tables->table_name = tables->alias = name;
|
|
tables->table_name_length = strlen(tables->table_name);
|
|
|
|
/*
|
|
The following test is just a shortcut for check_access() (to avoid
|
|
calculating db_access) under the assumption that it's common to
|
|
give persons global right to execute all stored SP (but not
|
|
necessary to create them).
|
|
Note that this effectively bypasses the ACL_internal_schema_access checks
|
|
that are implemented for the INFORMATION_SCHEMA and PERFORMANCE_SCHEMA,
|
|
which are located in check_access().
|
|
Since the I_S and P_S do not contain routines, this bypass is ok,
|
|
as long as this code path is not abused to create routines.
|
|
The assert enforce that.
|
|
*/
|
|
DBUG_ASSERT((want_access & CREATE_PROC_ACL) == 0);
|
|
if (thd->security_context()->check_access(want_access, db))
|
|
tables->grant.privilege = want_access;
|
|
else {
|
|
DBUG_PRINT("info", ("Checking routine %s.%s for schema level access.", db,
|
|
tables->table_name));
|
|
if (check_access(thd, want_access, db, &tables->grant.privilege,
|
|
&tables->grant.m_internal, 0, no_errors))
|
|
|
|
return true;
|
|
}
|
|
|
|
DBUG_PRINT("info", ("Checking routine %s.%s for routine level access.", db,
|
|
tables->table_name));
|
|
return check_grant_routine(thd, want_access, tables, is_proc, no_errors);
|
|
}
|
|
|
|
/**
|
|
Check if the given table has any of the asked privileges
|
|
|
|
@param thd Thread handler
|
|
@param want_access Bitmap of possible privileges to check for
|
|
@param table The table for which access needs to be validated
|
|
@retval
|
|
0 ok
|
|
@retval
|
|
1 error
|
|
*/
|
|
|
|
bool check_some_access(THD *thd, ulong want_access, TABLE_LIST *table) {
|
|
ulong access;
|
|
DBUG_TRACE;
|
|
|
|
/* This loop will work as long as we have less than 32 privileges */
|
|
for (access = 1; access < want_access; access <<= 1) {
|
|
if (access & want_access) {
|
|
if (!check_access(thd, access, table->db, &table->grant.privilege,
|
|
&table->grant.m_internal, 0, 1) &&
|
|
!check_grant(thd, access, table, false, 1, true))
|
|
return 0;
|
|
}
|
|
}
|
|
DBUG_PRINT("exit", ("no matching access rights"));
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
Check if the routine has any of the routine privileges.
|
|
|
|
@param thd Thread handler
|
|
@param db Database name
|
|
@param name Routine name
|
|
@param is_proc True if this is a SP rather than a function
|
|
@retval
|
|
0 ok
|
|
@retval
|
|
1 error
|
|
*/
|
|
|
|
bool check_some_routine_access(THD *thd, const char *db, const char *name,
|
|
bool is_proc) {
|
|
DBUG_TRACE;
|
|
ulong save_priv;
|
|
/*
|
|
The following test is just a shortcut for check_access() (to avoid
|
|
calculating db_access)
|
|
Note that this effectively bypasses the ACL_internal_schema_access checks
|
|
that are implemented for the INFORMATION_SCHEMA and PERFORMANCE_SCHEMA,
|
|
which are located in check_access().
|
|
Since the I_S and P_S do not contain routines, this bypass is ok,
|
|
as it only opens SHOW_PROC_ACLS.
|
|
*/
|
|
if (thd->security_context()->check_access(SHOW_PROC_ACLS, db ? db : "", true))
|
|
return false;
|
|
if (!check_access(thd, SHOW_PROC_ACLS, db, &save_priv, NULL, 0, 1) ||
|
|
(save_priv & SHOW_PROC_ACLS))
|
|
return false;
|
|
return check_routine_level_acl(thd, db, name, is_proc);
|
|
}
|
|
|
|
/**
|
|
@brief Compare requested privileges with the privileges acquired from the
|
|
User- and Db-tables.
|
|
@param thd Thread handler
|
|
@param want_access The requested access privileges.
|
|
@param db A pointer to the Db name.
|
|
@param[out] save_priv A pointer to the granted privileges will be stored.
|
|
@param grant_internal_info A pointer to the internal grant cache.
|
|
@param dont_check_global_grants True if no global grants are checked.
|
|
@param no_errors True if no errors should be sent to the client.
|
|
|
|
'save_priv' is used to save the User-table (global) and Db-table grants for
|
|
the supplied db name. Note that we don't store db level grants if the global
|
|
grants is enough to satisfy the request AND the global grants contains a
|
|
SELECT grant.
|
|
|
|
For internal databases (INFORMATION_SCHEMA, PERFORMANCE_SCHEMA),
|
|
additional rules apply, see ACL_internal_schema_access.
|
|
|
|
@see check_grant
|
|
|
|
@return Status of denial of access by exclusive ACLs.
|
|
@retval false Access can't exclusively be denied by Db- and User-table
|
|
access unless Column- and Table-grants are checked too.
|
|
@retval true Access denied. The DA is set if no_error = false!
|
|
*/
|
|
|
|
bool check_access(THD *thd, ulong want_access, const char *db, ulong *save_priv,
|
|
GRANT_INTERNAL_INFO *grant_internal_info,
|
|
bool dont_check_global_grants, bool no_errors) {
|
|
Security_context *sctx = thd->security_context();
|
|
ulong db_access;
|
|
const std::string &db_name = db ? db : "";
|
|
|
|
/*
|
|
GRANT command:
|
|
In case of database level grant the database name may be a pattern,
|
|
in case of table|column level grant the database name can not be a pattern.
|
|
We use 'dont_check_global_grants' as a flag to determine
|
|
if it's database level grant command
|
|
(see SQLCOM_GRANT case, mysql_execute_command() function) and
|
|
set db_is_pattern according to 'dont_check_global_grants' value.
|
|
*/
|
|
bool db_is_pattern = ((want_access & GRANT_ACL) && dont_check_global_grants);
|
|
ulong dummy;
|
|
DBUG_TRACE;
|
|
DBUG_PRINT("enter",
|
|
("db: %s want_access: %lu master_access: %lu", db_name.c_str(),
|
|
want_access, sctx->master_access(db_name)));
|
|
|
|
if (save_priv)
|
|
*save_priv = 0;
|
|
else {
|
|
save_priv = &dummy;
|
|
dummy = 0;
|
|
}
|
|
|
|
THD_STAGE_INFO(thd, stage_checking_permissions);
|
|
if ((!db || !db[0]) && !thd->db().str && !dont_check_global_grants) {
|
|
DBUG_PRINT("error", ("No database"));
|
|
if (!no_errors) my_error(ER_NO_DB_ERROR, MYF(0)); /* purecov: tested */
|
|
return true; /* purecov: tested */
|
|
}
|
|
|
|
if (db != NULL) {
|
|
if (db != any_db) {
|
|
const ACL_internal_schema_access *access;
|
|
access = get_cached_schema_access(grant_internal_info, db);
|
|
if (access) {
|
|
switch (access->check(want_access, save_priv)) {
|
|
case ACL_INTERNAL_ACCESS_GRANTED:
|
|
/*
|
|
All the privileges requested have been granted internally.
|
|
[out] *save_privileges= Internal privileges.
|
|
*/
|
|
return false;
|
|
case ACL_INTERNAL_ACCESS_DENIED:
|
|
if (!no_errors) {
|
|
my_error(ER_DBACCESS_DENIED_ERROR, MYF(0), sctx->priv_user().str,
|
|
sctx->priv_host().str, db);
|
|
}
|
|
return true;
|
|
case ACL_INTERNAL_ACCESS_CHECK_GRANT:
|
|
/*
|
|
Only some of the privilege requested have been granted internally,
|
|
proceed with the remaining bits of the request (want_access).
|
|
*/
|
|
want_access &= ~(*save_priv);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (sctx->check_access(want_access, db_name)) {
|
|
/*
|
|
1. If we don't have a global SELECT privilege, we have to get the
|
|
database specific access rights to be able to handle queries of type
|
|
UPDATE t1 SET a=1 WHERE b > 0
|
|
2. Change db access if it isn't current db which is being addressed
|
|
*/
|
|
if (!(sctx->check_access(SELECT_ACL, db_name))) {
|
|
if (db &&
|
|
(!thd->db().str || db_is_pattern || strcmp(db, thd->db().str))) {
|
|
if (sctx->get_active_roles()->size() > 0) {
|
|
bool use_patterns =
|
|
((want_access & GRANT_ACL) ? true : !dont_check_global_grants);
|
|
db_access = sctx->db_acl({db, strlen(db)}, use_patterns);
|
|
} else {
|
|
db_access = acl_get(thd, sctx->host().str, sctx->ip().str,
|
|
sctx->priv_user().str, db, db_is_pattern);
|
|
}
|
|
} else {
|
|
/* get access for current db */
|
|
db_access = sctx->current_db_access();
|
|
}
|
|
/*
|
|
The effective privileges are the union of the global privileges
|
|
and the intersection of db- and host-privileges,
|
|
plus the internal privileges.
|
|
*/
|
|
*save_priv |= sctx->master_access(db_name) | db_access;
|
|
} else
|
|
*save_priv |= sctx->master_access(db_name);
|
|
return false;
|
|
}
|
|
|
|
if (((want_access & ~sctx->master_access(db_name)) & ~DB_ACLS) ||
|
|
(!db && dont_check_global_grants)) { // We can never grant this
|
|
DBUG_PRINT("error", ("No possible access"));
|
|
if (!no_errors) {
|
|
if (thd->password == 2)
|
|
my_error(ER_ACCESS_DENIED_NO_PASSWORD_ERROR, MYF(0),
|
|
sctx->priv_user().str, sctx->priv_host().str);
|
|
else
|
|
my_error(ER_ACCESS_DENIED_ERROR, MYF(0), sctx->priv_user().str,
|
|
sctx->priv_host().str,
|
|
(thd->password ? ER_THD(thd, ER_YES)
|
|
: ER_THD(thd, ER_NO))); /* purecov: tested */
|
|
}
|
|
return true; /* purecov: tested */
|
|
}
|
|
|
|
if (db == any_db) {
|
|
/*
|
|
Access granted; Allow select on *any* db.
|
|
[out] *save_privileges= 0
|
|
*/
|
|
return false;
|
|
}
|
|
|
|
if (db && (!thd->db().str || db_is_pattern || strcmp(db, thd->db().str))) {
|
|
if (sctx->get_active_roles()->size() > 0) {
|
|
bool flags =
|
|
((want_access & GRANT_ACL) ? true : !dont_check_global_grants);
|
|
db_access = sctx->db_acl({db, strlen(db)}, flags);
|
|
DBUG_PRINT("info", ("check_access using db-level privilege for %s. "
|
|
"ACL: %lu",
|
|
db, db_access));
|
|
} else {
|
|
db_access = acl_get(thd, sctx->host().str, sctx->ip().str,
|
|
sctx->priv_user().str, db, db_is_pattern);
|
|
}
|
|
} else
|
|
db_access = sctx->current_db_access();
|
|
DBUG_PRINT("info",
|
|
("db_access: %lu want_access: %lu", db_access, want_access));
|
|
|
|
/*
|
|
Save the union of User-table and the intersection between Db-table and
|
|
Host-table privileges, with the already saved internal privileges.
|
|
*/
|
|
db_access = (db_access | sctx->master_access(db_name));
|
|
*save_priv |= db_access;
|
|
|
|
/*
|
|
We need to investigate column- and table access if all requested privileges
|
|
belongs to the bit set of .
|
|
*/
|
|
bool need_table_or_column_check =
|
|
(want_access & (TABLE_ACLS | PROC_ACLS | db_access)) == want_access;
|
|
/*
|
|
Grant access if the requested access is in the intersection of
|
|
host- and db-privileges (as retrieved from the acl cache),
|
|
also grant access if all the requested privileges are in the union of
|
|
TABLES_ACLS and PROC_ACLS; see check_grant.
|
|
*/
|
|
if (((db_access & want_access) == want_access) ||
|
|
(!dont_check_global_grants && need_table_or_column_check)) {
|
|
/*
|
|
Ok; but need to check table- and column privileges.
|
|
[out] *save_privileges is (User-priv | (Db-priv & Host-priv) |
|
|
Internal-priv)
|
|
*/
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
Access is denied;
|
|
[out] *save_privileges is (User-priv | (Db-priv & Host-priv) |
|
|
Internal-priv)
|
|
*/
|
|
DBUG_PRINT("error", ("Access denied"));
|
|
if (!no_errors)
|
|
my_error(ER_DBACCESS_DENIED_ERROR, MYF(0), sctx->priv_user().str,
|
|
sctx->priv_host().str,
|
|
(db ? db : (thd->db().str ? thd->db().str : "unknown")));
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
@brief Check if the requested privileges exists in either User-, DB- or,
|
|
tables- tables.
|
|
@param thd Thread context
|
|
@param requirements Privileges requested
|
|
@param tables List of tables to be compared against
|
|
@param no_errors Don't report error to the client (using my_error() call).
|
|
@param any_combination_of_privileges_will_do true if any privileges on any
|
|
column combination is enough.
|
|
@param number Only the first 'number' tables in the linked list are
|
|
relevant.
|
|
|
|
The supplied table list contains cached privileges. This functions calls the
|
|
help functions check_access and check_grant to verify the first three steps
|
|
in the privileges check queue:
|
|
1. Global privileges
|
|
2. OR (db privileges AND host privileges)
|
|
3. OR table privileges
|
|
4. OR column privileges (not checked by this function!)
|
|
5. OR routine privileges (not checked by this function!)
|
|
|
|
@see check_access
|
|
@see check_grant
|
|
|
|
@note This functions assumes that table list used and
|
|
thd->lex->query_tables_own_last value correspond to each other
|
|
(the latter should be either 0 or point to next_global member
|
|
of one of elements of this table list).
|
|
|
|
@return
|
|
@retval false OK
|
|
@retval true Access denied; But column or routine privileges might need to
|
|
be checked also.
|
|
*/
|
|
|
|
bool check_table_access(THD *thd, ulong requirements, TABLE_LIST *tables,
|
|
bool any_combination_of_privileges_will_do, uint number,
|
|
bool no_errors) {
|
|
DBUG_TRACE;
|
|
TABLE_LIST *org_tables = tables;
|
|
TABLE_LIST *first_not_own_table = thd->lex->first_not_own_table();
|
|
uint i = 0;
|
|
Security_context *sctx = thd->security_context();
|
|
Security_context *backup_ctx = thd->security_context();
|
|
|
|
/*
|
|
The check that first_not_own_table is not reached is for the case when
|
|
the given table list refers to the list for prelocking (contains tables
|
|
of other queries). For simple queries first_not_own_table is 0.
|
|
*/
|
|
for (; i < number && tables != first_not_own_table && tables;
|
|
tables = tables->next_global, i++) {
|
|
TABLE_LIST *const table_ref =
|
|
tables->correspondent_table ? tables->correspondent_table : tables;
|
|
ulong want_access = requirements;
|
|
if (table_ref->security_ctx)
|
|
sctx = table_ref->security_ctx;
|
|
else
|
|
sctx = backup_ctx;
|
|
|
|
/*
|
|
We should not encounter table list elements for reformed SHOW
|
|
statements unless this is first table list element in the main
|
|
select.
|
|
Such table list elements require additional privilege check
|
|
(see check_show_access()). This check is carried out by caller,
|
|
but only for the first table list element from the main select.
|
|
*/
|
|
DBUG_ASSERT(!table_ref->schema_table_reformed ||
|
|
table_ref == thd->lex->select_lex->table_list.first);
|
|
|
|
DBUG_PRINT("info",
|
|
("table: %s derived: %d view: %d", table_ref->table_name,
|
|
table_ref->is_derived(), table_ref->is_view()));
|
|
|
|
if (table_ref->is_internal()) continue;
|
|
|
|
thd->set_security_context(sctx);
|
|
|
|
if (check_access(thd, want_access, table_ref->get_db_name(),
|
|
&table_ref->grant.privilege, &table_ref->grant.m_internal,
|
|
0, no_errors))
|
|
goto deny;
|
|
}
|
|
thd->set_security_context(backup_ctx);
|
|
|
|
DBUG_EXECUTE_IF("force_check_table_access_return_ok", return false;);
|
|
return check_grant(thd, requirements, org_tables,
|
|
any_combination_of_privileges_will_do, number, no_errors);
|
|
deny:
|
|
thd->set_security_context(backup_ctx);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
Check if a current user has the privilege TABLE_ENCRYPTION_ADMIN required
|
|
to create encrypted table. We skip the same for slave threads.
|
|
|
|
@param thd Current thread
|
|
|
|
@retval false A user has the privilege TABLE_ENCRYPTION_ADMIN
|
|
@retval true A user doesn't have the privilege TABLE_ENCRYPTION_ADMIN
|
|
*/
|
|
|
|
bool check_table_encryption_admin_access(THD *thd) {
|
|
Security_context *sctx = thd->security_context();
|
|
|
|
/* replication slave thread can do anything */
|
|
if (thd->slave_thread) {
|
|
return false;
|
|
}
|
|
|
|
if (!sctx->has_global_grant(STRING_WITH_LEN("TABLE_ENCRYPTION_ADMIN"))
|
|
.first) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
Given a TABLE_LIST object this function checks against
|
|
1. global privileges
|
|
2. db privileges
|
|
3. table level privileges
|
|
|
|
This function only checks the existence of required ACL on a single table
|
|
object. No special consideration is made for the table type (derived, view,
|
|
temporary etc).
|
|
|
|
@param thd Thread handle
|
|
@param required_acl The privileges which are required to continue
|
|
@param table An initialized, single TABLE_LIST object
|
|
|
|
@return
|
|
@retval true Access is granted
|
|
@retval false Access denied
|
|
*/
|
|
|
|
bool is_granted_table_access(THD *thd, ulong required_acl, TABLE_LIST *table) {
|
|
DBUG_TRACE;
|
|
const char *table_name = table->get_table_name();
|
|
const char *db_name = table->get_db_name();
|
|
if (thd->security_context()->get_active_roles()->size() != 0) {
|
|
/* Check privilege against the role privilege cache */
|
|
ulong global_acl = thd->security_context()->master_access(db_name);
|
|
if ((global_acl & required_acl) == required_acl) {
|
|
DBUG_PRINT("info", ("Access granted for %s.%s by global privileges",
|
|
db_name, table_name));
|
|
return true;
|
|
}
|
|
ulong db_acl =
|
|
thd->security_context()->db_acl({db_name, strlen(db_name)}, true) |
|
|
global_acl;
|
|
if ((db_acl & required_acl) == required_acl) {
|
|
DBUG_PRINT("info", ("Access granted for %s.%s by schema privileges",
|
|
db_name, table_name));
|
|
return true;
|
|
}
|
|
|
|
Grant_table_aggregate aggr = thd->security_context()->table_and_column_acls(
|
|
{table_name, strlen(table_name)}, {db_name, strlen(db_name)});
|
|
if (((aggr.table_access | db_acl) & required_acl) == required_acl) {
|
|
DBUG_PRINT("info", ("Access granted for %s.%s by table privileges",
|
|
db_name, table_name));
|
|
return true;
|
|
}
|
|
} else {
|
|
/* No active roles */
|
|
Security_context *sctx = thd->security_context();
|
|
if ((sctx->master_access(db_name) & required_acl) == required_acl) {
|
|
DBUG_PRINT("info",
|
|
("(no role) Access granted for %s.%s by global privileges",
|
|
db_name, table_name));
|
|
return true;
|
|
}
|
|
ulong db_access;
|
|
if ((!thd->db().str || strcmp(db_name, thd->db().str)))
|
|
db_access = acl_get(thd, sctx->host().str, sctx->ip().str,
|
|
sctx->priv_user().str, db_name, false);
|
|
else
|
|
db_access = sctx->current_db_access();
|
|
db_access = (db_access | sctx->master_access(db_name));
|
|
if ((db_access & required_acl) == required_acl) {
|
|
DBUG_PRINT("info",
|
|
("(no role) Access granted for %s.%s by schema privileges",
|
|
db_name, table_name));
|
|
return true;
|
|
}
|
|
GRANT_TABLE *grant_table =
|
|
table_hash_search(sctx->host().str, sctx->ip().str, db_name,
|
|
sctx->priv_user().str, table_name, false);
|
|
if (!grant_table) return false;
|
|
|
|
if (((grant_table->privs | db_access) & required_acl) == required_acl) {
|
|
DBUG_PRINT("info",
|
|
("(no role) Access granted for %s.%s by table privileges",
|
|
db_name, table_name));
|
|
return true;
|
|
}
|
|
}
|
|
// permission denied
|
|
return false;
|
|
}
|
|
|
|
/****************************************************************************
|
|
Handle GRANT commands
|
|
****************************************************************************/
|
|
|
|
bool has_grant_role_privilege(THD *thd, const LEX_CSTRING &role_name,
|
|
const LEX_CSTRING &role_host) {
|
|
DBUG_TRACE;
|
|
Security_context *sctx = thd->security_context();
|
|
if (sctx->check_access(SUPER_ACL) ||
|
|
sctx->has_global_grant(STRING_WITH_LEN("ROLE_ADMIN")).first) {
|
|
DBUG_PRINT("info", ("`%s`@`%s` has with admin privileges for `%s`@`%s` "
|
|
"through super privileges or ROLE_ADMIN",
|
|
sctx->priv_user().str, sctx->priv_host().str,
|
|
role_name.str, role_host.str));
|
|
return true;
|
|
}
|
|
/*
|
|
1. user has global ROLE_ADMIN or SUPER_ACL privileges
|
|
2. user has inherited the GRANT r TO CURRENT_USER WITH ADMIN OPTION
|
|
privileges, where r is a node in some active role graph R granted to
|
|
CURRENT_USER.
|
|
*/
|
|
|
|
if (sctx->has_with_admin_acl(role_name, role_host)) {
|
|
DBUG_PRINT("info", ("`%s`@`%s` has with admin privileges for `%s`@`%s` by "
|
|
" WITH ADMIN from granted roles",
|
|
sctx->priv_user().str, sctx->priv_host().str,
|
|
role_name.str, role_host.str));
|
|
return true;
|
|
}
|
|
|
|
DBUG_PRINT("info", ("`%s`@`%s` doesn't have admin privileges for `%s`@`%s`",
|
|
sctx->priv_user().str, sctx->priv_host().str,
|
|
role_name.str, role_host.str));
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
Store table level and column level grants in the privilege tables
|
|
|
|
SYNOPSIS
|
|
mysql_table_grant()
|
|
thd Thread handle
|
|
table_list List of tables to give grant
|
|
user_list List of users to give grant
|
|
columns List of columns to give grant
|
|
rights Table level grant
|
|
revoke_grant Set to true if this is a REVOKE command
|
|
|
|
RETURN
|
|
false ok
|
|
true error
|
|
*/
|
|
|
|
int mysql_table_grant(THD *thd, TABLE_LIST *table_list,
|
|
List<LEX_USER> &user_list, List<LEX_COLUMN> &columns,
|
|
ulong rights, bool revoke_grant) {
|
|
ulong column_priv = 0;
|
|
List_iterator<LEX_USER> str_list(user_list);
|
|
LEX_USER *Str, *tmp_Str;
|
|
TABLE_LIST tables[ACL_TABLES::LAST_ENTRY];
|
|
const char *db_name, *table_name;
|
|
bool transactional_tables;
|
|
acl_table::Pod_user_what_to_update what_to_set;
|
|
bool result = false;
|
|
int ret = 0;
|
|
std::set<LEX_USER *> existing_users;
|
|
|
|
DBUG_TRACE;
|
|
|
|
DBUG_ASSERT(initialized);
|
|
|
|
if (rights & ~TABLE_ACLS) {
|
|
my_error(ER_ILLEGAL_GRANT_FOR_TABLE, MYF(0));
|
|
return true;
|
|
}
|
|
|
|
if (!revoke_grant) {
|
|
if (columns.elements) {
|
|
class LEX_COLUMN *column;
|
|
List_iterator<LEX_COLUMN> column_iter(columns);
|
|
|
|
if (open_tables_for_query(thd, table_list, 0)) return true;
|
|
|
|
if (table_list->is_view()) {
|
|
if (table_list->resolve_derived(thd, false))
|
|
return true; /* purecov: inspected */
|
|
|
|
// Prepare a readonly (materialized) view for access to columns
|
|
if (table_list->setup_materialized_derived(thd))
|
|
return true; /* purecov: inspected */
|
|
}
|
|
while ((column = column_iter++)) {
|
|
uint unused_field_idx = NO_CACHED_FIELD_INDEX;
|
|
TABLE_LIST *dummy;
|
|
Field *f = find_field_in_table_ref(
|
|
thd, table_list, column->column.ptr(), column->column.length(),
|
|
column->column.ptr(), NULL, NULL, NULL,
|
|
// check that we have the
|
|
// to-be-granted privilege:
|
|
column->rights, false, &unused_field_idx, false, &dummy);
|
|
if (f == (Field *)0) {
|
|
my_error(ER_BAD_FIELD_ERROR, MYF(0), column->column.c_ptr(),
|
|
table_list->alias);
|
|
return true;
|
|
}
|
|
if (f == (Field *)-1) return true;
|
|
column_priv |= column->rights;
|
|
}
|
|
close_mysql_tables(thd);
|
|
} else {
|
|
if (!(rights & CREATE_ACL)) {
|
|
// We need at least a shared MDL lock on the table to be allowed
|
|
// to safely check its existence.
|
|
MDL_request mdl_request;
|
|
MDL_REQUEST_INIT(&mdl_request, MDL_key::TABLE, table_list->db,
|
|
table_list->table_name, MDL_SHARED, MDL_TRANSACTION);
|
|
if (thd->mdl_context.acquire_lock(&mdl_request,
|
|
thd->variables.lock_wait_timeout))
|
|
return true;
|
|
|
|
bool exists;
|
|
if (dd::table_exists(thd->dd_client(), table_list->db,
|
|
table_list->table_name, &exists))
|
|
return true;
|
|
|
|
if (!exists) {
|
|
my_error(ER_NO_SUCH_TABLE, MYF(0), table_list->db, table_list->alias);
|
|
return true;
|
|
}
|
|
}
|
|
ulong missing_privilege = rights & ~table_list->grant.privilege;
|
|
if (missing_privilege) {
|
|
char command[128];
|
|
get_privilege_desc(command, sizeof(command), missing_privilege);
|
|
my_error(ER_TABLEACCESS_DENIED_ERROR, MYF(0), command,
|
|
thd->security_context()->priv_user().str,
|
|
thd->security_context()->host_or_ip().str, table_list->alias);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
This statement will be replicated as a statement, even when using
|
|
row-based replication. The binlog state will be cleared here to
|
|
statement based replication and will be reset to the originals
|
|
values when we are out of this function scope
|
|
*/
|
|
Save_and_Restore_binlog_format_state binlog_format_state(thd);
|
|
|
|
/*
|
|
The lock api is depending on the thd->lex variable which needs to be
|
|
re-initialized.
|
|
*/
|
|
Query_tables_list backup;
|
|
thd->lex->reset_n_backup_query_tables_list(&backup);
|
|
/*
|
|
Restore Query_tables_list::sql_command value, which was reset
|
|
above, as the code writing query to the binary log assumes that
|
|
this value corresponds to the statement being executed.
|
|
*/
|
|
thd->lex->sql_command = backup.sql_command;
|
|
|
|
{ /* Critical Section */
|
|
Acl_cache_lock_guard acl_cache_lock(thd, Acl_cache_lock_mode::WRITE_MODE);
|
|
|
|
if ((ret = open_grant_tables(thd, tables, &transactional_tables))) {
|
|
thd->lex->restore_backup_query_tables_list(&backup);
|
|
return ret != 1; /* purecov: deadcode */
|
|
}
|
|
|
|
if (!acl_cache_lock.lock()) {
|
|
commit_and_close_mysql_tables(thd);
|
|
return true;
|
|
}
|
|
|
|
if (check_system_user_privilege(thd, user_list)) {
|
|
commit_and_close_mysql_tables(thd);
|
|
return true;
|
|
}
|
|
|
|
MEM_ROOT *old_root = thd->mem_root;
|
|
thd->mem_root = &memex;
|
|
grant_version++;
|
|
|
|
while ((tmp_Str = str_list++)) {
|
|
int error;
|
|
GRANT_TABLE *grant_table;
|
|
|
|
if (!(Str = get_current_user(thd, tmp_Str))) {
|
|
result = true;
|
|
continue;
|
|
}
|
|
|
|
Userhostpassword_list password_list;
|
|
if (set_and_validate_user_attributes(
|
|
thd, Str, what_to_set, false, false,
|
|
&tables[ACL_TABLES::TABLE_PASSWORD_HISTORY], NULL,
|
|
revoke_grant ? "REVOKE" : "GRANT", password_list)) {
|
|
result = true;
|
|
continue;
|
|
}
|
|
|
|
ACL_USER *this_user = find_acl_user(Str->host.str, Str->user.str, true);
|
|
if (this_user && (what_to_set.m_what & PLUGIN_ATTR))
|
|
existing_users.insert(tmp_Str);
|
|
|
|
db_name = table_list->get_db_name();
|
|
thd->add_to_binlog_accessed_dbs(db_name); // collecting db:s for MTS
|
|
table_name = table_list->get_table_name();
|
|
|
|
/* Find/create cached table grant */
|
|
grant_table = table_hash_search(Str->host.str, NullS, db_name,
|
|
Str->user.str, table_name, 1);
|
|
if (!grant_table) {
|
|
if (revoke_grant) {
|
|
my_error(ER_NONEXISTING_TABLE_GRANT, MYF(0), Str->user.str,
|
|
Str->host.str, table_list->table_name);
|
|
result = true;
|
|
continue;
|
|
}
|
|
|
|
DBUG_EXECUTE_IF("mysql_table_grant_out_of_memory",
|
|
DBUG_SET("+d,simulate_out_of_memory"););
|
|
grant_table = new (thd->mem_root)
|
|
GRANT_TABLE(Str->host.str, db_name, Str->user.str, table_name,
|
|
rights, column_priv);
|
|
DBUG_EXECUTE_IF("mysql_table_grant_out_of_memory",
|
|
DBUG_SET("-d,simulate_out_of_memory"););
|
|
|
|
if (!grant_table) {
|
|
result = true; /* purecov: deadcode */
|
|
break; /* purecov: deadcode */
|
|
}
|
|
column_priv_hash->emplace(
|
|
grant_table->hash_key,
|
|
unique_ptr_destroy_only<GRANT_TABLE>(grant_table));
|
|
}
|
|
|
|
/* If revoke_grant, calculate the new column privilege for tables_priv */
|
|
if (revoke_grant) {
|
|
class LEX_COLUMN *column;
|
|
List_iterator<LEX_COLUMN> column_iter(columns);
|
|
GRANT_COLUMN *grant_column;
|
|
|
|
/* Fix old grants */
|
|
while ((column = column_iter++)) {
|
|
grant_column = column_hash_search(grant_table, column->column.ptr(),
|
|
column->column.length());
|
|
if (grant_column) grant_column->rights &= ~(column->rights | rights);
|
|
}
|
|
/* scan trough all columns to get new column grant */
|
|
column_priv = 0;
|
|
for (const auto &key_and_value : grant_table->hash_columns) {
|
|
grant_column = key_and_value.second.get();
|
|
grant_column->rights &= ~rights; // Fix other columns
|
|
column_priv |= grant_column->rights;
|
|
}
|
|
} else {
|
|
column_priv |= grant_table->cols;
|
|
}
|
|
|
|
/* update table and columns */
|
|
|
|
// Hold on to grant_table if it gets deleted, since we use it below.
|
|
std::unique_ptr<GRANT_TABLE, Destroy_only<GRANT_TABLE>>
|
|
deleted_grant_table;
|
|
|
|
if ((error = replace_table_table(
|
|
thd, grant_table, &deleted_grant_table,
|
|
tables[ACL_TABLES::TABLE_TABLES_PRIV].table, *Str, db_name,
|
|
table_name, rights, column_priv, revoke_grant))) {
|
|
result = true;
|
|
if (error < 0) break;
|
|
|
|
continue;
|
|
}
|
|
|
|
if (tables[3].table) {
|
|
if ((error = replace_column_table(
|
|
thd, grant_table, tables[ACL_TABLES::TABLE_COLUMNS_PRIV].table,
|
|
*Str, columns, db_name, table_name, rights, revoke_grant))) {
|
|
result = true;
|
|
if (error < 0) break;
|
|
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
thd->mem_root = old_root;
|
|
|
|
DBUG_ASSERT(!result || thd->is_error());
|
|
|
|
result = log_and_commit_acl_ddl(thd, transactional_tables);
|
|
|
|
{
|
|
/* Notify audit plugin. We will ignore the return value. */
|
|
LEX_USER *existing_user;
|
|
for (LEX_USER *one_user : existing_users) {
|
|
if ((existing_user = get_current_user(thd, one_user)))
|
|
mysql_audit_notify(
|
|
thd, AUDIT_EVENT(MYSQL_AUDIT_AUTHENTICATION_CREDENTIAL_CHANGE),
|
|
thd->is_error(), existing_user->user.str, existing_user->host.str,
|
|
existing_user->plugin.str, is_role_id(existing_user), NULL, NULL);
|
|
}
|
|
}
|
|
get_global_acl_cache()->increase_version();
|
|
} /* Critical section */
|
|
|
|
if (!result) {
|
|
my_ok(thd);
|
|
/* Notify storage engines */
|
|
acl_notify_htons(thd, revoke_grant ? SQLCOM_REVOKE : SQLCOM_GRANT,
|
|
&user_list);
|
|
}
|
|
|
|
thd->lex->restore_backup_query_tables_list(&backup);
|
|
DEBUG_SYNC(thd, "after_table_grant_revoke");
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
Store routine level grants in the privilege tables
|
|
|
|
@param thd Thread handle
|
|
@param table_list List of routines to give grant
|
|
@param is_proc Is this a list of procedures?
|
|
@param user_list List of users to give grant
|
|
@param rights Table level grant
|
|
@param revoke_grant Is this is a REVOKE command?
|
|
@param write_to_binlog True if this statement should be written to binlog
|
|
|
|
@return
|
|
@retval false Success.
|
|
@retval true An error occurred.
|
|
*/
|
|
|
|
bool mysql_routine_grant(THD *thd, TABLE_LIST *table_list, bool is_proc,
|
|
List<LEX_USER> &user_list, ulong rights,
|
|
bool revoke_grant, bool write_to_binlog) {
|
|
List_iterator<LEX_USER> str_list(user_list);
|
|
LEX_USER *Str, *tmp_Str;
|
|
TABLE_LIST tables[ACL_TABLES::LAST_ENTRY];
|
|
const char *db_name, *table_name;
|
|
bool transactional_tables;
|
|
acl_table::Pod_user_what_to_update what_to_set;
|
|
bool result = false;
|
|
int ret;
|
|
std::set<LEX_USER *> existing_users;
|
|
|
|
DBUG_TRACE;
|
|
|
|
DBUG_ASSERT(initialized);
|
|
|
|
if (rights & ~PROC_ACLS) {
|
|
my_error(ER_ILLEGAL_GRANT_FOR_TABLE, MYF(0));
|
|
return true;
|
|
}
|
|
|
|
if (!revoke_grant) {
|
|
if (sp_exist_routines(thd, table_list, is_proc)) return true;
|
|
}
|
|
|
|
/*
|
|
This statement will be replicated as a statement, even when using
|
|
row-based replication. The binlog state will be cleared here to
|
|
statement based replication and will be reset to the originals
|
|
values when we are out of this function scope
|
|
*/
|
|
Save_and_Restore_binlog_format_state binlog_format_state(thd);
|
|
if ((ret = open_grant_tables(thd, tables, &transactional_tables)))
|
|
return ret != 1;
|
|
|
|
{ /* Critical section */
|
|
Acl_cache_lock_guard acl_cache_lock(thd, Acl_cache_lock_mode::WRITE_MODE);
|
|
|
|
if (!acl_cache_lock.lock()) {
|
|
commit_and_close_mysql_tables(thd);
|
|
return true;
|
|
}
|
|
|
|
if (check_system_user_privilege(thd, user_list)) {
|
|
commit_and_close_mysql_tables(thd);
|
|
return true;
|
|
}
|
|
|
|
MEM_ROOT *old_root = thd->mem_root;
|
|
thd->mem_root = &memex;
|
|
|
|
DBUG_PRINT("info", ("now time to iterate and add users"));
|
|
|
|
while ((tmp_Str = str_list++)) {
|
|
int error;
|
|
GRANT_NAME *grant_name;
|
|
|
|
if (!(Str = get_current_user(thd, tmp_Str))) {
|
|
result = true;
|
|
continue;
|
|
}
|
|
|
|
Userhostpassword_list password_list;
|
|
if (set_and_validate_user_attributes(
|
|
thd, Str, what_to_set, false, false,
|
|
&tables[ACL_TABLES::TABLE_PASSWORD_HISTORY], NULL,
|
|
revoke_grant ? "REVOKE" : "GRANT", password_list)) {
|
|
result = true;
|
|
continue;
|
|
}
|
|
|
|
ACL_USER *this_user = find_acl_user(Str->host.str, Str->user.str, true);
|
|
if (this_user && (what_to_set.m_what & PLUGIN_ATTR))
|
|
existing_users.insert(tmp_Str);
|
|
|
|
db_name = table_list->db;
|
|
if (write_to_binlog) thd->add_to_binlog_accessed_dbs(db_name);
|
|
table_name = table_list->table_name;
|
|
grant_name = routine_hash_search(Str->host.str, NullS, db_name,
|
|
Str->user.str, table_name, is_proc, 1);
|
|
if (!grant_name) {
|
|
if (revoke_grant) {
|
|
my_error(ER_NONEXISTING_PROC_GRANT, MYF(0), Str->user.str,
|
|
Str->host.str, table_name);
|
|
result = true;
|
|
continue;
|
|
}
|
|
grant_name = new (thd->mem_root) GRANT_NAME(
|
|
Str->host.str, db_name, Str->user.str, table_name, rights, true);
|
|
if (!grant_name) {
|
|
result = true;
|
|
break;
|
|
}
|
|
if (is_proc)
|
|
proc_priv_hash->emplace(
|
|
grant_name->hash_key,
|
|
unique_ptr_destroy_only<GRANT_NAME>(grant_name));
|
|
else
|
|
func_priv_hash->emplace(
|
|
grant_name->hash_key,
|
|
unique_ptr_destroy_only<GRANT_NAME>(grant_name));
|
|
}
|
|
|
|
if ((error = replace_routine_table(thd, grant_name, tables[4].table, *Str,
|
|
db_name, table_name, is_proc, rights,
|
|
revoke_grant))) {
|
|
result = true; // Remember error
|
|
if (error < 0) break;
|
|
|
|
continue;
|
|
}
|
|
}
|
|
thd->mem_root = old_root;
|
|
|
|
/*
|
|
mysql_routine_grant can be called in following scenarios:
|
|
1. As a part of GRANT statement
|
|
2. As a part of CREATE PROCEDURE/ROUTINE statement
|
|
|
|
In case of 2, even if we fail to grant permission on
|
|
newly created routine, it is not a critical error and
|
|
is suppressed by caller. Instead, a warning is thrown
|
|
to user.
|
|
|
|
So, if we are here and result is set to true, either of the following must
|
|
be true:
|
|
1. An error is set in THD
|
|
2. Current statement is SQLCOM_CREATE_PROCEDURE or
|
|
SQLCOM_CREATE_SPFUNCTION
|
|
|
|
So assert for the same.
|
|
*/
|
|
DBUG_ASSERT(!result || thd->is_error() ||
|
|
thd->lex->sql_command == SQLCOM_CREATE_PROCEDURE ||
|
|
thd->lex->sql_command == SQLCOM_CREATE_SPFUNCTION);
|
|
|
|
result = log_and_commit_acl_ddl(thd, transactional_tables, NULL, NULL,
|
|
result, write_to_binlog);
|
|
|
|
{
|
|
/* Notify audit plugin. We will ignore the return value. */
|
|
for (LEX_USER *one_user : existing_users) {
|
|
LEX_USER *existing_user;
|
|
if ((existing_user = get_current_user(thd, one_user)))
|
|
mysql_audit_notify(
|
|
thd, AUDIT_EVENT(MYSQL_AUDIT_AUTHENTICATION_CREDENTIAL_CHANGE),
|
|
thd->is_error(), existing_user->user.str, existing_user->host.str,
|
|
existing_user->plugin.str, is_role_id(existing_user), NULL, NULL);
|
|
}
|
|
}
|
|
get_global_acl_cache()->increase_version();
|
|
} /* Critical section */
|
|
|
|
/* Notify storage engines */
|
|
if (write_to_binlog && !result) {
|
|
acl_notify_htons(thd, revoke_grant ? SQLCOM_REVOKE : SQLCOM_GRANT,
|
|
&user_list);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
bool mysql_revoke_role(THD *thd, const List<LEX_USER> *users,
|
|
const List<LEX_USER> *roles) {
|
|
DBUG_TRACE;
|
|
|
|
/*
|
|
This statement will be replicated as a statement, even when using
|
|
row-based replication. The binlog state will be cleared here to
|
|
statement based replication and will be reset to the originals
|
|
values when we are out of this function scope
|
|
*/
|
|
Save_and_Restore_binlog_format_state binlog_format_state(thd);
|
|
TABLE_LIST tables[ACL_TABLES::LAST_ENTRY];
|
|
List_iterator<LEX_USER> users_it(const_cast<List<LEX_USER> &>(*users));
|
|
List_iterator<LEX_USER> roles_it(const_cast<List<LEX_USER> &>(*roles));
|
|
bool errors = false;
|
|
LEX_USER *lex_user;
|
|
TABLE *table = NULL;
|
|
int ret;
|
|
LEX_USER *role = 0;
|
|
bool transactional_tables;
|
|
if ((ret = open_grant_tables(thd, tables, &transactional_tables)))
|
|
return ret != 1; /* purecov: deadcode */
|
|
|
|
{ /* Critical section */
|
|
Acl_cache_lock_guard acl_cache_lock(thd, Acl_cache_lock_mode::WRITE_MODE);
|
|
|
|
if (!acl_cache_lock.lock()) {
|
|
commit_and_close_mysql_tables(thd);
|
|
return true;
|
|
}
|
|
|
|
if (check_system_user_privilege(thd, *users)) {
|
|
commit_and_close_mysql_tables(thd);
|
|
return true;
|
|
}
|
|
|
|
table = tables[ACL_TABLES::TABLE_ROLE_EDGES].table;
|
|
|
|
std::vector<Role_id> mandatory_roles;
|
|
get_mandatory_roles(&mandatory_roles);
|
|
while ((role = roles_it++) != 0) {
|
|
if (std::find_if(mandatory_roles.begin(), mandatory_roles.end(),
|
|
[&](const Role_id &id) -> bool {
|
|
Role_id id2(role->user, role->host);
|
|
return id == id2;
|
|
}) != mandatory_roles.end()) {
|
|
Role_id authid(role->user, role->host);
|
|
std::string out;
|
|
authid.auth_str(&out);
|
|
my_error(ER_MANDATORY_ROLE, MYF(0), out.c_str());
|
|
return true;
|
|
}
|
|
}
|
|
while ((lex_user = users_it++) && !errors) {
|
|
roles_it.rewind();
|
|
LEX_USER *role;
|
|
if (lex_user->user.str == 0) {
|
|
// HACK: We're using CURRENT_USER()
|
|
lex_user = get_current_user(thd, lex_user);
|
|
DBUG_PRINT("note", ("current user= %s@%s", lex_user->user.str,
|
|
lex_user->host.str));
|
|
}
|
|
|
|
ACL_USER *acl_user;
|
|
if ((acl_user = find_acl_user(lex_user->host.str, lex_user->user.str,
|
|
true)) == NULL) {
|
|
my_error(ER_UNKNOWN_AUTHID, MYF(0), lex_user->user.str,
|
|
lex_user->host.str);
|
|
errors = true;
|
|
break;
|
|
}
|
|
while ((role = roles_it++) && !errors) {
|
|
ACL_USER *acl_role;
|
|
if ((acl_role = find_acl_user(role->host.str, role->user.str, true)) ==
|
|
NULL) {
|
|
my_error(ER_UNKNOWN_AUTHID, MYF(0), role->user.str, role->host.str);
|
|
errors = true;
|
|
break;
|
|
} else {
|
|
DBUG_PRINT("info", ("User %s@%s will drop parent %s@%s",
|
|
acl_user->user, acl_user->host.get_host(),
|
|
role->user.str, role->host.str));
|
|
Security_context *sctx = thd->security_context();
|
|
if (sctx->can_operate_with({role}, consts::system_user)) {
|
|
my_error(ER_SPECIFIC_ACCESS_DENIED_ERROR, MYF(0),
|
|
consts::system_user.c_str());
|
|
errors = true;
|
|
break;
|
|
}
|
|
|
|
Auth_id_ref from_user = create_authid_from(role);
|
|
Auth_id_ref to_user = create_authid_from(acl_user);
|
|
errors = modify_role_edges_in_table(thd, table, from_user, to_user,
|
|
false, true);
|
|
if (errors) {
|
|
my_error(ER_FAILED_REVOKE_ROLE, MYF(0), role->user.str,
|
|
role->host.str);
|
|
break;
|
|
}
|
|
revoke_role(thd, acl_role, acl_user);
|
|
drop_default_role_policy(
|
|
thd, tables[ACL_TABLES::TABLE_DEFAULT_ROLES].table,
|
|
create_authid_from(acl_role), create_authid_from(acl_user));
|
|
}
|
|
}
|
|
}
|
|
DBUG_ASSERT(!errors || thd->is_error());
|
|
|
|
errors = log_and_commit_acl_ddl(thd, transactional_tables);
|
|
|
|
get_global_acl_cache()->increase_version();
|
|
} /* Critical section */
|
|
|
|
if (!errors) {
|
|
my_ok(thd);
|
|
/* Notify storage engines */
|
|
acl_notify_htons(thd, SQLCOM_REVOKE, users);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool has_dynamic_privilege_grant_option(Security_context *sctx,
|
|
std::string priv) {
|
|
return sctx->has_global_grant(priv.c_str(), priv.length()).second;
|
|
}
|
|
|
|
/**
|
|
Grants a list of roles to a list of users. Changes are persistent and written
|
|
in the mysql.roles_edges table.
|
|
|
|
@param thd Thread handler
|
|
@param users A list of authorization IDs
|
|
@param roles A list of authorization IDs
|
|
@param with_admin_opt True if the granted users should be able to pass on
|
|
the roles to other authorization IDs
|
|
|
|
@return Success state
|
|
@retval true An error occurred and the DA is set.
|
|
@retval false The operation was successful and DA is set.
|
|
*/
|
|
|
|
bool mysql_grant_role(THD *thd, const List<LEX_USER> *users,
|
|
const List<LEX_USER> *roles, bool with_admin_opt) {
|
|
DBUG_TRACE;
|
|
/*
|
|
This statement will be replicated as a statement, even when using
|
|
row-based replication. The binlog state will be cleared here to
|
|
statement based replication and will be reset to the originals
|
|
values when we are out of this function scope
|
|
*/
|
|
Save_and_Restore_binlog_format_state binlog_format_state(thd);
|
|
TABLE_LIST tables[ACL_TABLES::LAST_ENTRY];
|
|
List_iterator<LEX_USER> users_it(const_cast<List<LEX_USER> &>(*users));
|
|
bool errors = false;
|
|
LEX_USER *lex_user;
|
|
TABLE *table = NULL;
|
|
int ret;
|
|
bool transactional_tables;
|
|
|
|
if ((ret = open_grant_tables(thd, tables, &transactional_tables)))
|
|
return ret != 1; /* purecov: deadcode */
|
|
|
|
{ /* Critical section */
|
|
Acl_cache_lock_guard acl_cache_lock(thd, Acl_cache_lock_mode::WRITE_MODE);
|
|
|
|
if (!acl_cache_lock.lock()) {
|
|
commit_and_close_mysql_tables(thd);
|
|
return true;
|
|
}
|
|
|
|
if (check_system_user_privilege(thd, *users)) {
|
|
commit_and_close_mysql_tables(thd);
|
|
return true;
|
|
}
|
|
|
|
table = tables[6].table;
|
|
|
|
while ((lex_user = users_it++) && !errors) {
|
|
List_iterator<LEX_USER> roles_it(const_cast<List<LEX_USER> &>(*roles));
|
|
LEX_USER *role;
|
|
if (lex_user->user.str == 0) {
|
|
// HACK: We're using CURRENT_USER()
|
|
lex_user = get_current_user(thd, lex_user);
|
|
DBUG_PRINT("note", ("current user= %s@%s", lex_user->user.str,
|
|
lex_user->host.str));
|
|
} else if (lex_user->user.length == 0 || *(lex_user->user.str) == '\0') {
|
|
/* Granting roles to an anonymous user isn't allowed */
|
|
my_error(ER_CANNOT_GRANT_ROLES_TO_ANONYMOUS_USER, MYF(0));
|
|
return true;
|
|
}
|
|
|
|
ACL_USER *acl_user;
|
|
if ((acl_user = find_acl_user(lex_user->host.str, lex_user->user.str,
|
|
true)) == NULL) {
|
|
my_error(ER_UNKNOWN_AUTHID, MYF(0), lex_user->user.str,
|
|
lex_user->host.str);
|
|
return true;
|
|
}
|
|
while ((role = roles_it++) && !errors) {
|
|
ACL_USER *acl_role;
|
|
if (role->user.length == 0 || *(role->user.str) == '\0') {
|
|
/* Anonymous roles aren't allowed */
|
|
errors = true;
|
|
std::string user_str = create_authid_str_from(acl_user);
|
|
std::string role_str = create_authid_str_from(role);
|
|
my_error(ER_FAILED_ROLE_GRANT, MYF(0), role_str.c_str(),
|
|
user_str.c_str());
|
|
break;
|
|
} else if ((acl_role = find_acl_user(role->host.str, role->user.str,
|
|
true)) == NULL) {
|
|
my_error(ER_UNKNOWN_AUTHID, MYF(0), role->user.str, role->host.str);
|
|
errors = true;
|
|
break;
|
|
} else {
|
|
DBUG_PRINT("info", ("User %s@%s will inherit from %s@%s",
|
|
acl_user->user, acl_user->host.get_host(),
|
|
role->user.str, role->host.str));
|
|
Security_context *sctx = thd->security_context();
|
|
if (sctx->can_operate_with({role}, consts::system_user)) {
|
|
my_error(ER_SPECIFIC_ACCESS_DENIED_ERROR, MYF(0),
|
|
consts::system_user.c_str());
|
|
errors = true;
|
|
break;
|
|
}
|
|
grant_role(acl_role, acl_user, with_admin_opt);
|
|
Auth_id_ref from_user = create_authid_from(role);
|
|
Auth_id_ref to_user = create_authid_from(acl_user);
|
|
errors = modify_role_edges_in_table(thd, table, from_user, to_user,
|
|
with_admin_opt, false);
|
|
if (errors) {
|
|
std::string user_str = create_authid_str_from(acl_user);
|
|
std::string role_str = create_authid_str_from(role);
|
|
my_error(ER_FAILED_ROLE_GRANT, MYF(0), user_str.c_str(),
|
|
role_str.c_str());
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
DBUG_ASSERT(!errors || thd->is_error());
|
|
|
|
errors = log_and_commit_acl_ddl(thd, transactional_tables);
|
|
get_global_acl_cache()->increase_version();
|
|
} /* Critical section */
|
|
|
|
if (!errors) {
|
|
my_ok(thd);
|
|
/* Notify storage engines */
|
|
acl_notify_htons(thd, SQLCOM_GRANT, users);
|
|
}
|
|
|
|
return errors;
|
|
}
|
|
|
|
bool mysql_grant(THD *thd, const char *db, List<LEX_USER> &list, ulong rights,
|
|
bool revoke_grant, bool is_proxy,
|
|
const List<LEX_CSTRING> &dynamic_privilege,
|
|
bool grant_all_current_privileges, LEX_GRANT_AS *grant_as) {
|
|
List_iterator<LEX_USER> str_list(list);
|
|
LEX_USER *user, *target_user, *proxied_user = NULL;
|
|
TABLE_LIST tables[ACL_TABLES::LAST_ENTRY];
|
|
bool transactional_tables;
|
|
acl_table::Pod_user_what_to_update what_to_set;
|
|
bool error = false;
|
|
int ret;
|
|
TABLE *dynpriv_table;
|
|
std::set<LEX_USER *> existing_users;
|
|
bool partial_revokes = false;
|
|
const List<LEX_CSTRING> *granted_dynamic_privs = &dynamic_privilege;
|
|
DBUG_TRACE;
|
|
DBUG_ASSERT(initialized);
|
|
|
|
if (is_proxy) {
|
|
DBUG_ASSERT(!db);
|
|
proxied_user = str_list++;
|
|
}
|
|
|
|
/*
|
|
This statement will be replicated as a statement, even when using
|
|
row-based replication. The binlog state will be cleared here to
|
|
statement based replication and will be reset to the originals
|
|
values when we are out of this function scope
|
|
*/
|
|
Save_and_Restore_binlog_format_state binlog_format_state(thd);
|
|
if ((ret = open_grant_tables(thd, tables, &transactional_tables)))
|
|
return ret != 1;
|
|
|
|
{ /* Critical section */
|
|
Acl_cache_lock_guard acl_cache_lock(thd, Acl_cache_lock_mode::WRITE_MODE);
|
|
if (!acl_cache_lock.lock()) {
|
|
commit_and_close_mysql_tables(thd);
|
|
return true;
|
|
}
|
|
|
|
bool with_grant_option = ((rights & GRANT_ACL) != 0);
|
|
bool grant_option = thd->lex->grant_privilege;
|
|
if (db == 0 && with_grant_option && (rights & ~GRANT_ACL) == 0 &&
|
|
dynamic_privilege.elements > 0) {
|
|
/*
|
|
If this is a grant on global privilege level and there only dynamic
|
|
privileges specified; don't apply the GRANT OPTION on a global privilege
|
|
level.
|
|
*/
|
|
rights = 0;
|
|
}
|
|
|
|
dynpriv_table = tables[ACL_TABLES::TABLE_DYNAMIC_PRIV].table;
|
|
Grant_validator grant_validator(
|
|
thd, db, list, rights, revoke_grant, dynamic_privilege,
|
|
grant_all_current_privileges, grant_as, dynpriv_table);
|
|
if (grant_validator.validate()) {
|
|
commit_and_close_mysql_tables(thd);
|
|
return true;
|
|
}
|
|
|
|
/* go through users in user_list */
|
|
grant_version++;
|
|
while ((target_user = str_list++)) {
|
|
if (!(user = get_current_user(thd, target_user))) {
|
|
error = true;
|
|
continue;
|
|
}
|
|
|
|
Userhostpassword_list password_list;
|
|
if (set_and_validate_user_attributes(
|
|
thd, user, what_to_set, false, false,
|
|
&tables[ACL_TABLES::TABLE_PASSWORD_HISTORY], NULL,
|
|
revoke_grant ? "REVOKE" : "GRANT", password_list)) {
|
|
error = true;
|
|
continue;
|
|
}
|
|
|
|
ACL_USER *this_user = find_acl_user(user->host.str, user->user.str, true);
|
|
Restrictions restrictions(nullptr);
|
|
DB_restrictions db_restrictions(nullptr);
|
|
ulong filtered_rights = rights;
|
|
std::unique_ptr<Restrictions_aggregator> aggregator =
|
|
Restrictions_aggregator_factory::create(thd, this_user, db, rights,
|
|
grant_all_current_privileges);
|
|
if (aggregator) {
|
|
partial_revokes = true;
|
|
if (aggregator->generate(db_restrictions)) {
|
|
error = true;
|
|
break;
|
|
}
|
|
restrictions.set_db(db_restrictions);
|
|
what_to_set.m_what |= USER_ATTRIBUTES;
|
|
what_to_set.m_user_attributes |= acl_table::USER_ATTRIBUTE_RESTRICTIONS;
|
|
}
|
|
if (this_user && (what_to_set.m_what & PLUGIN_ATTR))
|
|
existing_users.insert(target_user);
|
|
what_to_set.m_what |= ACCESS_RIGHTS_ATTR;
|
|
if ((ret = replace_user_table(thd, tables[ACL_TABLES::TABLE_USER].table,
|
|
user, (!db ? rights : 0), revoke_grant,
|
|
false, what_to_set, &restrictions))) {
|
|
error = true;
|
|
if (ret < 0) break;
|
|
|
|
continue;
|
|
}
|
|
/*
|
|
DB table operation is needed in either of the following cases :
|
|
- There is no partial revoke(s)
|
|
- Hybrid operation; Partial revokes filtered some of the access
|
|
for DB table
|
|
*/
|
|
else if (db && (aggregator == nullptr ||
|
|
aggregator->find_if_require_next_level_operation(
|
|
filtered_rights))) {
|
|
ulong db_rights = filtered_rights & DB_ACLS;
|
|
if (db_rights == filtered_rights) {
|
|
if ((ret = replace_db_table(thd, tables[ACL_TABLES::TABLE_DB].table,
|
|
db, *user, db_rights, revoke_grant))) {
|
|
error = true;
|
|
if (ret < 0) break;
|
|
|
|
continue;
|
|
}
|
|
thd->add_to_binlog_accessed_dbs(db);
|
|
} else {
|
|
my_error(ER_WRONG_USAGE, MYF(0), "DB GRANT", "GLOBAL PRIVILEGES");
|
|
error = true;
|
|
continue;
|
|
}
|
|
} else if (is_proxy) {
|
|
if ((ret = replace_proxies_priv_table(
|
|
thd, tables[5].table, user, proxied_user,
|
|
rights & GRANT_ACL ? true : false, revoke_grant))) {
|
|
error = true;
|
|
if (ret < 0) break;
|
|
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (!db &&
|
|
(dynamic_privilege.elements > 0 || grant_all_current_privileges)) {
|
|
LEX_CSTRING *priv;
|
|
Update_dynamic_privilege_table update_table(thd, dynpriv_table);
|
|
List<LEX_CSTRING> *privileges_to_check;
|
|
if (grant_all_current_privileges) {
|
|
/*
|
|
Copy all currently available dynamic privileges to the list of
|
|
dynamic privileges to grant.
|
|
*/
|
|
privileges_to_check = new (thd->mem_root) List<LEX_CSTRING>;
|
|
iterate_all_dynamic_privileges(thd, [&](const char *str) {
|
|
LEX_CSTRING *new_str =
|
|
(LEX_CSTRING *)thd->alloc(sizeof(LEX_CSTRING));
|
|
new_str->str = str;
|
|
new_str->length = strlen(str);
|
|
privileges_to_check->push_back(new_str);
|
|
return false;
|
|
});
|
|
granted_dynamic_privs = privileges_to_check;
|
|
} else
|
|
privileges_to_check =
|
|
&const_cast<List<LEX_CSTRING> &>(dynamic_privilege);
|
|
List_iterator<LEX_CSTRING> priv_it(*privileges_to_check);
|
|
while ((priv = priv_it++) && !error) {
|
|
/* We already checked privileges required to perform GRANT/REVOKE */
|
|
if (revoke_grant) {
|
|
error = revoke_dynamic_privilege(*priv, user->user, user->host,
|
|
update_table);
|
|
} else {
|
|
/* We performed SYSTEM_USER check before */
|
|
error = grant_dynamic_privilege(*priv, user->user, user->host,
|
|
with_grant_option, update_table);
|
|
}
|
|
if (error) {
|
|
/*
|
|
If the operation fails the DA might have been set already, but
|
|
if wasn't we can assume the dynamic privilege wasn't
|
|
registered in which case a syntax error is a reasonable response.
|
|
*/
|
|
if (!thd->get_stmt_da()->is_error())
|
|
my_error(ER_SYNTAX_ERROR, MYF(0));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!db && grant_option) {
|
|
bool error = 0;
|
|
Update_dynamic_privilege_table update_table(thd, dynpriv_table);
|
|
if (!revoke_grant)
|
|
error = grant_grant_option_for_all_dynamic_privileges(
|
|
user->user, user->host, update_table);
|
|
else
|
|
error = revoke_grant_option_for_all_dynamic_privileges(
|
|
user->user, user->host, update_table);
|
|
if (error) {
|
|
if (!thd->get_stmt_da()->is_error())
|
|
my_error(ER_SYNTAX_ERROR, MYF(0));
|
|
}
|
|
}
|
|
} // for each user
|
|
|
|
DBUG_ASSERT(!error || thd->is_error());
|
|
|
|
{
|
|
/*
|
|
We want to add AS ... clause while rewritting GRANT statement in
|
|
following cases:
|
|
|
|
1. The GRANT statement being executed contains AS clause.
|
|
In this case we retain it as it is except rewriting
|
|
CURRENT_USER() and use current session's user/host part.
|
|
2. If all of the following condtions are met:
|
|
- --partial_revokes is ON
|
|
- Statement is a GRANT at global level (*.*)
|
|
This is required because, current user may have restriction
|
|
list and grant may propagate the same to grantee. In order
|
|
to repliably replay this on other nodes in an HA setup, details
|
|
of current user has to be captured.
|
|
*/
|
|
LEX_GRANT_AS *grant_as_ptr = nullptr;
|
|
bool grant_as_specified = false;
|
|
LEX_GRANT_AS grant_as_for_rewrite;
|
|
if (grant_as->grant_as_used) {
|
|
grant_as_specified = grant_as->grant_as_used;
|
|
grant_as_ptr = grant_as;
|
|
} else if (partial_revokes && !revoke_grant && !is_proxy && !db) {
|
|
/* Set LEX_GRANT_AS for given GRANT */
|
|
grant_as_for_rewrite.grant_as_used = true;
|
|
|
|
grant_as_for_rewrite.role_type =
|
|
thd->security_context()->get_active_roles()->size()
|
|
? role_enum::ROLE_NAME
|
|
: role_enum::ROLE_NONE;
|
|
|
|
LEX_CSTRING priv_user = thd->security_context()->priv_user();
|
|
LEX_CSTRING priv_host = thd->security_context()->priv_host();
|
|
grant_as_for_rewrite.user = LEX_USER::alloc(
|
|
thd, (LEX_STRING *)&priv_user, (LEX_STRING *)&priv_host);
|
|
|
|
if (grant_as_for_rewrite.role_type == role_enum::ROLE_NAME) {
|
|
grant_as_for_rewrite.role_list = new (thd->mem_root) List<LEX_USER>;
|
|
thd->security_context()->get_active_roles(
|
|
thd, *grant_as_for_rewrite.role_list);
|
|
}
|
|
grant_as_specified = false;
|
|
grant_as_ptr = &grant_as_for_rewrite;
|
|
}
|
|
|
|
Grant_params grant_rewrite_params(grant_as_specified, grant_as_ptr);
|
|
|
|
error = log_and_commit_acl_ddl(thd, transactional_tables, nullptr,
|
|
&grant_rewrite_params);
|
|
}
|
|
|
|
{
|
|
/* Notify audit plugin. We will ignore the return value. */
|
|
LEX_USER *existing_user;
|
|
for (LEX_USER *one_user : existing_users) {
|
|
if ((existing_user = get_current_user(thd, one_user)))
|
|
mysql_audit_notify(
|
|
thd, AUDIT_EVENT(MYSQL_AUDIT_AUTHENTICATION_CREDENTIAL_CHANGE),
|
|
thd->is_error(), existing_user->user.str, existing_user->host.str,
|
|
existing_user->plugin.str, is_role_id(existing_user), NULL, NULL);
|
|
}
|
|
}
|
|
|
|
get_global_acl_cache()->increase_version();
|
|
} /* Critical section */
|
|
|
|
if (!error) {
|
|
my_ok(thd);
|
|
/* Notify storage engines */
|
|
acl_notify_htons(thd, revoke_grant ? SQLCOM_REVOKE : SQLCOM_GRANT, &list,
|
|
granted_dynamic_privs);
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
/**
|
|
@brief Check table level grants
|
|
|
|
@param thd Thread handler
|
|
@param want_access Bits of privileges user needs to have.
|
|
@param tables List of tables to check. The user should have
|
|
'want_access' to all tables in list.
|
|
@param any_combination_will_do true if it's enough to have any privilege for
|
|
any combination of the table columns.
|
|
@param number Check at most this number of tables.
|
|
@param no_errors true if no error should be sent directly to the client.
|
|
|
|
If table->grant.want_privilege != 0 then the requested privileges where
|
|
in the set of COL_ACLS but access was not granted on the table level. As
|
|
a consequence an extra check of column privileges is required.
|
|
|
|
Specifically if this function returns false the user has some kind of
|
|
privilege on a combination of columns in each table.
|
|
|
|
This function is usually preceeded by check_access which establish the
|
|
User-, Db- and Host access rights.
|
|
|
|
@see check_access
|
|
@see check_table_access
|
|
|
|
@note This functions assumes that either number of tables to be inspected
|
|
by it is limited explicitly (i.e. is is not UINT_MAX) or table list
|
|
used and thd->lex->query_tables_own_last value correspond to each
|
|
other (the latter should be either 0 or point to next_global member
|
|
of one of elements of this table list).
|
|
|
|
@return Access status
|
|
@retval false Access granted; But column privileges need to be checked.
|
|
@retval true The user did not have the requested privileges on any of the
|
|
tables.
|
|
|
|
*/
|
|
|
|
bool check_grant(THD *thd, ulong want_access, TABLE_LIST *tables,
|
|
bool any_combination_will_do, uint number, bool no_errors) {
|
|
TABLE_LIST *tl;
|
|
TABLE_LIST *const first_not_own_table = thd->lex->first_not_own_table();
|
|
Security_context *sctx = thd->security_context();
|
|
ulong orig_want_access = want_access;
|
|
std::vector<TABLE_LIST *> tables_to_be_processed_further;
|
|
DBUG_TRACE;
|
|
DBUG_ASSERT(number > 0);
|
|
|
|
for (tl = tables; tl && number-- && tl != first_not_own_table;
|
|
tl = tl->next_global) {
|
|
TABLE_LIST *const t_ref =
|
|
tl->correspondent_table ? tl->correspondent_table : tl;
|
|
sctx = (t_ref->security_ctx != nullptr) ? t_ref->security_ctx
|
|
: thd->security_context();
|
|
const char *db_name = t_ref->get_db_name();
|
|
const ACL_internal_table_access *access = get_cached_table_access(
|
|
&t_ref->grant.m_internal, db_name, t_ref->get_table_name());
|
|
|
|
if (access) {
|
|
switch (access->check(orig_want_access, &t_ref->grant.privilege)) {
|
|
case ACL_INTERNAL_ACCESS_GRANTED:
|
|
/*
|
|
Grant all access to the table to skip column checks.
|
|
Depend on the controls in the P_S table itself.
|
|
*/
|
|
t_ref->grant.privilege |= TMP_TABLE_ACLS;
|
|
continue;
|
|
case ACL_INTERNAL_ACCESS_DENIED:
|
|
goto err;
|
|
case ACL_INTERNAL_ACCESS_CHECK_GRANT:
|
|
break;
|
|
}
|
|
}
|
|
|
|
want_access = orig_want_access;
|
|
want_access &= ~sctx->master_access(db_name);
|
|
if (!want_access) continue; // ok
|
|
|
|
if (!(~t_ref->grant.privilege & want_access) || t_ref->is_internal() ||
|
|
t_ref->schema_table)
|
|
continue;
|
|
|
|
if (is_temporary_table(t_ref)) {
|
|
/*
|
|
If this table list element corresponds to a pre-opened temporary
|
|
table skip checking of all relevant table-level privileges for it.
|
|
Note that during creation of temporary table we still need to check
|
|
if user has CREATE_TMP_ACL.
|
|
*/
|
|
t_ref->grant.privilege |= TMP_TABLE_ACLS;
|
|
continue;
|
|
}
|
|
|
|
if (sctx->get_active_roles()->size() != 0) {
|
|
t_ref->grant.grant_table = 0;
|
|
t_ref->grant.version = grant_version;
|
|
|
|
Grant_table_aggregate aggr = sctx->table_and_column_acls(
|
|
{t_ref->get_db_name(), strlen(t_ref->get_db_name())},
|
|
{t_ref->get_table_name(), strlen(t_ref->get_table_name())});
|
|
|
|
DBUG_PRINT("info",
|
|
("Acl_map table %s.%s has access %lu", t_ref->get_db_name(),
|
|
t_ref->get_table_name(), aggr.table_access));
|
|
/*
|
|
For SHOW COLUMNS, SHOW INDEX it is enough to have some
|
|
privileges on any column combination on the table.
|
|
*/
|
|
if (any_combination_will_do && (aggr.cols != 0 || aggr.table_access != 0))
|
|
continue;
|
|
|
|
/*
|
|
For SHOW COLUMNS, SHOW INDEX it is enough to have some
|
|
privileges on any column combination on the table.
|
|
*/
|
|
if (any_combination_will_do) continue;
|
|
t_ref->grant.privilege = aggr.table_access;
|
|
if (!(~t_ref->grant.privilege & want_access)) {
|
|
DBUG_PRINT("info",
|
|
("Access not denied because of column acls for %s.%s."
|
|
"want_access= %lu, grant.privilege= %lu",
|
|
t_ref->get_db_name(), t_ref->get_table_name(), want_access,
|
|
t_ref->grant.privilege));
|
|
continue;
|
|
}
|
|
if (want_access & ~(aggr.cols | t_ref->grant.privilege)) {
|
|
want_access &= ~(aggr.cols | t_ref->grant.privilege);
|
|
DBUG_PRINT("info", ("Access denied for %s.%s. Unfulfilled access: %lu",
|
|
t_ref->get_db_name(), t_ref->get_table_name(),
|
|
want_access));
|
|
goto err;
|
|
}
|
|
} else {
|
|
/*
|
|
For these tables we have to access ACL caches.
|
|
We will first go over all TABLE_LIST objects and
|
|
create a list of objects to be processed further.
|
|
Later, we will access ACL caches for these tables.
|
|
It allows us to delay locking of ACL caches.
|
|
*/
|
|
tables_to_be_processed_further.push_back(tl);
|
|
} // end else
|
|
} // end for
|
|
|
|
if (!tables_to_be_processed_further.empty()) {
|
|
tl = 0;
|
|
Acl_cache_lock_guard acl_cache_lock(thd, Acl_cache_lock_mode::READ_MODE);
|
|
if (!acl_cache_lock.lock(!no_errors)) return true;
|
|
|
|
for (TABLE_LIST *tl_tmp : tables_to_be_processed_further) {
|
|
tl = tl_tmp;
|
|
TABLE_LIST *const t_ref =
|
|
tl->correspondent_table ? tl->correspondent_table : tl;
|
|
sctx = (t_ref->security_ctx != nullptr) ? t_ref->security_ctx
|
|
: thd->security_context();
|
|
const char *db_name = t_ref->get_db_name();
|
|
want_access = orig_want_access;
|
|
want_access &= ~sctx->master_access(db_name);
|
|
DBUG_ASSERT(want_access != 0);
|
|
|
|
GRANT_TABLE *grant_table = table_hash_search(
|
|
sctx->host().str, sctx->ip().str, db_name, sctx->priv_user().str,
|
|
t_ref->get_table_name(), false);
|
|
|
|
if (!grant_table) {
|
|
DBUG_PRINT("info",
|
|
("Table %s didn't exist in the legacy table acl cache",
|
|
t_ref->get_table_name()));
|
|
want_access &= ~t_ref->grant.privilege;
|
|
goto err; // No grants
|
|
}
|
|
|
|
/*
|
|
For SHOW COLUMNS, SHOW INDEX it is enough to have some
|
|
privileges on any column combination on the table.
|
|
*/
|
|
if (any_combination_will_do) continue;
|
|
|
|
t_ref->grant.grant_table = grant_table; // Remember for column test
|
|
t_ref->grant.version = grant_version;
|
|
t_ref->grant.privilege |= grant_table->privs;
|
|
|
|
DBUG_PRINT("info",
|
|
("t_ref->grant.privilege = %lu", t_ref->grant.privilege));
|
|
if (!(~t_ref->grant.privilege & want_access)) continue;
|
|
|
|
if (want_access & ~(grant_table->cols | t_ref->grant.privilege)) {
|
|
want_access &= ~(grant_table->cols | t_ref->grant.privilege);
|
|
goto err; // impossible
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
|
|
err:
|
|
|
|
if (!no_errors) // Not a silent skip of table
|
|
{
|
|
char command[128];
|
|
get_privilege_desc(command, sizeof(command), want_access);
|
|
my_error(ER_TABLEACCESS_DENIED_ERROR, MYF(0), command,
|
|
sctx->priv_user().str, sctx->host_or_ip().str,
|
|
tl ? tl->get_table_name() : "unknown");
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
Check column rights in given security context
|
|
|
|
SYNOPSIS
|
|
check_grant_column()
|
|
thd thread handler
|
|
grant grant information structure
|
|
db_name db name
|
|
table_name table name
|
|
name column name
|
|
length column name length
|
|
sctx security context
|
|
want_privilege wanted privileges
|
|
|
|
RETURN
|
|
false OK
|
|
true access denied
|
|
*/
|
|
|
|
bool check_grant_column(THD *thd, GRANT_INFO *grant, const char *db_name,
|
|
const char *table_name, const char *name, size_t length,
|
|
Security_context *sctx, ulong want_privilege) {
|
|
GRANT_TABLE *grant_table;
|
|
GRANT_COLUMN *grant_column;
|
|
DBUG_TRACE;
|
|
DBUG_PRINT("enter",
|
|
("table: %s want_privilege: %lu", table_name, want_privilege));
|
|
|
|
// Adjust wanted privileges based on privileges granted to table:
|
|
want_privilege &= ~grant->privilege;
|
|
if (!want_privilege) return false; // Already checked
|
|
Acl_cache_lock_guard acl_cache_lock(thd, Acl_cache_lock_mode::READ_MODE);
|
|
if (!acl_cache_lock.lock()) return true;
|
|
|
|
/* reload table if someone has modified any grants */
|
|
if (sctx->get_active_roles()->size() != 0) {
|
|
DBUG_ASSERT(grant->grant_table == 0);
|
|
Grant_table_aggregate agg = sctx->table_and_column_acls(
|
|
{(const char *)db_name, strlen(db_name)},
|
|
{(const char *)table_name, strlen(table_name)});
|
|
std::string q_name(name);
|
|
Column_map::iterator it = agg.columns.find(q_name);
|
|
if (it != agg.columns.end()) {
|
|
if (!(~(it->second) & want_privilege)) {
|
|
DBUG_PRINT("info", ("Sufficient column privileges found for %s.%s.%s",
|
|
db_name, table_name, name));
|
|
return false;
|
|
}
|
|
} else {
|
|
DBUG_PRINT("info", ("No column privileges found for %s.%s.%s", db_name,
|
|
table_name, name));
|
|
}
|
|
} else {
|
|
if (grant->version != grant_version) {
|
|
grant->grant_table = table_hash_search(
|
|
sctx->host().str, sctx->ip().str, db_name, sctx->priv_user().str,
|
|
table_name, 0); /* purecov: inspected */
|
|
grant->version = grant_version; /* purecov: inspected */
|
|
}
|
|
if (!(grant_table = grant->grant_table)) goto err; /* purecov: deadcode */
|
|
|
|
grant_column = column_hash_search(grant_table, name, length);
|
|
if (grant_column && !(~grant_column->rights & want_privilege)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
err:
|
|
char command[128];
|
|
get_privilege_desc(command, sizeof(command), want_privilege);
|
|
my_error(ER_COLUMNACCESS_DENIED_ERROR, MYF(0), command, sctx->priv_user().str,
|
|
sctx->host_or_ip().str, name, table_name);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
Check the privileges for a column depending on the type of table.
|
|
|
|
@param thd thread handler
|
|
@param table_ref table reference where to check the field
|
|
@param name name of field to check
|
|
@param length length of name
|
|
@param want_privilege wanted privileges
|
|
|
|
Check the privileges for a column depending on the type of table the column
|
|
belongs to. The function provides a generic interface to check column
|
|
privileges that hides the heterogeneity of the column representation -
|
|
whether it belongs to a view or a base table.
|
|
|
|
Notice that this function does not understand that a column from a view
|
|
reference must be checked for privileges both in the view and in the
|
|
underlying base table (or view) reference. This is the responsibility of
|
|
the caller.
|
|
|
|
Columns from temporary tables and derived tables are ignored by this function.
|
|
|
|
@returns false if success, true if error (access denied)
|
|
*/
|
|
|
|
bool check_column_grant_in_table_ref(THD *thd, TABLE_LIST *table_ref,
|
|
const char *name, size_t length,
|
|
ulong want_privilege) {
|
|
DBUG_TRACE;
|
|
GRANT_INFO *grant;
|
|
const char *db_name;
|
|
const char *table_name;
|
|
Security_context *sctx = (table_ref->security_ctx != nullptr)
|
|
? table_ref->security_ctx
|
|
: thd->security_context();
|
|
|
|
DBUG_ASSERT(want_privilege);
|
|
|
|
if (is_temporary_table(table_ref) || table_ref->is_internal()) {
|
|
// Temporary table or optimizer internal table: no need to evaluate
|
|
// privileges
|
|
return false;
|
|
} else if (table_ref->is_view() || table_ref->field_translation) {
|
|
/* View or derived information schema table. */
|
|
ulong view_privs;
|
|
grant = &(table_ref->grant);
|
|
db_name = table_ref->view_db.str;
|
|
table_name = table_ref->view_name.str;
|
|
if (table_ref->belong_to_view &&
|
|
thd->lex->sql_command == SQLCOM_SHOW_FIELDS) {
|
|
if (sctx->get_active_roles()->size() > 0) {
|
|
view_privs = sctx->table_acl({db_name, strlen(db_name)},
|
|
{table_name, strlen(table_name)});
|
|
DBUG_PRINT("info", ("Found role privileges for %s.%s : %lu", db_name,
|
|
table_name, view_privs));
|
|
} else
|
|
view_privs = get_column_grant(thd, grant, db_name, table_name, name);
|
|
if (view_privs & VIEW_ANY_ACL) {
|
|
return false;
|
|
}
|
|
my_error(ER_VIEW_NO_EXPLAIN, MYF(0));
|
|
return true;
|
|
}
|
|
} else if (table_ref->nested_join) {
|
|
bool error = false;
|
|
List_iterator<TABLE_LIST> it(table_ref->nested_join->join_list);
|
|
TABLE_LIST *table;
|
|
while (!error && (table = it++))
|
|
error |= check_column_grant_in_table_ref(thd, table, name, length,
|
|
want_privilege);
|
|
return error;
|
|
} else {
|
|
// Regular, persistent base table
|
|
grant = &table_ref->grant;
|
|
db_name = table_ref->db;
|
|
table_name = table_ref->table_name;
|
|
DBUG_ASSERT(strcmp(db_name, table_ref->table->s->db.str) == 0 &&
|
|
strcmp(table_name, table_ref->table->s->table_name.str) == 0);
|
|
}
|
|
|
|
if (check_grant_column(thd, grant, db_name, table_name, name, length, sctx,
|
|
want_privilege))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
@brief check if a query can access a set of columns
|
|
|
|
@param thd the current thread
|
|
@param want_access_arg the privileges requested
|
|
@param fields an iterator over the fields of a table reference.
|
|
@return Operation status
|
|
@retval 0 Success
|
|
@retval 1 Falure
|
|
@details This function walks over the columns of a table reference
|
|
The columns may originate from different tables, depending on the kind of
|
|
table reference, e.g. join, view.
|
|
For each table it will retrieve the grant information and will use it
|
|
to check the required access privileges for the fields requested from it.
|
|
*/
|
|
bool check_grant_all_columns(THD *thd, ulong want_access_arg,
|
|
Field_iterator_table_ref *fields) {
|
|
Security_context *sctx = thd->security_context();
|
|
ulong want_access = want_access_arg;
|
|
const char *table_name = nullptr;
|
|
const char *field_name = nullptr;
|
|
const char *db_name = nullptr;
|
|
GRANT_INFO *grant = nullptr;
|
|
GRANT_TABLE *grant_table = nullptr;
|
|
/*
|
|
Flag that gets set if privilege checking has to be performed on column
|
|
level.
|
|
*/
|
|
bool using_column_privileges = false;
|
|
bool has_roles = thd->security_context()->get_active_roles()->size() > 0;
|
|
Grant_table_aggregate aggr;
|
|
DEBUG_SYNC(thd, "in_check_grant_all_columns");
|
|
Acl_cache_lock_guard acl_cache_lock(thd, Acl_cache_lock_mode::READ_MODE);
|
|
if (!acl_cache_lock.lock()) return true;
|
|
|
|
for (; !fields->end_of_fields(); fields->next()) {
|
|
grant = fields->grant(); /* Get cached GRANT_INFO on field */
|
|
// Check the privileges at column level if table does not have wanted access
|
|
want_access = want_access_arg & ~grant->privilege;
|
|
if (want_access) {
|
|
field_name = fields->name();
|
|
table_name = fields->get_table_name();
|
|
db_name = fields->get_db_name();
|
|
if (has_roles) {
|
|
LEX_CSTRING str_db_name = {db_name, strlen(db_name)};
|
|
LEX_CSTRING str_table_name = {table_name, strlen(table_name)};
|
|
aggr = thd->security_context()->table_and_column_acls(str_db_name,
|
|
str_table_name);
|
|
/* Update it to reflect current role privileges */
|
|
grant->privilege = aggr.table_access;
|
|
/* Reduce remaining privilege requirements */
|
|
want_access = want_access_arg & ~grant->privilege;
|
|
/* Does any of the columns have the access we need? */
|
|
if (aggr.cols & want_access) {
|
|
/* Find our column */
|
|
std::string q_name(field_name);
|
|
Column_map::iterator it = aggr.columns.find(q_name);
|
|
if (it != aggr.columns.end()) using_column_privileges = true;
|
|
if (it == aggr.columns.end() || (~(it->second) & want_access)) {
|
|
DBUG_PRINT("info", ("No column privileges found for %s.%s.%s",
|
|
db_name, table_name, field_name));
|
|
goto err;
|
|
}
|
|
DBUG_PRINT("info", ("Sufficient column privileges found for %s.%s.%s",
|
|
db_name, table_name, field_name));
|
|
}
|
|
} else {
|
|
/* reload table if someone has modified any grants */
|
|
if (grant->version != grant_version) {
|
|
grant->grant_table = table_hash_search(
|
|
sctx->host().str, sctx->ip().str, db_name, sctx->priv_user().str,
|
|
table_name, 0); /* purecov: inspected */
|
|
grant->version = grant_version; /* purecov: inspected */
|
|
}
|
|
grant_table = grant->grant_table;
|
|
if (grant_table == nullptr) goto err;
|
|
GRANT_COLUMN *grant_column =
|
|
column_hash_search(grant_table, field_name, strlen(field_name));
|
|
if (grant_column) using_column_privileges = true;
|
|
if (grant_column == nullptr || (~grant_column->rights & want_access))
|
|
goto err;
|
|
}
|
|
}
|
|
} // next field
|
|
return false;
|
|
|
|
err:
|
|
|
|
char command[128];
|
|
get_privilege_desc(command, sizeof(command), want_access);
|
|
/*
|
|
Do not give an error message listing a column name unless the user has
|
|
privilege to see all columns.
|
|
*/
|
|
if (using_column_privileges)
|
|
my_error(ER_TABLEACCESS_DENIED_ERROR, MYF(0), command,
|
|
sctx->priv_user().str, sctx->host_or_ip().str, table_name);
|
|
else
|
|
my_error(ER_COLUMNACCESS_DENIED_ERROR, MYF(0), command,
|
|
sctx->priv_user().str, sctx->host_or_ip().str, fields->name(),
|
|
table_name);
|
|
return true;
|
|
}
|
|
|
|
static bool check_grant_db_routine(
|
|
THD *thd, const char *db,
|
|
malloc_unordered_multimap<std::string, unique_ptr_destroy_only<GRANT_NAME>>
|
|
*hash) {
|
|
DBUG_TRACE;
|
|
Security_context *sctx = thd->security_context();
|
|
DBUG_ASSERT(assert_acl_cache_read_lock(thd));
|
|
|
|
if (sctx->get_active_roles()->size() != 0 && db != 0) {
|
|
DBUG_PRINT("info",
|
|
("Using roles Acl_map to detect schema level privileges"));
|
|
ulong acl = sctx->db_acl({db, strlen(db)});
|
|
return acl == 0;
|
|
} else {
|
|
for (const auto &key_and_value : *hash) {
|
|
GRANT_NAME *item = key_and_value.second.get();
|
|
|
|
if (strcmp(item->user, sctx->priv_user().str) == 0 &&
|
|
strcmp(item->db, db) == 0 &&
|
|
item->host.compare_hostname(sctx->host().str, sctx->ip().str)) {
|
|
return false;
|
|
}
|
|
} // end for
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool has_any_table_acl(Security_context *sctx, const LEX_CSTRING &str) {
|
|
if (sctx->get_active_roles()->size() > 0) {
|
|
return sctx->any_table_acl(str);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool has_any_routine_acl(Security_context *sctx, const LEX_CSTRING &db) {
|
|
if (sctx->get_active_roles()->size() > 0) {
|
|
return sctx->any_sp_acl(db);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
Check if a user has the right to access a database.
|
|
Access is accepted if the user has a database operations related grant
|
|
(i.e. not including the GRANT_ACL) for any table/column/routine in the
|
|
database.
|
|
|
|
@param thd The thread handler
|
|
@param db The name of the database
|
|
|
|
@return
|
|
@retval 1 Access is denied
|
|
@retval 0 Otherwise
|
|
*/
|
|
bool check_grant_db(THD *thd, const char *db) {
|
|
DBUG_TRACE;
|
|
Security_context *sctx = thd->security_context();
|
|
LEX_CSTRING priv_user = sctx->priv_user();
|
|
bool error = true;
|
|
|
|
if (sctx->get_active_roles()->size() > 0) {
|
|
size_t db_len = strlen(db);
|
|
ulong db_access = sctx->db_acl({db, db_len});
|
|
if ((db_access & DB_OP_ACLS) != 0) return 0;
|
|
return !has_any_table_acl(sctx, {db, db_len}) &&
|
|
!has_any_routine_acl(sctx, {db, db_len});
|
|
}
|
|
|
|
std::string key = to_string(priv_user);
|
|
key.push_back('\0');
|
|
key.append(db);
|
|
key.push_back('\0');
|
|
|
|
Acl_cache_lock_guard acl_cache_lock(thd, Acl_cache_lock_mode::READ_MODE);
|
|
if (!acl_cache_lock.lock()) return true;
|
|
|
|
for (const auto &key_and_value : *column_priv_hash) {
|
|
GRANT_TABLE *grant_table = key_and_value.second.get();
|
|
if (grant_table->hash_key.compare(0, key.size(), key) == 0 &&
|
|
grant_table->host.compare_hostname(sctx->host().str, sctx->ip().str) &&
|
|
((grant_table->privs | grant_table->cols) & TABLE_OP_ACLS)) {
|
|
error = false; /* Found match. */
|
|
DBUG_PRINT("info", ("Detected table level acl in column_priv_hash"));
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (error) {
|
|
DBUG_PRINT("info", ("No table level acl in column_priv_hash; checking "
|
|
"for schema level acls"));
|
|
error = check_grant_db_routine(thd, db, proc_priv_hash.get()) &&
|
|
check_grant_db_routine(thd, db, func_priv_hash.get());
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
/****************************************************************************
|
|
Check routine level grants
|
|
|
|
SYNPOSIS
|
|
bool check_grant_routine()
|
|
thd Thread handler
|
|
want_access Bits of privileges user needs to have
|
|
procs List of routines to check. The user should have 'want_access'
|
|
is_proc True if the list is all procedures, else functions
|
|
no_errors If 0 then we write an error. The error is sent directly to
|
|
the client
|
|
|
|
RETURN
|
|
false ok
|
|
true Error: User did not have the requested privielges
|
|
****************************************************************************/
|
|
|
|
bool check_grant_routine(THD *thd, ulong want_access, TABLE_LIST *procs,
|
|
bool is_proc, bool no_errors) {
|
|
TABLE_LIST *table;
|
|
Security_context *sctx = thd->security_context();
|
|
const char *user = sctx->priv_user().str;
|
|
const char *host = sctx->priv_host().str;
|
|
const std::string db_name(procs->db, procs->db_length);
|
|
bool has_roles = thd->security_context()->get_active_roles()->size() > 0;
|
|
DBUG_TRACE;
|
|
|
|
want_access &= ~sctx->master_access(db_name);
|
|
if (!want_access) return false; // ok
|
|
|
|
Acl_cache_lock_guard acl_cache_lock(thd, Acl_cache_lock_mode::READ_MODE);
|
|
if (!acl_cache_lock.lock()) return true;
|
|
|
|
for (table = procs; table; table = table->next_global) {
|
|
if (has_roles) {
|
|
ulong acl;
|
|
if (is_proc) {
|
|
acl =
|
|
sctx->procedure_acl({table->db, table->db_length},
|
|
{table->table_name, table->table_name_length});
|
|
} else {
|
|
acl = sctx->function_acl({table->db, table->db_length},
|
|
{table->table_name, table->table_name_length});
|
|
}
|
|
table->grant.privilege |= acl;
|
|
DBUG_PRINT("info",
|
|
("Checking Acl_map for proc acls in %s.%s; "
|
|
"found %lu",
|
|
table->db, table->table_name, table->grant.privilege));
|
|
} else {
|
|
GRANT_NAME *grant_proc;
|
|
if ((grant_proc =
|
|
routine_hash_search(host, sctx->ip().str, table->db, user,
|
|
table->table_name, is_proc, 0))) {
|
|
table->grant.privilege |= grant_proc->privs;
|
|
DBUG_PRINT("info", ("Checking for routine acls in %s; "
|
|
"found %lu",
|
|
table->db, grant_proc->privs));
|
|
}
|
|
}
|
|
if ((want_access & table->grant.privilege) != want_access) {
|
|
want_access &= ~table->grant.privilege;
|
|
goto err;
|
|
}
|
|
}
|
|
return false;
|
|
|
|
err:
|
|
if (!no_errors) {
|
|
char buff[1024];
|
|
const char *command = "";
|
|
if (table) strxmov(buff, table->db, ".", table->table_name, NullS);
|
|
if (want_access & EXECUTE_ACL)
|
|
command = "execute";
|
|
else if (want_access & ALTER_PROC_ACL)
|
|
command = "alter routine";
|
|
else if (want_access & GRANT_ACL)
|
|
command = "grant";
|
|
my_error(ER_PROCACCESS_DENIED_ERROR, MYF(0), command, user, host,
|
|
table ? buff : "unknown");
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
Check if routine has any of the
|
|
routine level grants
|
|
|
|
SYNPOSIS
|
|
bool check_routine_level_acl()
|
|
thd Thread handler
|
|
db Database name
|
|
name Routine name
|
|
|
|
RETURN
|
|
false Ok
|
|
true error
|
|
*/
|
|
|
|
static bool check_routine_level_acl(THD *thd, const char *db, const char *name,
|
|
bool is_proc) {
|
|
DBUG_TRACE;
|
|
bool no_routine_acl = 1;
|
|
GRANT_NAME *grant_proc;
|
|
Security_context *sctx = thd->security_context();
|
|
Acl_cache_lock_guard acl_cache_lock(thd, Acl_cache_lock_mode::READ_MODE);
|
|
if (!acl_cache_lock.lock(false)) return no_routine_acl;
|
|
|
|
if ((grant_proc =
|
|
routine_hash_search(sctx->priv_host().str, sctx->ip().str, db,
|
|
sctx->priv_user().str, name, is_proc, 0)))
|
|
no_routine_acl = !(grant_proc->privs & SHOW_PROC_ACLS);
|
|
return no_routine_acl;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
Functions to retrieve the grant for a table/column (for SHOW functions)
|
|
*****************************************************************************/
|
|
|
|
ulong get_table_grant(THD *thd, TABLE_LIST *table) {
|
|
ulong privilege;
|
|
Security_context *sctx = thd->security_context();
|
|
const char *db = table->db ? table->db : thd->db().str;
|
|
GRANT_TABLE *grant_table;
|
|
Acl_cache_lock_guard acl_cache_lock(thd, Acl_cache_lock_mode::READ_MODE);
|
|
|
|
if (!acl_cache_lock.lock(false)) return (NO_ACCESS);
|
|
|
|
grant_table = table_hash_search(sctx->host().str, sctx->ip().str, db,
|
|
sctx->priv_user().str, table->table_name, 0);
|
|
table->grant.grant_table = grant_table; // Remember for column test
|
|
table->grant.version = grant_version;
|
|
if (grant_table) table->grant.privilege |= grant_table->privs;
|
|
privilege = table->grant.privilege;
|
|
return privilege;
|
|
}
|
|
|
|
/*
|
|
Determine the access priviliges for a field.
|
|
|
|
SYNOPSIS
|
|
get_column_grant()
|
|
thd thread handler
|
|
grant grants table descriptor
|
|
db_name name of database that the field belongs to
|
|
table_name name of table that the field belongs to
|
|
field_name name of field
|
|
|
|
DESCRIPTION
|
|
The procedure may also modify: grant->grant_table and grant->version.
|
|
|
|
RETURN
|
|
The access priviliges for the field db_name.table_name.field_name
|
|
*/
|
|
|
|
ulong get_column_grant(THD *thd, GRANT_INFO *grant, const char *db_name,
|
|
const char *table_name, const char *field_name) {
|
|
GRANT_TABLE *grant_table;
|
|
GRANT_COLUMN *grant_column;
|
|
ulong priv;
|
|
Security_context *sctx = thd->security_context();
|
|
Acl_cache_lock_guard acl_cache_lock(thd, Acl_cache_lock_mode::READ_MODE);
|
|
|
|
if (!acl_cache_lock.lock(false)) return (NO_ACCESS);
|
|
|
|
/* reload table if someone has modified any grants */
|
|
/*
|
|
note: grant->grant_table is set iff we need to check column level routines
|
|
and table level privileges hasn't isn't enough to fulfill the requirement.
|
|
However, we might be using this routine to check table level privileges
|
|
too so we must still check these before we continue. Doh..
|
|
*/
|
|
if (sctx->get_active_roles()->size() != 0 && !grant->grant_table) {
|
|
priv = grant->privilege;
|
|
Grant_table_aggregate aggr = sctx->table_and_column_acls(
|
|
{(const char *)db_name, strlen(db_name)},
|
|
{(const char *)table_name, strlen(table_name)});
|
|
priv |= aggr.table_access;
|
|
/* Find our column */
|
|
std::string q_name;
|
|
q_name.append(field_name);
|
|
Column_map::iterator it = aggr.columns.find(std::string(q_name.c_str()));
|
|
if (it != aggr.columns.end()) {
|
|
priv |= it->second;
|
|
}
|
|
|
|
} else {
|
|
if (grant->version != grant_version) {
|
|
grant->grant_table = table_hash_search(
|
|
sctx->host().str, sctx->ip().str, db_name, sctx->priv_user().str,
|
|
table_name, 0); /* purecov: inspected */
|
|
grant->version = grant_version; /* purecov: inspected */
|
|
}
|
|
|
|
if (!(grant_table = grant->grant_table))
|
|
priv = grant->privilege;
|
|
else {
|
|
grant_column =
|
|
column_hash_search(grant_table, field_name, strlen(field_name));
|
|
if (!grant_column)
|
|
priv = (grant->privilege | grant_table->privs);
|
|
else
|
|
priv = (grant->privilege | grant_table->privs | grant_column->rights);
|
|
}
|
|
}
|
|
return priv;
|
|
}
|
|
|
|
/*
|
|
Make a clear-text version of the requested privilege.
|
|
*/
|
|
|
|
void get_privilege_desc(char *to, uint max_length, ulong access) {
|
|
uint pos;
|
|
char *start = to;
|
|
DBUG_ASSERT(max_length >= 30); // For end ', ' removal
|
|
|
|
if (access) {
|
|
max_length--; // Reserve place for end-zero
|
|
for (pos = 0; access; pos++, access >>= 1) {
|
|
if ((access & 1) &&
|
|
global_acls_vector[pos].length() + (uint)(to - start) < max_length) {
|
|
to = my_stpcpy(to, global_acls_vector[pos].c_str());
|
|
*to++ = ',';
|
|
*to++ = ' ';
|
|
}
|
|
}
|
|
to--; // Remove end ' '
|
|
to--; // Remove end ','
|
|
}
|
|
*to = 0;
|
|
}
|
|
|
|
/**
|
|
Iterate a string by comma separation and apply a function on each chunk
|
|
separated by the commas.
|
|
@param str The string to be iterated
|
|
@param f A function which will receive the comma separated strings.
|
|
|
|
*/
|
|
void iterate_comma_separated_quoted_string(
|
|
std::string str, const std::function<bool(const std::string)> &f) {
|
|
if (str.length() == 0) return;
|
|
std::string::iterator i = str.begin();
|
|
std::stringstream ss;
|
|
bool q1 = false;
|
|
bool q2 = false;
|
|
bool q3 = false;
|
|
while (i != str.end()) {
|
|
if (!q2 && !q3 && *i == '`') {
|
|
if (q1)
|
|
q1 = false;
|
|
else
|
|
q1 = true;
|
|
} else if (!q1 && !q3 && *i == '\'') {
|
|
if (q2)
|
|
q2 = false;
|
|
else
|
|
q2 = true;
|
|
} else if (!q1 && !q2 && *i == '\'') {
|
|
if (q3)
|
|
q3 = false;
|
|
else
|
|
q3 = true;
|
|
} else if (q1 == false && q2 == false && q3 == false && *i == ',') {
|
|
if (f(ss.str())) return;
|
|
ss.str("");
|
|
++i;
|
|
continue;
|
|
} else if (q1 == false && q2 == false && q3 == false && *i == ' ') {
|
|
++i;
|
|
continue;
|
|
}
|
|
ss << *i;
|
|
++i;
|
|
}
|
|
f(ss.str());
|
|
}
|
|
|
|
/**
|
|
Return the unquoted authorization id as a user,host-tuple
|
|
@param str The quoted or unquoted string representation of an authid
|
|
|
|
@return The unquoted authorization id as a user,host-tuple
|
|
*/
|
|
|
|
std::pair<std::string, std::string> get_authid_from_quoted_string(
|
|
std::string str) {
|
|
std::string::iterator i;
|
|
std::stringstream user;
|
|
std::stringstream host;
|
|
int ct = 0;
|
|
bool q1 = false;
|
|
bool q2 = false;
|
|
bool q3 = false;
|
|
for (i = str.begin(); i != str.end(); ++i) {
|
|
if (!q2 && !q3 && *i == '`') {
|
|
if (q1)
|
|
q1 = false;
|
|
else
|
|
q1 = true;
|
|
continue;
|
|
} else if (!q1 && !q3 && *i == '\'') {
|
|
if (q2)
|
|
q2 = false;
|
|
else
|
|
q2 = true;
|
|
continue;
|
|
} else if (!q1 && !q2 && *i == '"') {
|
|
if (q3)
|
|
q3 = false;
|
|
else
|
|
q3 = true;
|
|
continue;
|
|
} else if (q1 == false && q2 == false && q3 == false && *i == '@') {
|
|
++ct;
|
|
continue;
|
|
} else if (q1 == false && q2 == false && q3 == false && *i == ' ') {
|
|
continue;
|
|
}
|
|
if (ct == 0) {
|
|
user << *i;
|
|
} else {
|
|
host << *i;
|
|
}
|
|
}
|
|
if (ct == 0 && !user.str().empty()) host << '%';
|
|
return std::make_pair(user.str(), host.str());
|
|
}
|
|
|
|
bool operator==(const std::pair<Role_id, bool> &rid, const Auth_id_ref &ref) {
|
|
return (rid.first.user() == std::string(ref.first.str, ref.first.length) &&
|
|
rid.first.host() == std::string(ref.second.str, ref.second.length));
|
|
}
|
|
|
|
bool operator==(const Auth_id_ref &ref, const std::pair<Role_id, bool> &rid) {
|
|
return operator==(rid, ref);
|
|
}
|
|
|
|
void get_privilege_access_maps(
|
|
ACL_USER *acl_user, const List_of_auth_id_refs *using_roles, ulong *access,
|
|
Db_access_map *db_map, Db_access_map *db_wild_map,
|
|
Table_access_map *table_map, SP_access_map *sp_map, SP_access_map *func_map,
|
|
List_of_granted_roles *granted_roles, Grant_acl_set *with_admin_acl,
|
|
Dynamic_privileges *dynamic_acl, Restrictions &restrictions) {
|
|
DBUG_TRACE;
|
|
DBUG_ASSERT(assert_acl_cache_read_lock(current_thd));
|
|
List_of_auth_id_refs activated_roles_ref;
|
|
boost::graph_traits<Granted_roles_graph>::edge_iterator ei, ei_end;
|
|
/* First we check the current users access control */
|
|
// Get global access
|
|
*access = acl_user->access;
|
|
DBUG_PRINT("info", ("Global access for acl_user %s@%s is %lu", acl_user->user,
|
|
acl_user->host.get_host(), acl_user->access));
|
|
// Get database access
|
|
get_database_access_map(acl_user, db_map, db_wild_map);
|
|
// Get table- and column privileges
|
|
get_table_access_map(acl_user, table_map);
|
|
// get stored procedure privileges
|
|
get_sp_access_map(acl_user, sp_map, proc_priv_hash.get());
|
|
// get user function privileges
|
|
get_sp_access_map(acl_user, func_map, func_priv_hash.get());
|
|
// get dynamic privileges
|
|
get_dynamic_privileges(acl_user, dynamic_acl);
|
|
/* Find out the existing restrictions of the current user. */
|
|
restrictions = acl_restrictions->find_restrictions(acl_user);
|
|
|
|
/* We don't support role hierarchies for anonymous accounts. */
|
|
if (acl_user->user == nullptr) return;
|
|
|
|
/*
|
|
Temporarily apply the mandatory roles on this user for the sake of
|
|
generating an Acl_map.
|
|
*/
|
|
std::vector<Role_id> mandatory_roles;
|
|
std::vector<Role_vertex_descriptor> mandatory_roles_vertex_ids;
|
|
get_mandatory_roles(&mandatory_roles);
|
|
|
|
/* Only check roles if there are any granted roles at all */
|
|
boost::tie(ei, ei_end) = boost::edges(*g_granted_roles);
|
|
Role_vertex_descriptor user_vertex, active_role_vertex;
|
|
|
|
std::string user_key = create_authid_str_from(acl_user);
|
|
Role_index_map::iterator user_vertex_it = g_authid_to_vertex->find(user_key);
|
|
bool has_granted_roles = (ei != ei_end);
|
|
std::set<Role_id> granted_active_roles;
|
|
std::set<Role_id> all_granted_roles;
|
|
std::set<Role_id> all_active_roles;
|
|
boost::vector_property_map<boost::default_color_type> v_color(
|
|
boost::num_vertices(*g_granted_roles));
|
|
|
|
Get_access_maps vis(acl_user, access, db_map, db_wild_map, table_map, sp_map,
|
|
func_map, with_admin_acl, dynamic_acl, &restrictions);
|
|
if (has_granted_roles || mandatory_roles.size() > 0) {
|
|
bool acl_user_has_vertex = (user_vertex_it != g_authid_to_vertex->end());
|
|
if (!acl_user_has_vertex) return;
|
|
user_vertex = user_vertex_it->second;
|
|
if (acl_user_has_vertex) {
|
|
get_granted_roles(user_vertex, [&](const Role_id &rid, bool with_admin) {
|
|
all_granted_roles.insert(rid);
|
|
granted_roles->push_back(std::make_pair(rid, with_admin));
|
|
});
|
|
for (auto &rid : mandatory_roles) {
|
|
all_granted_roles.insert(rid);
|
|
}
|
|
for (auto &rid : *using_roles) {
|
|
Role_id id(rid.first, rid.second);
|
|
all_active_roles.insert(id);
|
|
}
|
|
|
|
std::set_intersection(
|
|
all_granted_roles.begin(), all_granted_roles.end(),
|
|
all_active_roles.begin(), all_active_roles.end(),
|
|
std::inserter(granted_active_roles, granted_active_roles.begin()));
|
|
int vertex_count = 0;
|
|
for (auto &&rid : granted_active_roles) {
|
|
String rolestr;
|
|
append_identifier(&rolestr, rid.user().c_str(), rid.user().length());
|
|
rolestr.append('@');
|
|
append_identifier(&rolestr, rid.host().c_str(), rid.host().length());
|
|
Role_index_map::iterator rindex =
|
|
g_authid_to_vertex->find(rolestr.c_ptr_quick());
|
|
if (rindex == g_authid_to_vertex->end()) {
|
|
THD *thd = current_thd;
|
|
if (thd) {
|
|
push_warning_printf(thd, Sql_condition::SL_WARNING,
|
|
ER_UNKNOWN_AUTHID,
|
|
"Illegal role %s@%s was ignored.",
|
|
rid.user().c_str(), rid.host().c_str());
|
|
}
|
|
continue; // next role
|
|
}
|
|
active_role_vertex = rindex->second;
|
|
if (vertex_count == 0) {
|
|
/* breadth_first_search will initialize our v_color vector for us */
|
|
boost::breadth_first_search(*g_granted_roles, active_role_vertex,
|
|
boost::color_map(v_color).visitor(vis));
|
|
} else {
|
|
/* breadth_first_visit will not reinitialize the v_color vector */
|
|
boost::breadth_first_visit(*g_granted_roles, active_role_vertex,
|
|
boost::color_map(v_color).visitor(vis));
|
|
++vertex_count;
|
|
}
|
|
|
|
/*
|
|
An active edge might have been granted WITH ADMIN; make sure
|
|
we update the temporary edge with this property.
|
|
*/
|
|
Role_edge_descriptor edge;
|
|
bool found;
|
|
boost::tie(edge, found) =
|
|
boost::edge(user_vertex, active_role_vertex, *g_granted_roles);
|
|
if (found) {
|
|
int with_admin_opt =
|
|
boost::get(boost::edge_capacity_t(), *g_granted_roles)[edge];
|
|
if (with_admin_opt) {
|
|
with_admin_acl->insert(std::string(rolestr.c_ptr()));
|
|
}
|
|
}
|
|
|
|
} // end for
|
|
} // if user_vertex_it != g_authid_to_vertex->end()
|
|
} // if has_granted_roles
|
|
DBUG_PRINT("info", ("Global access for role user %s@%s is %lu",
|
|
acl_user->user, acl_user->host.get_host(), *access));
|
|
}
|
|
|
|
/**
|
|
SHOW GRANTS FOR user USING [ALL | role [,role ...]]
|
|
@param thd
|
|
@param lex_user
|
|
@param using_roles An forward iterable container of LEX_STRING std::pair
|
|
@param show_mandatory_roles true means mandatory roles are listed
|
|
|
|
@return Success status
|
|
*/
|
|
bool mysql_show_grants(THD *thd, LEX_USER *lex_user,
|
|
const List_of_auth_id_refs &using_roles,
|
|
bool show_mandatory_roles) {
|
|
int error = 0;
|
|
ACL_USER *acl_user = NULL;
|
|
char buff[1024];
|
|
DBUG_TRACE;
|
|
|
|
DBUG_ASSERT(initialized);
|
|
Acl_cache_lock_guard acl_cache_lock(thd, Acl_cache_lock_mode::READ_MODE);
|
|
if (!acl_cache_lock.lock()) return true;
|
|
|
|
acl_user = find_acl_user(lex_user->host.str, lex_user->user.str, true);
|
|
if (!acl_user) {
|
|
my_error(ER_NONEXISTING_GRANT, MYF(0), lex_user->user.str,
|
|
lex_user->host.str);
|
|
return true;
|
|
}
|
|
|
|
std::vector<Role_id> mandatory_roles;
|
|
get_mandatory_roles(&mandatory_roles);
|
|
for (auto &role_ref : using_roles) {
|
|
List_of_granted_roles granted_roles;
|
|
get_granted_roles(lex_user, &granted_roles);
|
|
std::string authid(create_authid_str_from(role_ref));
|
|
if (find(granted_roles.begin(), granted_roles.end(), authid) ==
|
|
granted_roles.end()) {
|
|
if (std::find_if(mandatory_roles.begin(), mandatory_roles.end(),
|
|
[&](const Role_id &id) -> bool {
|
|
std::string id_str, rid_str;
|
|
id.auth_str(&id_str);
|
|
Role_id rid(role_ref.first, role_ref.second);
|
|
rid.auth_str(&rid_str);
|
|
return (Role_id(role_ref.first, role_ref.second) ==
|
|
id);
|
|
}) == mandatory_roles.end()) {
|
|
my_error(ER_ROLE_NOT_GRANTED, MYF(0), role_ref.first.str,
|
|
role_ref.second.str, lex_user->user.str, lex_user->host.str);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
Item_string *field = new Item_string("", 0, &my_charset_latin1);
|
|
List<Item> field_list;
|
|
field->max_length = 1024;
|
|
strxmov(buff, "Grants for ", lex_user->user.str, "@", lex_user->host.str,
|
|
NullS);
|
|
field->item_name.set(buff);
|
|
field_list.push_back(field);
|
|
if (thd->send_result_metadata(&field_list,
|
|
Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)) {
|
|
return true;
|
|
}
|
|
// aggregate over the active role and user privileges
|
|
Db_access_map db_map;
|
|
Db_access_map db_wild_map;
|
|
Table_access_map table_map;
|
|
SP_access_map sp_map;
|
|
SP_access_map func_map;
|
|
Grant_acl_set with_admin_acl;
|
|
Dynamic_privileges dynamic_acl;
|
|
List_of_granted_roles granted_roles;
|
|
Restrictions restrictions(thd->mem_root);
|
|
ulong access;
|
|
table_map.set_thd(thd);
|
|
get_privilege_access_maps(acl_user, &using_roles, &access, &db_map,
|
|
&db_wild_map, &table_map, &sp_map, &func_map,
|
|
&granted_roles, &with_admin_acl, &dynamic_acl,
|
|
restrictions);
|
|
String output;
|
|
make_global_privilege_statement(thd, access, acl_user, &output);
|
|
Protocol *protocol = thd->get_protocol();
|
|
protocol->start_row();
|
|
protocol->store_string(output.ptr(), output.length(), output.charset());
|
|
protocol->end_row();
|
|
|
|
make_dynamic_privilege_statement(thd, acl_user, protocol, dynamic_acl);
|
|
make_database_privilege_statement(thd, acl_user, protocol, db_map,
|
|
db_wild_map, restrictions.db());
|
|
make_table_privilege_statement(thd, acl_user, protocol, table_map);
|
|
make_sp_privilege_statement(thd, acl_user, protocol, sp_map, 0);
|
|
make_sp_privilege_statement(thd, acl_user, protocol, func_map, 1);
|
|
make_proxy_privilege_statement(thd, acl_user, protocol);
|
|
make_roles_privilege_statement(thd, acl_user, protocol, granted_roles,
|
|
show_mandatory_roles);
|
|
make_with_admin_privilege_statement(thd, acl_user, protocol, with_admin_acl,
|
|
granted_roles);
|
|
|
|
my_eof(thd);
|
|
return error;
|
|
}
|
|
|
|
void roles_graphml(THD *thd, String *str) {
|
|
Acl_cache_lock_guard acl_cache_lock(thd, Acl_cache_lock_mode::READ_MODE);
|
|
if (!acl_cache_lock.lock()) return;
|
|
boost::dynamic_properties dp;
|
|
dp.property("name", boost::get(boost::vertex_name_t(), *g_granted_roles));
|
|
dp.property("color", boost::get(boost::edge_capacity_t(), *g_granted_roles));
|
|
std::stringstream ss;
|
|
boost::write_graphml(ss, *g_granted_roles, dp, true);
|
|
std::string out = ss.str();
|
|
str->copy(out.c_str(), out.length(), system_charset_info);
|
|
}
|
|
|
|
/**
|
|
Remove db access privileges.
|
|
|
|
@param thd Current thread execution context.
|
|
@param table Pointer to a TABLE object for opened table mysql.db.
|
|
@param lex_user User information.
|
|
|
|
@return Operation result
|
|
@retval 0 OK.
|
|
@retval 1 Application error happen, it is allowed
|
|
continuing of operations.
|
|
@retval < 0 Engine error.
|
|
*/
|
|
|
|
static int remove_db_access_privileges(THD *thd, TABLE *table,
|
|
const LEX_USER &lex_user) {
|
|
ACL_DB *acl_db;
|
|
int revoked, result = 0;
|
|
|
|
/*
|
|
Because acl_dbs shrink and may re-order as privileges are removed,
|
|
removal occurs in a repeated loop until no more privileges are revoked.
|
|
*/
|
|
do {
|
|
for (revoked = 0, acl_db = acl_dbs->begin(); acl_db != acl_dbs->end();) {
|
|
const char *user, *host;
|
|
|
|
if (!(user = acl_db->user)) user = "";
|
|
if (!(host = acl_db->host.get_host())) host = "";
|
|
|
|
if (!strcmp(lex_user.user.str, user) &&
|
|
!strcmp(lex_user.host.str, host)) {
|
|
int ret =
|
|
replace_db_table(thd, table, acl_db->db, lex_user, ~(ulong)0, true);
|
|
if (!ret) {
|
|
/*
|
|
Don't increment loop variable as replace_db_table deleted the
|
|
current element in acl_dbs.
|
|
*/
|
|
revoked = 1;
|
|
continue;
|
|
} else if (ret < 0)
|
|
return ret; // Something went wrong
|
|
else
|
|
/*
|
|
For the case when replace_db_table() returns 1 we continue
|
|
iteration in order to remove all db access privileges. It is safe
|
|
since this function is called as part of handling the statement
|
|
REVOKE ALL.
|
|
*/
|
|
result = 1;
|
|
}
|
|
++acl_db;
|
|
}
|
|
} while (revoked);
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
Remove column access privileges.
|
|
|
|
@param thd Thread handler.
|
|
@param tables_priv_table Pointer to a TABLE object for opened table
|
|
mysql.tables_priv_table.
|
|
@param columns_priv_table Pointer to a TABLE object for opened table
|
|
mysql.columns_priv_table.
|
|
@param lex_user User information.
|
|
|
|
@return Operation result
|
|
@retval 0 OK.
|
|
@retval 1 Application error happen, it is allowed
|
|
continuing of operations.
|
|
@retval < 0 Engine error.
|
|
*/
|
|
|
|
static int remove_column_access_privileges(THD *thd, TABLE *tables_priv_table,
|
|
TABLE *columns_priv_table,
|
|
const LEX_USER &lex_user) {
|
|
bool revoked = false;
|
|
int result = 0;
|
|
/*
|
|
Remove column access.
|
|
Because column_priv_hash shrink and may re-order as privileges are removed,
|
|
removal occurs in a repeated loop until no more privileges are revoked.
|
|
*/
|
|
do {
|
|
revoked = false;
|
|
for (auto it = column_priv_hash->begin(), next_it = it;
|
|
it != column_priv_hash->end(); it = next_it) {
|
|
/*
|
|
Store an iterator pointing to the next element now, since
|
|
replace_table_table could delete elements, invalidating "it".
|
|
*/
|
|
next_it = next(it);
|
|
|
|
const char *user, *host;
|
|
GRANT_TABLE *grant_table = it->second.get();
|
|
if (!(user = grant_table->user)) user = "";
|
|
if (!(host = grant_table->host.get_host())) host = "";
|
|
|
|
if (!strcmp(lex_user.user.str, user) &&
|
|
!strcmp(lex_user.host.str, host)) {
|
|
// Hold on to grant_table if it gets deleted, since we use it below.
|
|
std::unique_ptr<GRANT_TABLE, Destroy_only<GRANT_TABLE>>
|
|
deleted_grant_table;
|
|
|
|
int ret = replace_table_table(
|
|
thd, grant_table, &deleted_grant_table, tables_priv_table, lex_user,
|
|
grant_table->db, grant_table->tname, ~(ulong)0, 0, true);
|
|
if (ret < 0) {
|
|
return ret;
|
|
} else if (ret > 0) {
|
|
/*
|
|
For the case when replace_table_table() returns 1 we continue
|
|
iteration in order to remove all column access privileges.
|
|
*/
|
|
result = 1;
|
|
revoked = true;
|
|
break;
|
|
} else {
|
|
if (!grant_table->cols) {
|
|
revoked = true;
|
|
break;
|
|
}
|
|
List<LEX_COLUMN> columns;
|
|
ret = replace_column_table(thd, grant_table, columns_priv_table,
|
|
lex_user, columns, grant_table->db,
|
|
grant_table->tname, ~(ulong)0, true);
|
|
if (!ret) {
|
|
revoked = true;
|
|
break;
|
|
}
|
|
/*
|
|
If we come there then the variable ret always has a value < 0 since
|
|
the actual argument 'columns' doesn't contain any elements
|
|
*/
|
|
DBUG_ASSERT(ret < 0);
|
|
|
|
return ret;
|
|
}
|
|
}
|
|
}
|
|
} while (revoked);
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
Remove procedure access privileges.
|
|
|
|
@param thd Thread handler.
|
|
@param procs_priv_table Pointer to a TABLE object for opened table
|
|
mysql.procs_priv_table.
|
|
@param lex_user User information.
|
|
|
|
@return Operation result.
|
|
@retval 0 OK.
|
|
@retval 1 Application error happen, it is allowed
|
|
continuing of operations.
|
|
@retval < 0 Engine error.
|
|
*/
|
|
|
|
static int remove_procedure_access_privileges(THD *thd, TABLE *procs_priv_table,
|
|
const LEX_USER &lex_user) {
|
|
/* Remove procedure access */
|
|
int result = 0;
|
|
bool revoked;
|
|
for (int is_proc = 0; is_proc < 2; is_proc++) do {
|
|
malloc_unordered_multimap<std::string,
|
|
unique_ptr_destroy_only<GRANT_NAME>> *hash =
|
|
is_proc ? proc_priv_hash.get() : func_priv_hash.get();
|
|
revoked = false;
|
|
for (auto it = hash->begin(), next_it = it; it != hash->end();
|
|
it = next_it) {
|
|
/*
|
|
Store an iterator pointing to the next element now, since
|
|
replace_routine_table could delete elements, invalidating "it".
|
|
*/
|
|
next_it = next(it);
|
|
|
|
const char *user, *host;
|
|
GRANT_NAME *grant_proc = it->second.get();
|
|
if (!(user = grant_proc->user)) user = "";
|
|
if (!(host = grant_proc->host.get_host())) host = "";
|
|
|
|
if (!strcmp(lex_user.user.str, user) &&
|
|
!strcmp(lex_user.host.str, host)) {
|
|
int ret = replace_routine_table(
|
|
thd, grant_proc, procs_priv_table, lex_user, grant_proc->db,
|
|
grant_proc->tname, is_proc, ~(ulong)0, true);
|
|
|
|
if (!ret) {
|
|
revoked = 1;
|
|
continue;
|
|
} else if (ret < 0)
|
|
return ret;
|
|
else
|
|
/*
|
|
For the case when replace_routine_table() returns 1 we continue
|
|
iteration in order to remove all procedure access privileges.
|
|
It is safe since this function is called as part of handling
|
|
the statement REVOKE ALL.
|
|
*/
|
|
result = 1;
|
|
}
|
|
}
|
|
} while (revoked);
|
|
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
Revoke all privileges from a list of users.
|
|
|
|
SYNOPSIS
|
|
mysql_revoke_all()
|
|
thd The current thread.
|
|
list The users to revoke all privileges from.
|
|
|
|
RETURN
|
|
> 0 Error. Error message already sent.
|
|
0 OK.
|
|
< 0 Error. Error message not yet sent.
|
|
*/
|
|
|
|
bool mysql_revoke_all(THD *thd, List<LEX_USER> &list) {
|
|
bool result = false;
|
|
TABLE_LIST tables[ACL_TABLES::LAST_ENTRY];
|
|
bool transactional_tables;
|
|
int ret = 0;
|
|
DBUG_TRACE;
|
|
|
|
/*
|
|
This statement will be replicated as a statement, even when using
|
|
row-based replication. The binlog state will be cleared here to
|
|
statement based replication and will be reset to the originals
|
|
values when we are out of this function scope
|
|
*/
|
|
Save_and_Restore_binlog_format_state binlog_format_state(thd);
|
|
if ((ret = open_grant_tables(thd, tables, &transactional_tables)))
|
|
return ret != 1;
|
|
|
|
{ /* Critical section */
|
|
Acl_cache_lock_guard acl_cache_lock(thd, Acl_cache_lock_mode::WRITE_MODE);
|
|
|
|
if (!acl_cache_lock.lock()) {
|
|
commit_and_close_mysql_tables(thd);
|
|
return true;
|
|
}
|
|
|
|
if (check_system_user_privilege(thd, list)) {
|
|
commit_and_close_mysql_tables(thd);
|
|
return true;
|
|
}
|
|
|
|
TABLE *dynpriv_table = tables[ACL_TABLES::TABLE_DYNAMIC_PRIV].table;
|
|
LEX_USER *lex_user, *tmp_lex_user;
|
|
List_iterator<LEX_USER> user_list(list);
|
|
|
|
while ((tmp_lex_user = user_list++)) {
|
|
ulong what_to_set = 0;
|
|
if (!(lex_user = get_current_user(thd, tmp_lex_user))) {
|
|
result = true;
|
|
continue;
|
|
}
|
|
ACL_USER *acl_user =
|
|
find_acl_user(lex_user->host.str, lex_user->user.str, true);
|
|
if (acl_user == nullptr) {
|
|
result = true;
|
|
continue;
|
|
}
|
|
|
|
Update_dynamic_privilege_table update_table(thd, dynpriv_table);
|
|
if ((result = revoke_all_dynamic_privileges(
|
|
lex_user->user, lex_user->host, update_table))) {
|
|
break;
|
|
}
|
|
/* copy password expire attributes to individual user */
|
|
lex_user->alter_status = thd->lex->alter_password;
|
|
|
|
acl_table::Pod_user_what_to_update what_to_update;
|
|
what_to_update.m_what = (what_to_set | ACCESS_RIGHTS_ATTR);
|
|
ulong rights = ~(ulong)0;
|
|
DB_restrictions db_restrictions(nullptr);
|
|
Restrictions restrictions(nullptr);
|
|
std::unique_ptr<Restrictions_aggregator> aggregator =
|
|
Restrictions_aggregator_factory::create(thd, acl_user, nullptr,
|
|
rights, false);
|
|
if (aggregator) {
|
|
if (aggregator->generate(db_restrictions)) {
|
|
result = true;
|
|
continue;
|
|
}
|
|
what_to_update.m_what |= USER_ATTRIBUTES;
|
|
what_to_update.m_user_attributes |=
|
|
acl_table::USER_ATTRIBUTE_RESTRICTIONS;
|
|
restrictions.set_db(db_restrictions);
|
|
}
|
|
if ((ret = replace_user_table(thd, tables[ACL_TABLES::TABLE_USER].table,
|
|
lex_user, rights, true, false,
|
|
what_to_update, &restrictions))) {
|
|
result = true;
|
|
if (ret < 0) break;
|
|
|
|
continue;
|
|
}
|
|
|
|
int ret1, ret2, ret3;
|
|
if ((ret1 = remove_db_access_privileges(
|
|
thd, tables[ACL_TABLES::TABLE_DB].table, *lex_user)) < 0 ||
|
|
(ret2 = remove_column_access_privileges(
|
|
thd, tables[ACL_TABLES::TABLE_TABLES_PRIV].table,
|
|
tables[ACL_TABLES::TABLE_COLUMNS_PRIV].table, *lex_user)) < 0 ||
|
|
(ret3 = remove_procedure_access_privileges(
|
|
thd, tables[ACL_TABLES::TABLE_PROCS_PRIV].table, *lex_user)) <
|
|
0) {
|
|
result = true; // Something went wrong
|
|
break;
|
|
} else if (ret1 || ret2 || ret3) {
|
|
result = true;
|
|
continue;
|
|
}
|
|
} // end while
|
|
|
|
DBUG_EXECUTE_IF("force_mysql_revoke_all_fail", { result = 1; });
|
|
|
|
if (result && !thd->is_error()) my_error(ER_REVOKE_GRANTS, MYF(0));
|
|
|
|
result = log_and_commit_acl_ddl(thd, transactional_tables);
|
|
get_global_acl_cache()->increase_version();
|
|
} /* Critical section */
|
|
|
|
/* Notify storage engines */
|
|
if (!result) {
|
|
acl_notify_htons(thd, SQLCOM_REVOKE_ALL, &list);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
If the defining user for a routine does not exist, then the ACL lookup
|
|
code should raise two errors which we should intercept. We convert the more
|
|
descriptive error into a warning, and consume the other.
|
|
|
|
If any other errors are raised, then we set a flag that should indicate
|
|
that there was some failure we should complain at a higher level.
|
|
*/
|
|
class Silence_routine_definer_errors : public Internal_error_handler {
|
|
public:
|
|
Silence_routine_definer_errors() : is_grave(false) {}
|
|
|
|
virtual bool handle_condition(THD *, uint sql_errno, const char *,
|
|
Sql_condition::enum_severity_level *level,
|
|
const char *) {
|
|
if (*level == Sql_condition::SL_ERROR) {
|
|
if (sql_errno == ER_NONEXISTING_PROC_GRANT) {
|
|
/* Convert the error into a warning. */
|
|
*level = Sql_condition::SL_WARNING;
|
|
return true;
|
|
} else
|
|
is_grave = true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool has_errors() const { return is_grave; }
|
|
|
|
private:
|
|
bool is_grave;
|
|
};
|
|
|
|
/**
|
|
Revoke privileges for all users on a stored procedure. Use an error handler
|
|
that converts errors about missing grants into warnings.
|
|
|
|
@param thd The current thread.
|
|
@param sp_db DB of the stored procedure
|
|
@param sp_name Name of the stored procedure
|
|
@param is_proc True if this is a SP rather than a function.
|
|
|
|
@retval
|
|
false OK.
|
|
@retval
|
|
true Error. Error message not yet sent.
|
|
*/
|
|
|
|
bool sp_revoke_privileges(THD *thd, const char *sp_db, const char *sp_name,
|
|
bool is_proc) {
|
|
bool revoked;
|
|
int int_result;
|
|
bool result = false;
|
|
TABLE_LIST tables[ACL_TABLES::LAST_ENTRY];
|
|
Silence_routine_definer_errors error_handler;
|
|
bool transactional_tables;
|
|
DBUG_TRACE;
|
|
|
|
if (0 != (int_result = open_grant_tables(thd, tables, &transactional_tables)))
|
|
return int_result != 1;
|
|
|
|
Acl_cache_lock_guard acl_cache_lock(thd, Acl_cache_lock_mode::WRITE_MODE);
|
|
if (!acl_cache_lock.lock()) {
|
|
commit_and_close_mysql_tables(thd);
|
|
return true;
|
|
}
|
|
|
|
/* Be sure to pop this before exiting this scope! */
|
|
thd->push_internal_handler(&error_handler);
|
|
|
|
/*
|
|
This statement will be replicated as a statement, even when using
|
|
row-based replication. The binlog state will be cleared here to
|
|
statement based replication and will be reset to the originals
|
|
values when we are out of this function scope
|
|
*/
|
|
Save_and_Restore_binlog_format_state binlog_format_state(thd);
|
|
|
|
/* Remove procedure access */
|
|
malloc_unordered_multimap<std::string, unique_ptr_destroy_only<GRANT_NAME>>
|
|
*hash = is_proc ? proc_priv_hash.get() : func_priv_hash.get();
|
|
do {
|
|
revoked = false;
|
|
for (auto it = hash->begin(), next_it = it; it != hash->end();
|
|
it = next_it) {
|
|
/*
|
|
Store an iterator pointing to the next element now, since
|
|
replace_routine_table could delete elements, invalidating "it".
|
|
*/
|
|
next_it = next(it);
|
|
GRANT_NAME *grant_proc = it->second.get();
|
|
if (!my_strcasecmp(&my_charset_utf8_bin, grant_proc->db, sp_db) &&
|
|
!my_strcasecmp(system_charset_info, grant_proc->tname, sp_name)) {
|
|
LEX_USER lex_user;
|
|
lex_user.user.str = grant_proc->user;
|
|
lex_user.user.length = strlen(grant_proc->user);
|
|
lex_user.host.str =
|
|
grant_proc->host.get_host() ? grant_proc->host.get_host() : "";
|
|
lex_user.host.length = grant_proc->host.get_host()
|
|
? strlen(grant_proc->host.get_host())
|
|
: 0;
|
|
|
|
int ret = replace_routine_table(
|
|
thd, grant_proc, tables[4].table, lex_user, grant_proc->db,
|
|
grant_proc->tname, is_proc, ~(ulong)0, true);
|
|
if (ret < 0) {
|
|
result = true;
|
|
revoked = false;
|
|
break;
|
|
} else if (ret == 0) {
|
|
revoked = true;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
} while (revoked);
|
|
|
|
/* We don't want to write to binlog or notify htons about this. */
|
|
result |= log_and_commit_acl_ddl(thd, transactional_tables, NULL, NULL,
|
|
result, false);
|
|
|
|
thd->pop_internal_handler();
|
|
return error_handler.has_errors() || result;
|
|
}
|
|
|
|
/**
|
|
Grant EXECUTE,ALTER privilege for a stored procedure
|
|
|
|
@param thd The current thread.
|
|
@param sp_db DB of the stored procedure.
|
|
@param sp_name Name of the stored procedure
|
|
@param is_proc True if this is a SP rather than a function
|
|
|
|
@return
|
|
@retval false Success
|
|
@retval true An error occurred. Error message not yet sent.
|
|
*/
|
|
|
|
bool sp_grant_privileges(THD *thd, const char *sp_db, const char *sp_name,
|
|
bool is_proc) {
|
|
TABLE_LIST tables[1];
|
|
List<LEX_USER> user_list;
|
|
bool result = true;
|
|
Dummy_error_handler error_handler;
|
|
|
|
DBUG_TRACE;
|
|
|
|
LEX_CSTRING sctx_user = thd->security_context()->priv_user();
|
|
LEX_CSTRING sctx_host = thd->security_context()->priv_host();
|
|
LEX_USER *combo =
|
|
LEX_USER::alloc(thd, (LEX_STRING *)&sctx_user, (LEX_STRING *)&sctx_host);
|
|
if (combo == nullptr) return true;
|
|
|
|
Acl_cache_lock_guard acl_cache_lock(thd, Acl_cache_lock_mode::READ_MODE);
|
|
if (!acl_cache_lock.lock()) return true;
|
|
|
|
ACL_USER *au = find_acl_user(combo->host.str, combo->user.str, false);
|
|
if (au == nullptr) {
|
|
result = true;
|
|
goto end;
|
|
}
|
|
|
|
acl_cache_lock.unlock();
|
|
|
|
new (&tables[0]) TABLE_LIST();
|
|
user_list.empty();
|
|
|
|
tables->db = sp_db;
|
|
tables->table_name = tables->alias = sp_name;
|
|
|
|
lex_string_strmake(thd->mem_root, &combo->user, combo->user.str,
|
|
strlen(combo->user.str));
|
|
lex_string_strmake(thd->mem_root, &combo->host, combo->host.str,
|
|
strlen(combo->host.str));
|
|
|
|
if (user_list.push_back(combo)) return true;
|
|
|
|
thd->lex->ssl_type = SSL_TYPE_NOT_SPECIFIED;
|
|
thd->lex->ssl_cipher = thd->lex->x509_subject = thd->lex->x509_issuer = 0;
|
|
memset(&thd->lex->mqh, 0, sizeof(thd->lex->mqh));
|
|
/* set default values */
|
|
thd->lex->alter_password.cleanup();
|
|
|
|
combo->alter_status = thd->lex->alter_password;
|
|
|
|
/*
|
|
Only care about whether the operation failed or succeeded
|
|
as all errors will be handled later.
|
|
*/
|
|
thd->push_internal_handler(&error_handler);
|
|
result = mysql_routine_grant(thd, tables, is_proc, user_list,
|
|
DEFAULT_CREATE_PROC_ACLS, false, false);
|
|
thd->pop_internal_handler();
|
|
end:
|
|
return result;
|
|
}
|
|
|
|
static bool update_schema_privilege(THD *thd, TABLE *table, char *buff,
|
|
const char *db, const char *t_name,
|
|
const char *column, size_t col_length,
|
|
const char *priv, size_t priv_length,
|
|
const char *is_grantable) {
|
|
int i = 2;
|
|
CHARSET_INFO *cs = system_charset_info;
|
|
DBUG_ASSERT(assert_acl_cache_read_lock(thd));
|
|
restore_record(table, s->default_values);
|
|
table->field[0]->store(buff, strlen(buff), cs);
|
|
table->field[1]->store(STRING_WITH_LEN("def"), cs);
|
|
if (db) table->field[i++]->store(db, strlen(db), cs);
|
|
if (t_name) table->field[i++]->store(t_name, strlen(t_name), cs);
|
|
if (column) table->field[i++]->store(column, col_length, cs);
|
|
table->field[i++]->store(priv, priv_length, cs);
|
|
table->field[i]->store(is_grantable, strlen(is_grantable), cs);
|
|
return schema_table_store_record(thd, table);
|
|
}
|
|
|
|
/*
|
|
fill effective privileges for table
|
|
|
|
SYNOPSIS
|
|
fill_effective_table_privileges()
|
|
thd thread handler
|
|
grant grants table descriptor
|
|
db db name
|
|
table table name
|
|
*/
|
|
|
|
void fill_effective_table_privileges(THD *thd, GRANT_INFO *grant,
|
|
const char *db, const char *table) {
|
|
Security_context *sctx = thd->security_context();
|
|
LEX_CSTRING priv_user = sctx->priv_user();
|
|
DBUG_TRACE;
|
|
DBUG_PRINT("enter", ("Host: '%s', Ip: '%s', User: '%s', table: `%s`.`%s`",
|
|
sctx->priv_host().str,
|
|
(sctx->ip().length ? sctx->ip().str : "(NULL)"),
|
|
(priv_user.str ? priv_user.str : "(NULL)"), db, table));
|
|
/*
|
|
This function is not intended for derived tables which doesn't have a
|
|
name. If this happens something is wrong.
|
|
*/
|
|
/* --skip-grants */
|
|
if (!initialized) {
|
|
DBUG_PRINT("info", ("skip grants"));
|
|
grant->privilege = ~NO_ACCESS; // everything is allowed
|
|
DBUG_PRINT("info", ("privilege 0x%lx", grant->privilege));
|
|
return;
|
|
}
|
|
DBUG_PRINT("info", ("Effective table privileges are deduced from active roles"
|
|
" (%lu)",
|
|
(unsigned long)sctx->get_active_roles()->size()));
|
|
std::string db_name = db ? db : "";
|
|
if (sctx->get_active_roles()->size() > 0) {
|
|
/* global privileges */
|
|
grant->privilege = sctx->master_access(db_name);
|
|
LEX_CSTRING str_db = {db, strlen(db)};
|
|
/* db privileges */
|
|
grant->privilege |= sctx->db_acl(str_db);
|
|
LEX_CSTRING str_table = {table, strlen(table)};
|
|
/* table privileges */
|
|
grant->privilege |= sctx->table_acl(str_db, str_table);
|
|
grant->grant_table = 0;
|
|
DBUG_PRINT("info", ("Role used: %s db: %s db-acl: %lu all-acl: %lu ",
|
|
sctx->get_active_roles()->at(0).first.str, db,
|
|
sctx->db_acl(str_db), grant->privilege));
|
|
} else {
|
|
/* global privileges */
|
|
grant->privilege = sctx->master_access(db_name);
|
|
|
|
/* db privileges */
|
|
grant->privilege |=
|
|
acl_get(thd, sctx->host().str, sctx->ip().str, priv_user.str, db, 0);
|
|
|
|
DEBUG_SYNC(thd, "fill_effective_table_privileges");
|
|
/* table privileges */
|
|
Acl_cache_lock_guard acl_cache_lock(thd, Acl_cache_lock_mode::READ_MODE);
|
|
if (!acl_cache_lock.lock(false)) return;
|
|
|
|
if (grant->version != grant_version) {
|
|
grant->grant_table =
|
|
table_hash_search(sctx->host().str, sctx->ip().str, db, priv_user.str,
|
|
table, 0); /* purecov: inspected */
|
|
grant->version = grant_version; /* purecov: inspected */
|
|
}
|
|
if (grant->grant_table != 0) {
|
|
grant->privilege |= grant->grant_table->privs;
|
|
}
|
|
}
|
|
|
|
// Allow SELECT privilege for INFORMATION_SCHEMA.
|
|
if (is_infoschema_db(db)) grant->privilege |= SELECT_ACL;
|
|
|
|
DBUG_PRINT("info", ("privilege 0x%lx", grant->privilege));
|
|
}
|
|
|
|
bool acl_check_proxy_grant_access(THD *thd, const char *host, const char *user,
|
|
bool with_grant MY_ATTRIBUTE((unused))) {
|
|
DBUG_TRACE;
|
|
DBUG_PRINT("info",
|
|
("user=%s host=%s with_grant=%d", user, host, (int)with_grant));
|
|
DBUG_ASSERT(initialized);
|
|
/* replication slave thread can do anything */
|
|
if (thd->slave_thread) {
|
|
DBUG_PRINT("info", ("replication slave"));
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
one can grant proxy for self to others.
|
|
Security context in THD contains two pairs of (user,host):
|
|
1. (user,host) pair referring to inbound connection.
|
|
2. (priv_user,priv_host) pair obtained from mysql.user table after doing
|
|
authnetication of incoming connection.
|
|
Privileges should be checked wrt (priv_user, priv_host) tuple, because
|
|
(user,host) pair obtained from inbound connection may have different
|
|
values than what is actually stored in mysql.user table and while granting
|
|
or revoking proxy privilege, user is expected to provide entries mentioned
|
|
in mysql.user table.
|
|
*/
|
|
if (!strcmp(thd->security_context()->priv_user().str, user) &&
|
|
!my_strcasecmp(system_charset_info, host,
|
|
thd->security_context()->priv_host().str)) {
|
|
DBUG_PRINT("info", ("strcmp (%s, %s) my_casestrcmp (%s, %s) equal",
|
|
thd->security_context()->priv_user().str, user, host,
|
|
thd->security_context()->priv_host().str));
|
|
return false;
|
|
}
|
|
Acl_cache_lock_guard acl_cache_lock(thd, Acl_cache_lock_mode::READ_MODE);
|
|
if (!acl_cache_lock.lock()) return true;
|
|
|
|
/* check for matching WITH PROXY rights */
|
|
for (ACL_PROXY_USER *proxy = acl_proxy_users->begin();
|
|
proxy != acl_proxy_users->end(); ++proxy) {
|
|
DEBUG_SYNC(thd, "before_proxy_matches");
|
|
if (proxy->matches(thd->security_context()->host().str,
|
|
thd->security_context()->user().str,
|
|
thd->security_context()->ip().str, user, false) &&
|
|
proxy->get_with_grant()) {
|
|
DBUG_PRINT("info", ("found"));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
my_error(ER_ACCESS_DENIED_NO_PASSWORD_ERROR, MYF(0),
|
|
thd->security_context()->user().str,
|
|
thd->security_context()->host_or_ip().str);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
Grantee is of form 'user'@'hostname', so add +1 for '@' and +4 for the
|
|
single qoutes. And +1 for null byte too.
|
|
|
|
Note that we use USERNAME_LENGTH and not USERNAME_CHAR_LENGTH here
|
|
because the username can be utf8.
|
|
*/
|
|
static const int GRANTEE_MAX_BUFF_LENGTH =
|
|
USERNAME_LENGTH + 1 + HOSTNAME_LENGTH + 4 + 1;
|
|
|
|
int fill_schema_user_privileges(THD *thd, TABLE_LIST *tables, Item *) {
|
|
int error = 0;
|
|
ACL_USER *acl_user;
|
|
ulong want_access;
|
|
char buff[GRANTEE_MAX_BUFF_LENGTH];
|
|
TABLE *table = tables->table;
|
|
bool no_global_access =
|
|
check_access(thd, SELECT_ACL, consts::mysql.c_str(), NULL, NULL, 1, 1);
|
|
const char *curr_host = thd->security_context()->priv_host_name();
|
|
DBUG_TRACE;
|
|
|
|
if (!initialized) return 0;
|
|
|
|
Acl_cache_lock_guard acl_cache_lock(thd, Acl_cache_lock_mode::READ_MODE);
|
|
if (!acl_cache_lock.lock()) return 1;
|
|
|
|
for (acl_user = acl_users->begin(); acl_user != acl_users->end();
|
|
++acl_user) {
|
|
const char *user, *host, *is_grantable = "YES";
|
|
if (!(user = acl_user->user)) user = "";
|
|
if (!(host = acl_user->host.get_host())) host = "";
|
|
|
|
if (no_global_access &&
|
|
(strcmp(thd->security_context()->priv_user().str, user) ||
|
|
my_strcasecmp(system_charset_info, curr_host, host)))
|
|
continue;
|
|
|
|
want_access = acl_user->access;
|
|
if (!(want_access & GRANT_ACL)) is_grantable = "NO";
|
|
|
|
strxmov(buff, "'", user, "'@'", host, "'", NullS);
|
|
if (!(want_access & ~GRANT_ACL)) {
|
|
if (update_schema_privilege(thd, table, buff, 0, 0, 0, 0,
|
|
STRING_WITH_LEN("USAGE"), is_grantable)) {
|
|
error = 1;
|
|
goto err;
|
|
}
|
|
} else {
|
|
uint priv_id;
|
|
ulong j, test_access = want_access & ~GRANT_ACL;
|
|
for (priv_id = 0, j = SELECT_ACL; j <= GLOBAL_ACLS; priv_id++, j <<= 1) {
|
|
if (test_access & j) {
|
|
if (update_schema_privilege(thd, table, buff, 0, 0, 0, 0,
|
|
global_acls_vector[priv_id].c_str(),
|
|
global_acls_vector[priv_id].length(),
|
|
is_grantable)) {
|
|
error = 1;
|
|
goto err;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/* Process all global privileges */
|
|
Role_id key(create_authid_from(acl_user));
|
|
User_to_dynamic_privileges_map::iterator it, it_end;
|
|
std::tie(it, it_end) = g_dynamic_privileges_map->equal_range(key);
|
|
for (; it != it_end; ++it) {
|
|
size_t str_len = it->second.first.length();
|
|
if (it->second.second)
|
|
is_grantable = "YES";
|
|
else
|
|
is_grantable = "NO";
|
|
if (update_schema_privilege(thd, table, buff, 0, 0, 0, 0,
|
|
it->second.first.c_str(), str_len,
|
|
is_grantable)) {
|
|
error = 1;
|
|
goto err;
|
|
}
|
|
}
|
|
} // end for each user
|
|
|
|
err:
|
|
return error;
|
|
}
|
|
|
|
int fill_schema_schema_privileges(THD *thd, TABLE_LIST *tables, Item *) {
|
|
int error = 0;
|
|
ACL_DB *acl_db;
|
|
ulong want_access;
|
|
char buff[GRANTEE_MAX_BUFF_LENGTH];
|
|
TABLE *table = tables->table;
|
|
bool no_global_access =
|
|
check_access(thd, SELECT_ACL, consts::mysql.c_str(), NULL, NULL, 1, 1);
|
|
const char *curr_host = thd->security_context()->priv_host_name();
|
|
DBUG_TRACE;
|
|
|
|
if (!initialized) return 0;
|
|
|
|
Acl_cache_lock_guard acl_cache_lock(thd, Acl_cache_lock_mode::READ_MODE);
|
|
if (!acl_cache_lock.lock()) return 1;
|
|
|
|
for (acl_db = acl_dbs->begin(); acl_db != acl_dbs->end(); ++acl_db) {
|
|
const char *user, *host, *is_grantable = "YES";
|
|
|
|
if (!(user = acl_db->user)) user = "";
|
|
if (!(host = acl_db->host.get_host())) host = "";
|
|
|
|
if (no_global_access &&
|
|
(strcmp(thd->security_context()->priv_user().str, user) ||
|
|
my_strcasecmp(system_charset_info, curr_host, host)))
|
|
continue;
|
|
|
|
want_access = acl_db->access;
|
|
if (want_access) {
|
|
if (!(want_access & GRANT_ACL)) {
|
|
is_grantable = "NO";
|
|
}
|
|
strxmov(buff, "'", user, "'@'", host, "'", NullS);
|
|
if (!(want_access & ~GRANT_ACL)) {
|
|
if (update_schema_privilege(thd, table, buff, acl_db->db, 0, 0, 0,
|
|
STRING_WITH_LEN("USAGE"), is_grantable)) {
|
|
error = 1;
|
|
goto err;
|
|
}
|
|
} else {
|
|
int cnt;
|
|
ulong j, test_access = want_access & ~GRANT_ACL;
|
|
for (cnt = 0, j = SELECT_ACL; j <= DB_ACLS; cnt++, j <<= 1)
|
|
if (test_access & j) {
|
|
if (update_schema_privilege(thd, table, buff, acl_db->db, 0, 0, 0,
|
|
global_acls_vector[cnt].c_str(),
|
|
global_acls_vector[cnt].length(),
|
|
is_grantable)) {
|
|
error = 1;
|
|
goto err;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
err:
|
|
|
|
return error;
|
|
}
|
|
|
|
int fill_schema_table_privileges(THD *thd, TABLE_LIST *tables, Item *) {
|
|
int error = 0;
|
|
char buff[GRANTEE_MAX_BUFF_LENGTH];
|
|
TABLE *table = tables->table;
|
|
bool no_global_access =
|
|
check_access(thd, SELECT_ACL, consts::mysql.c_str(), NULL, NULL, 1, 1);
|
|
const char *curr_host = thd->security_context()->priv_host_name();
|
|
DBUG_TRACE;
|
|
|
|
Acl_cache_lock_guard acl_cache_lock(thd, Acl_cache_lock_mode::READ_MODE);
|
|
if (!acl_cache_lock.lock()) return 1;
|
|
|
|
if (column_priv_hash == nullptr) return error;
|
|
|
|
for (const auto &key_and_value : *column_priv_hash) {
|
|
const char *user, *host, *is_grantable = "YES";
|
|
GRANT_TABLE *grant_table = key_and_value.second.get();
|
|
if (!(user = grant_table->user)) user = "";
|
|
if (!(host = grant_table->host.get_host())) host = "";
|
|
|
|
if (no_global_access &&
|
|
(strcmp(thd->security_context()->priv_user().str, user) ||
|
|
my_strcasecmp(system_charset_info, curr_host, host)))
|
|
continue;
|
|
|
|
ulong table_access = grant_table->privs;
|
|
if (table_access) {
|
|
ulong test_access = table_access & ~GRANT_ACL;
|
|
/*
|
|
We should skip 'usage' privilege on table if
|
|
we have any privileges on column(s) of this table
|
|
*/
|
|
if (!test_access && grant_table->cols) continue;
|
|
if (!(table_access & GRANT_ACL)) is_grantable = "NO";
|
|
|
|
strxmov(buff, "'", user, "'@'", host, "'", NullS);
|
|
if (!test_access) {
|
|
if (update_schema_privilege(thd, table, buff, grant_table->db,
|
|
grant_table->tname, 0, 0,
|
|
STRING_WITH_LEN("USAGE"), is_grantable)) {
|
|
error = 1;
|
|
goto err;
|
|
}
|
|
} else {
|
|
ulong j;
|
|
int cnt;
|
|
for (cnt = 0, j = SELECT_ACL; j <= TABLE_ACLS; cnt++, j <<= 1) {
|
|
if (test_access & j) {
|
|
if (update_schema_privilege(
|
|
thd, table, buff, grant_table->db, grant_table->tname, 0, 0,
|
|
global_acls_vector[cnt].c_str(),
|
|
global_acls_vector[cnt].length(), is_grantable)) {
|
|
error = 1;
|
|
goto err;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
err:
|
|
|
|
return error;
|
|
}
|
|
|
|
int fill_schema_column_privileges(THD *thd, TABLE_LIST *tables, Item *) {
|
|
int error = 0;
|
|
char buff[GRANTEE_MAX_BUFF_LENGTH];
|
|
TABLE *table = tables->table;
|
|
bool no_global_access =
|
|
check_access(thd, SELECT_ACL, consts::mysql.c_str(), NULL, NULL, 1, 1);
|
|
const char *curr_host = thd->security_context()->priv_host_name();
|
|
DBUG_TRACE;
|
|
|
|
Acl_cache_lock_guard acl_cache_lock(thd, Acl_cache_lock_mode::READ_MODE);
|
|
if (!acl_cache_lock.lock()) return 1;
|
|
|
|
if (column_priv_hash == nullptr) return error;
|
|
|
|
for (const auto &key_and_value : *column_priv_hash) {
|
|
const char *user, *host, *is_grantable = "YES";
|
|
GRANT_TABLE *grant_table = key_and_value.second.get();
|
|
if (!(user = grant_table->user)) user = "";
|
|
if (!(host = grant_table->host.get_host())) host = "";
|
|
|
|
if (no_global_access &&
|
|
(strcmp(thd->security_context()->priv_user().str, user) ||
|
|
my_strcasecmp(system_charset_info, curr_host, host)))
|
|
continue;
|
|
|
|
ulong table_access = grant_table->cols;
|
|
if (table_access != 0) {
|
|
if (!(grant_table->privs & GRANT_ACL)) is_grantable = "NO";
|
|
|
|
ulong test_access = table_access & ~GRANT_ACL;
|
|
strxmov(buff, "'", user, "'@'", host, "'", NullS);
|
|
if (!test_access)
|
|
continue;
|
|
else {
|
|
ulong j;
|
|
int cnt;
|
|
for (cnt = 0, j = SELECT_ACL; j <= TABLE_ACLS; cnt++, j <<= 1) {
|
|
if (test_access & j) {
|
|
for (const auto &key_and_value : grant_table->hash_columns) {
|
|
GRANT_COLUMN *grant_column = key_and_value.second.get();
|
|
if ((grant_column->rights & j) && (table_access & j)) {
|
|
if (update_schema_privilege(
|
|
thd, table, buff, grant_table->db, grant_table->tname,
|
|
grant_column->column.data(),
|
|
grant_column->column.size(),
|
|
global_acls_vector[cnt].c_str(),
|
|
global_acls_vector[cnt].length(), is_grantable)) {
|
|
error = 1;
|
|
goto err;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
err:
|
|
|
|
return error;
|
|
}
|
|
|
|
bool is_privileged_user_for_credential_change(THD *thd) {
|
|
if (thd->slave_thread) return true;
|
|
return (
|
|
!check_access(thd, UPDATE_ACL, consts::mysql.c_str(), NULL, NULL, 1, 1) ||
|
|
thd->security_context()->check_access(CREATE_USER_ACL,
|
|
consts::mysql.c_str(), false));
|
|
}
|
|
|
|
/**
|
|
Check if user has enough privileges for execution of SHOW statement,
|
|
which was converted to query to one of I_S tables.
|
|
|
|
@param thd Thread context.
|
|
@param table Table list element for I_S table to be queried..
|
|
|
|
@retval false - Success.
|
|
@retval true - Failure.
|
|
*/
|
|
|
|
bool check_show_access(THD *thd, TABLE_LIST *table) {
|
|
// perform privilege checking for show statements on new dd tables
|
|
switch (thd->lex->sql_command) {
|
|
case SQLCOM_SHOW_DATABASES: {
|
|
return (specialflag & SPECIAL_SKIP_SHOW_DB) &&
|
|
check_global_access(thd, SHOW_DB_ACL);
|
|
}
|
|
case SQLCOM_SHOW_EVENTS: {
|
|
const char *db = thd->lex->select_lex->db;
|
|
DBUG_ASSERT(db != NULL);
|
|
/*
|
|
Nobody has EVENT_ACL for I_S and P_S,
|
|
even with a GRANT ALL to *.*,
|
|
because these schemas have additional ACL restrictions:
|
|
see ACL_internal_schema_registry.
|
|
|
|
Yet there are no events in I_S and P_S to hide either,
|
|
so this check voluntarily does not enforce ACL for
|
|
SHOW EVENTS in I_S or P_S,
|
|
to return an empty list instead of an access denied error.
|
|
|
|
This is more user friendly, in particular for tools.
|
|
|
|
EVENT_ACL is not fine grained enough to differentiate:
|
|
- creating / updating / deleting events
|
|
- viewing existing events
|
|
*/
|
|
if (!is_infoschema_db(db) && !is_perfschema_db(db) &&
|
|
check_access(thd, EVENT_ACL, db, NULL, NULL, 0, 0))
|
|
return true;
|
|
}
|
|
// Fall through
|
|
case SQLCOM_SHOW_TABLES:
|
|
case SQLCOM_SHOW_TABLE_STATUS:
|
|
case SQLCOM_SHOW_TRIGGERS: {
|
|
const char *dst_db_name = thd->lex->select_lex->db;
|
|
DBUG_ASSERT(dst_db_name != NULL);
|
|
if (!dst_db_name) break;
|
|
|
|
// Check if the user has global access
|
|
if (check_access(thd, SELECT_ACL, dst_db_name, &thd->col_access, NULL,
|
|
false, false))
|
|
return true;
|
|
|
|
// Now check, if user has access to any of database/table/column/routine
|
|
if (!(thd->col_access & DB_OP_ACLS) && check_grant_db(thd, dst_db_name)) {
|
|
my_error(ER_DBACCESS_DENIED_ERROR, MYF(0),
|
|
thd->security_context()->priv_user().str,
|
|
thd->security_context()->priv_host().str, dst_db_name);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
case SQLCOM_SHOW_FIELDS:
|
|
case SQLCOM_SHOW_KEYS: {
|
|
TABLE_LIST *dst_table;
|
|
dst_table = table->schema_select_lex->table_list.first;
|
|
|
|
DBUG_ASSERT(dst_table);
|
|
/*
|
|
Open temporary tables to be able to detect them during privilege check.
|
|
*/
|
|
if (open_temporary_tables(thd, dst_table)) return true;
|
|
|
|
if (check_access(thd, SELECT_ACL, dst_table->db,
|
|
&dst_table->grant.privilege,
|
|
&dst_table->grant.m_internal, false, false))
|
|
return true; /* Access denied */
|
|
|
|
/*
|
|
Check_grant will grant access if there is any column privileges on
|
|
all of the tables thanks to the fourth parameter (bool show_table).
|
|
*/
|
|
if (check_grant(thd, SELECT_ACL, dst_table, true, UINT_MAX, false))
|
|
return true; /* Access denied */
|
|
|
|
close_thread_tables(thd);
|
|
dst_table->table = NULL;
|
|
|
|
/* Access granted */
|
|
return false;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
check for global access and give descriptive error message if it fails.
|
|
|
|
@param thd Thread handler
|
|
@param want_access Use should have any of these global rights
|
|
|
|
@warning
|
|
One gets access right if one has ANY of the rights in want_access.
|
|
This is useful as one in most cases only need one global right,
|
|
but in some case we want to check if the user has SUPER or
|
|
REPL_CLIENT_ACL rights.
|
|
|
|
@retval
|
|
0 ok
|
|
@retval
|
|
1 Access denied. In this case an error is sent to the client
|
|
*/
|
|
|
|
bool check_global_access(THD *thd, ulong want_access) {
|
|
DBUG_TRACE;
|
|
char command[128];
|
|
if (thd->security_context()->check_access(
|
|
want_access, thd->db().str ? thd->db().str : "", true))
|
|
return 0;
|
|
get_privilege_desc(command, sizeof(command), want_access);
|
|
my_error(ER_SPECIFIC_ACCESS_DENIED_ERROR, MYF(0), command);
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
Checks foreign key's parent table access.
|
|
|
|
@param [in] thd Thread handler
|
|
@param [in] create_info Create information (like MAX_ROWS, ENGINE or
|
|
temporary table flag)
|
|
@param [in] alter_info Initial list of columns and indexes for the
|
|
table to be created
|
|
|
|
@retval
|
|
false ok.
|
|
@retval
|
|
true error or access denied. Error is sent to client in this case.
|
|
*/
|
|
bool check_fk_parent_table_access(THD *thd, HA_CREATE_INFO *create_info,
|
|
Alter_info *alter_info) {
|
|
DBUG_ASSERT(alter_info != nullptr);
|
|
|
|
handlerton *db_type =
|
|
create_info->db_type ? create_info->db_type : ha_default_handlerton(thd);
|
|
|
|
// Return if engine does not support Foreign key Constraint.
|
|
if (!ha_check_storage_engine_flag(db_type, HTON_SUPPORTS_FOREIGN_KEYS))
|
|
return false;
|
|
|
|
for (const Key_spec *key : alter_info->key_list) {
|
|
if (key->type == KEYTYPE_FOREIGN) {
|
|
const Foreign_key_spec *fk_key = down_cast<const Foreign_key_spec *>(key);
|
|
|
|
TABLE_LIST parent_table(fk_key->ref_db.str, fk_key->ref_db.length,
|
|
fk_key->ref_table.str, fk_key->ref_table.length,
|
|
fk_key->ref_table.str, TL_IGNORE);
|
|
|
|
/*
|
|
Check if user has REFERENCES_ACL privilege at table level on
|
|
"parent_table".
|
|
Having privilege on any of the parent_table column is not
|
|
enough so checking whether user has REFERENCES_ACL privilege
|
|
at table level here.
|
|
*/
|
|
if ((check_access(thd, REFERENCES_ACL, parent_table.db,
|
|
&parent_table.grant.privilege,
|
|
&parent_table.grant.m_internal, false, true) ||
|
|
check_grant(thd, REFERENCES_ACL, &parent_table, false, 1, true)) ||
|
|
(parent_table.grant.privilege & REFERENCES_ACL) == 0) {
|
|
char fqtn_buff[NAME_LEN + 1 + NAME_LEN + 1];
|
|
snprintf(fqtn_buff, sizeof(fqtn_buff), "%s.%s", fk_key->ref_db.str,
|
|
fk_key->ref_table.str);
|
|
my_error(ER_TABLEACCESS_DENIED_ERROR, MYF(0), "REFERENCES",
|
|
thd->security_context()->priv_user().str,
|
|
thd->security_context()->host_or_ip().str, fqtn_buff);
|
|
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
Examines if a user\@host authid is connected to a role\@role_host authid by
|
|
comparing all out-edges if the user\@host vertex in the global role graph.
|
|
|
|
@return
|
|
@retval true the two vertices are connected (role is granted)
|
|
@retval false not connected (role is not granted)
|
|
*/
|
|
bool check_if_granted_role(LEX_CSTRING user, LEX_CSTRING host, LEX_CSTRING role,
|
|
LEX_CSTRING role_host) {
|
|
String key;
|
|
append_identifier(&key, user.str, user.length);
|
|
key.append('@');
|
|
append_identifier(&key, host.str, host.length);
|
|
Role_index_map::iterator it =
|
|
g_authid_to_vertex->find(std::string(key.c_ptr_quick()));
|
|
if (it != g_authid_to_vertex->end()) {
|
|
/* Check if role is part of current role graph */
|
|
if (find_if_granted_role(it->second, role, role_host)) return true;
|
|
}
|
|
|
|
/*
|
|
No grated role match the requested role for the current user;
|
|
Check if a mandatory role is granted instead.
|
|
*/
|
|
std::vector<Role_id> mandatory_roles;
|
|
get_mandatory_roles(&mandatory_roles);
|
|
for (auto &&rid : mandatory_roles) {
|
|
if (rid == Role_id(role, role_host)) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
Given a vertex in the roles graph, this function finds a directly connected
|
|
vertex given a (role, role_host) tuple. The resulting vertex is returned to
|
|
the caller through an out-param.
|
|
|
|
@param v Vertex descriptor of the authid which might have a granted role
|
|
@param role User name part of an authid
|
|
@param role_host Host name part of an authid
|
|
@param [out] found_vertex The corresponding vertex of the granted role.
|
|
|
|
@return Success state
|
|
@retval true The role is granted and the corresponding vertex is returned.
|
|
@retval false No such role is granted.
|
|
*/
|
|
bool find_if_granted_role(Role_vertex_descriptor v, LEX_CSTRING role,
|
|
LEX_CSTRING role_host,
|
|
Role_vertex_descriptor *found_vertex) {
|
|
DBUG_ASSERT(assert_acl_cache_read_lock(current_thd));
|
|
boost::graph_traits<Granted_roles_graph>::out_edge_iterator ei, ei_end;
|
|
boost::tie(ei, ei_end) = boost::out_edges(v, *g_granted_roles);
|
|
/* Iterate all neighboring vertices */
|
|
for (; ei != ei_end; ++ei) {
|
|
/* find current user in role graph */
|
|
ACL_USER acl_user =
|
|
get(boost::vertex_acl_user_t(),
|
|
*g_granted_roles)[boost::target(*ei, *g_granted_roles)];
|
|
if ((role.length == strlen(acl_user.user)) &&
|
|
(role_host.length == acl_user.host.get_host_len()) &&
|
|
!strncmp(role.str, acl_user.user, role.length) &&
|
|
(role_host.length == 0 ||
|
|
!strncmp(role_host.str, acl_user.host.get_host(), role_host.length))) {
|
|
/* Found a vertex matching the active role */
|
|
if (found_vertex != 0)
|
|
*found_vertex = boost::target(*ei, *g_granted_roles);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void get_granted_roles(Role_vertex_descriptor &v,
|
|
std::function<void(const Role_id &, bool)> f) {
|
|
DBUG_TRACE;
|
|
DBUG_ASSERT(assert_acl_cache_read_lock(current_thd));
|
|
boost::graph_traits<Granted_roles_graph>::out_edge_iterator ei, ei_end;
|
|
boost::tie(ei, ei_end) = boost::out_edges(v, *g_granted_roles);
|
|
/* Iterate all neighboring vertices */
|
|
for (; ei != ei_end; ++ei) {
|
|
/* find current user in role graph */
|
|
ACL_USER acl_user =
|
|
get(boost::vertex_acl_user_t(),
|
|
*g_granted_roles)[boost::target(*ei, *g_granted_roles)];
|
|
auto edge_with_admin =
|
|
boost::get(boost::edge_capacity_t(), *g_granted_roles);
|
|
int with_admin_opt = edge_with_admin[*ei];
|
|
LEX_CSTRING tmp_user, tmp_host;
|
|
tmp_user.str = acl_user.user;
|
|
tmp_user.length = strlen(acl_user.user);
|
|
tmp_host.str = acl_user.host.get_host();
|
|
tmp_host.length = acl_user.host.get_host_len();
|
|
Role_id id(tmp_user, tmp_host);
|
|
f(id, with_admin_opt != 0);
|
|
}
|
|
}
|
|
|
|
/**
|
|
Populates a list of authorization IDs that are connected to a specified
|
|
graph vertex in the global roles graph.
|
|
|
|
The constructed list contains references to a shared memory. The authIDs
|
|
are not copied!
|
|
|
|
The list of granted roles is /appended/ to the out variable.
|
|
|
|
@param v A valid vertex descriptor from the global roles graph
|
|
@param [out] granted_roles A list of authorization IDs
|
|
*/
|
|
void get_granted_roles(Role_vertex_descriptor &v,
|
|
List_of_granted_roles *granted_roles) {
|
|
DBUG_TRACE;
|
|
DBUG_ASSERT(assert_acl_cache_read_lock(current_thd));
|
|
get_granted_roles(v, [&](const Role_id &rid, bool with_admin_opt) {
|
|
granted_roles->push_back(std::make_pair(rid, with_admin_opt));
|
|
});
|
|
}
|
|
|
|
/**
|
|
Activates all roles granted to the auth_id.
|
|
|
|
@param [in] acl_user ACL_USER for which all granted roles to be activated.
|
|
@param [in] sctx Push the activated role to secruity context
|
|
*/
|
|
void activate_all_granted_roles(const ACL_USER *acl_user,
|
|
Security_context *sctx) {
|
|
DBUG_ASSERT(assert_acl_cache_read_lock(current_thd));
|
|
std::string key = create_authid_str_from(acl_user);
|
|
Role_index_map::iterator it = g_authid_to_vertex->find(key);
|
|
if (it == g_authid_to_vertex->end()) return; // No user vertex founds
|
|
get_granted_roles(it->second, [&](const Role_id rid, bool) {
|
|
LEX_CSTRING str_user = {rid.user().c_str(), rid.user().length()};
|
|
LEX_CSTRING str_host = {rid.host().c_str(), rid.host().length()};
|
|
sctx->activate_role(str_user, str_host, false);
|
|
});
|
|
}
|
|
|
|
/**
|
|
Activates all the mandatory roles for the current user
|
|
|
|
@param [in] sctx Push the activated role to secruity context
|
|
*/
|
|
void activate_all_mandatory_roles(Security_context *sctx) {
|
|
DBUG_ASSERT(assert_acl_cache_read_lock(current_thd));
|
|
std::vector<Role_id> mandatory_roles;
|
|
get_mandatory_roles(&mandatory_roles);
|
|
for (auto &rid : mandatory_roles) {
|
|
LEX_CSTRING str_user = {rid.user().c_str(), rid.user().length()};
|
|
LEX_CSTRING str_host = {rid.host().c_str(), rid.host().length()};
|
|
sctx->activate_role(str_user, str_host, false);
|
|
}
|
|
}
|
|
|
|
void activate_all_granted_and_mandatory_roles(const ACL_USER *acl_user,
|
|
Security_context *sctx) {
|
|
activate_all_granted_roles(acl_user, sctx);
|
|
activate_all_mandatory_roles(sctx);
|
|
}
|
|
|
|
/**
|
|
This is a convenience function.
|
|
@see get_granted_roles(Role_vertex_descriptor &v,
|
|
List_of_granted_roles *granted_roles)
|
|
@param user The authid to check for granted roles
|
|
@param [out] granted_roles A list of granted authids
|
|
*/
|
|
|
|
void get_granted_roles(LEX_USER *user, List_of_granted_roles *granted_roles) {
|
|
Role_index_map::iterator it;
|
|
std::string str_user = create_authid_str_from(user);
|
|
if ((it = g_authid_to_vertex->find(str_user)) != g_authid_to_vertex->end()) {
|
|
get_granted_roles(it->second, granted_roles);
|
|
}
|
|
}
|
|
|
|
/**
|
|
Helper function for func_current_role used for Item_func_current_role.
|
|
@param thd The thread handler
|
|
@param roles [out] A list of Role_id granted to the current user.
|
|
*/
|
|
void get_active_roles(const THD *thd, List_of_granted_roles *roles) {
|
|
/*
|
|
We need the order of the current roles to stay consistent across platforms
|
|
so we copy the list of active roles here and sort the list.
|
|
Copying is crucial as the std::sort algorithms operates on pointers and
|
|
not on values which cause all references to become invalid.
|
|
*/
|
|
for (auto &ref : *thd->security_context()->get_active_roles()) {
|
|
roles->push_back(std::make_pair(Role_id(ref.first, ref.second), false));
|
|
}
|
|
}
|
|
|
|
/**
|
|
Helper function for Item_func_current_role.
|
|
@param thd Thread handler
|
|
@param active_role [out] Comma separated list of auth ids
|
|
|
|
@returns pointer to a string with all active roles or "NONE" if none found
|
|
*/
|
|
|
|
void func_current_role(const THD *thd, String *active_role) {
|
|
List_of_granted_roles roles;
|
|
get_active_roles(thd, &roles);
|
|
if (roles.size() == 0) {
|
|
active_role->set_ascii("NONE", 4);
|
|
return;
|
|
}
|
|
std::sort(roles.begin(), roles.end());
|
|
bool first = true;
|
|
for (auto &rid : roles) {
|
|
if (!first) {
|
|
active_role->append(',');
|
|
} else {
|
|
first = false;
|
|
}
|
|
append_identifier(thd, active_role, rid.first.user().c_str(),
|
|
rid.first.user().length());
|
|
active_role->append("@");
|
|
append_identifier(thd, active_role, rid.first.host().c_str(),
|
|
rid.first.host().length());
|
|
}
|
|
return;
|
|
}
|
|
|
|
/**
|
|
Shallow copy a list of default role authorization IDs from an Role_id storage
|
|
|
|
@param acl_user A valid authID for which we want the default roles.
|
|
@param [out] authlist The target list to be populated. The target list is set
|
|
to empty if no default role is found.
|
|
*/
|
|
void get_default_roles(const Auth_id_ref &acl_user,
|
|
List_of_auth_id_refs &authlist) {
|
|
if (g_default_roles == nullptr) return;
|
|
|
|
authlist.clear(); // Remove all items
|
|
|
|
Role_id user(acl_user);
|
|
Default_roles::iterator role_it, role_end;
|
|
boost::tie(role_it, role_end) = g_default_roles->equal_range(user);
|
|
for (; role_it != role_end; ++role_it) {
|
|
Auth_id_ref ref = create_authid_from(role_it->second);
|
|
authlist.push_back(ref);
|
|
}
|
|
}
|
|
|
|
/**
|
|
Removes all default role policies assigned to user. If the user is used as a
|
|
default role policy, this policy needs to be removed too.
|
|
Removed policies are copied to the vector supplied in the arguments.
|
|
|
|
@param thd Thread handler
|
|
@param table Open table handler
|
|
@param user_auth_id A reference to the authorization ID to clear
|
|
@param [out] default_roles The vector to which the removed roles are copied.
|
|
|
|
@return
|
|
@retval true An error occurred.
|
|
@retval false Success
|
|
*/
|
|
bool clear_default_roles(THD *thd, TABLE *table,
|
|
const Auth_id_ref &user_auth_id,
|
|
std::vector<Role_id> *default_roles) {
|
|
DBUG_TRACE;
|
|
DBUG_ASSERT(assert_acl_cache_write_lock(thd));
|
|
Default_roles::iterator role_it, role_end, begin_it;
|
|
Role_id user_role_id(user_auth_id);
|
|
boost::tie(begin_it, role_end) = g_default_roles->equal_range(user_role_id);
|
|
role_it = begin_it;
|
|
bool error = false;
|
|
for (; role_it != role_end && !error; ++role_it) {
|
|
if (default_roles != 0) {
|
|
default_roles->push_back(role_it->second);
|
|
}
|
|
Auth_id_ref role_auth_id = create_authid_from(role_it->second);
|
|
error = modify_default_roles_in_table(thd, table, user_auth_id,
|
|
role_auth_id, true);
|
|
}
|
|
g_default_roles->erase(begin_it, role_end);
|
|
|
|
return error;
|
|
}
|
|
|
|
/**
|
|
Drop a specific default role policy given the role- and user names.
|
|
|
|
@param thd Thread handler
|
|
@param table An open table handler to the default_roles table
|
|
@param default_role_policy The role name
|
|
@param user The user name
|
|
|
|
@retval Error state
|
|
@retval true An error occurred
|
|
@retval false Success
|
|
*/
|
|
|
|
bool drop_default_role_policy(THD *thd, TABLE *table,
|
|
const Auth_id_ref &default_role_policy,
|
|
const Auth_id_ref &user) {
|
|
Role_id id(user);
|
|
auto range = g_default_roles->equal_range(id);
|
|
for (; range.first != range.second; ++range.first) {
|
|
if (range.first->second == default_role_policy) {
|
|
g_default_roles->erase(range.first);
|
|
return modify_default_roles_in_table(thd, table, user,
|
|
default_role_policy, true);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
Set the default roles to NONE, ALL or list of authorization IDs as
|
|
roles, depending upon the role_type argument. It writes to table
|
|
mysql.default_roles and binlog.
|
|
|
|
@param thd Thread handler
|
|
@param role_type default role type specified by the user.
|
|
@param users Users for whom the default roles are set.
|
|
@param roles list of default roles to be set.
|
|
|
|
@return
|
|
@retval true An error occurred and DA is set
|
|
@retval false Successful
|
|
*/
|
|
bool mysql_alter_or_clear_default_roles(THD *thd, role_enum role_type,
|
|
const List<LEX_USER> *users,
|
|
const List<LEX_USER> *roles) {
|
|
DBUG_TRACE;
|
|
|
|
List<LEX_USER> *tmp_users = const_cast<List<LEX_USER> *>(users);
|
|
List<LEX_USER> *tmp_roles = const_cast<List<LEX_USER> *>(roles);
|
|
List_iterator<LEX_USER> users_it(*tmp_users);
|
|
List_iterator<LEX_USER> roles_it;
|
|
List_of_auth_id_refs authids;
|
|
Auth_id_ref authid;
|
|
LEX_USER *user = nullptr;
|
|
LEX_USER *role = nullptr;
|
|
TABLE_LIST tables[ACL_TABLES::LAST_ENTRY];
|
|
int result = 0;
|
|
bool transactional_tables = false;
|
|
|
|
/*
|
|
This statement will be replicated as a statement, even when using
|
|
row-based replication. The binlog state will be cleared here to
|
|
statement based replication and will be reset to the originals
|
|
values when we are out of this function scope
|
|
*/
|
|
Save_and_Restore_binlog_format_state binlog_format_state(thd);
|
|
|
|
if ((result = open_grant_tables(thd, tables, &transactional_tables)))
|
|
return result != 1;
|
|
|
|
TABLE *table = tables[ACL_TABLES::TABLE_DEFAULT_ROLES].table;
|
|
|
|
if (!table) {
|
|
my_error(ER_OPEN_ROLE_TABLES, MYF(MY_WME));
|
|
return true;
|
|
}
|
|
|
|
bool ret = false;
|
|
{ /* Critical section */
|
|
Acl_cache_lock_guard acl_cache_lock(thd, Acl_cache_lock_mode::WRITE_MODE);
|
|
|
|
if (!acl_cache_lock.lock()) {
|
|
commit_and_close_mysql_tables(thd);
|
|
return true;
|
|
}
|
|
|
|
if (check_system_user_privilege(thd, *users)) {
|
|
commit_and_close_mysql_tables(thd);
|
|
return true;
|
|
}
|
|
|
|
while ((user = users_it++) && !ret) {
|
|
// Check for CURRENT_USER token
|
|
user = get_current_user(thd, user);
|
|
if (strcmp(thd->security_context()->priv_user().str, user->user.str) !=
|
|
0 ||
|
|
strcmp(thd->security_context()->priv_host().str, user->host.str) !=
|
|
0) {
|
|
if (check_access(thd, UPDATE_ACL, consts::mysql.c_str(), NULL, NULL, 1,
|
|
1) &&
|
|
check_global_access(thd, CREATE_USER_ACL)) {
|
|
my_error(ER_ACCESS_DENIED_ERROR, MYF(0), user->user.str,
|
|
user->host.str,
|
|
(thd->password ? ER_THD(thd, ER_YES) : ER_THD(thd, ER_NO)));
|
|
return true;
|
|
}
|
|
if (roles != nullptr) {
|
|
roles_it = *tmp_roles;
|
|
while ((role = roles_it++)) {
|
|
if (!is_granted_role(user->user, user->host, role->user,
|
|
role->host)) {
|
|
my_error(ER_ROLE_NOT_GRANTED, MYF(0), role->user.str,
|
|
role->host.str, user->user.str, user->host.str);
|
|
return true;
|
|
}
|
|
authid = std::make_pair(role->user, role->host);
|
|
authids.push_back(authid);
|
|
}
|
|
}
|
|
} else {
|
|
// Verify that the user actually is granted the role before it is
|
|
// set as default.
|
|
if (roles != nullptr) {
|
|
roles_it = *tmp_roles;
|
|
while ((role = roles_it++)) {
|
|
if (!is_granted_role(thd->security_context()->priv_user(),
|
|
thd->security_context()->priv_host(),
|
|
role->user, role->host)) {
|
|
my_error(ER_ROLE_NOT_GRANTED, MYF(0), role->user.str,
|
|
role->host.str, thd->security_context()->priv_user().str,
|
|
thd->security_context()->priv_host().str);
|
|
return true;
|
|
}
|
|
authid = std::make_pair(role->user, role->host);
|
|
authids.push_back(authid);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (role_type == role_enum::ROLE_NONE) {
|
|
authid = create_authid_from(user);
|
|
ret = clear_default_roles(thd, table, authid, nullptr);
|
|
} else if (role_type == role_enum::ROLE_ALL) {
|
|
ret = alter_user_set_default_roles_all(thd, table, user);
|
|
} else if (role_type == role_enum::ROLE_NAME) {
|
|
ret = alter_user_set_default_roles(thd, table, user, authids);
|
|
}
|
|
|
|
if (ret) {
|
|
my_error(ER_FAILED_DEFAULT_ROLES, MYF(0));
|
|
}
|
|
}
|
|
|
|
ret = log_and_commit_acl_ddl(thd, transactional_tables, nullptr, nullptr,
|
|
ret);
|
|
get_global_acl_cache()->increase_version();
|
|
} /* Critical section */
|
|
|
|
/* Notify storage engines */
|
|
if (!ret) {
|
|
acl_notify_htons(thd, SQLCOM_ALTER_USER, users);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
Set all granted role as default roles. Writes to table mysql.default_roles
|
|
and binlog.
|
|
|
|
@param thd Thread handler
|
|
@param def_role_table Default role table
|
|
@param user The user whose default roles are set.
|
|
|
|
@return
|
|
@retval true An error occurred and DA is set
|
|
@retval false Successful
|
|
*/
|
|
|
|
bool alter_user_set_default_roles_all(THD *thd, TABLE *def_role_table,
|
|
LEX_USER *user) {
|
|
DBUG_ASSERT(assert_acl_cache_write_lock(thd));
|
|
std::string authid_role = create_authid_str_from(user);
|
|
Role_index_map::iterator it = g_authid_to_vertex->find(authid_role);
|
|
if (it == g_authid_to_vertex->end()) {
|
|
/* No such user */
|
|
my_error(ER_UNKNOWN_AUTHID, MYF(MY_WME), user->user.str, user->host.str);
|
|
return true;
|
|
}
|
|
List_of_granted_roles granted_roles;
|
|
get_granted_roles(it->second, &granted_roles);
|
|
List_of_auth_id_refs new_default_role_ref;
|
|
for (auto &&role : granted_roles) {
|
|
Auth_id_ref authid = create_authid_from(role.first);
|
|
new_default_role_ref.push_back(authid);
|
|
}
|
|
std::vector<Role_id> mandatory_roles;
|
|
get_mandatory_roles(&mandatory_roles);
|
|
for (auto &role : mandatory_roles) {
|
|
Auth_id_ref authid = create_authid_from(role);
|
|
auto res = std::find(new_default_role_ref.begin(),
|
|
new_default_role_ref.end(), authid);
|
|
if (res == new_default_role_ref.end()) {
|
|
new_default_role_ref.push_back(authid);
|
|
}
|
|
}
|
|
bool errors = alter_user_set_default_roles(thd, def_role_table, user,
|
|
new_default_role_ref);
|
|
|
|
return errors;
|
|
}
|
|
|
|
/**
|
|
Set the default roles for a particular user.
|
|
|
|
@param thd Thread handle
|
|
@param table Table handle to an open table
|
|
@param user AST component for the user for which we set def roles
|
|
@param new_auth_ids Default roles to set
|
|
@return
|
|
@retval true Operation failed
|
|
@retval false Operation was successful.
|
|
*/
|
|
|
|
bool alter_user_set_default_roles(THD *thd, TABLE *table, LEX_USER *user,
|
|
const List_of_auth_id_refs &new_auth_ids) {
|
|
DBUG_ASSERT(assert_acl_cache_write_lock(thd));
|
|
bool errors = false;
|
|
|
|
ACL_USER *acl_user = find_acl_user(user->host.str, user->user.str, true);
|
|
if (acl_user == 0) return true;
|
|
|
|
if (new_auth_ids.size() != 0) {
|
|
Default_roles::iterator role_it, role_end;
|
|
Auth_id_ref user_auth_id = create_authid_from(user);
|
|
Role_id user_role_id(user_auth_id);
|
|
boost::tie(role_it, role_end) = g_default_roles->equal_range(user_role_id);
|
|
for (; role_it != role_end && !errors; ++role_it) {
|
|
if (std::find(new_auth_ids.begin(), new_auth_ids.end(),
|
|
role_it->second) == new_auth_ids.end()) {
|
|
Auth_id_ref role_auth_id = create_authid_from(role_it->second);
|
|
errors = modify_default_roles_in_table(thd, table, user_auth_id,
|
|
role_auth_id, true);
|
|
}
|
|
}
|
|
List_of_auth_id_refs::const_iterator it = new_auth_ids.begin();
|
|
boost::tie(role_it, role_end) = g_default_roles->equal_range(user_role_id);
|
|
for (; it != new_auth_ids.end() && !errors; ++it) {
|
|
if (find(role_it, role_end, *it) == role_end) {
|
|
errors =
|
|
modify_default_roles_in_table(thd, table, user_auth_id, *it, false);
|
|
}
|
|
}
|
|
boost::tie(role_it, role_end) = g_default_roles->equal_range(user_role_id);
|
|
g_default_roles->erase(role_it, role_end);
|
|
it = new_auth_ids.begin();
|
|
for (; it != new_auth_ids.end() && !errors; ++it) {
|
|
Role_id role_role_id(*it);
|
|
g_default_roles->insert(std::make_pair(user_role_id, role_role_id));
|
|
}
|
|
}
|
|
|
|
return errors;
|
|
}
|
|
|
|
/**
|
|
Helper used for producing a key to a key-value-map
|
|
*/
|
|
std::string create_authid_str_from(const LEX_USER *user) {
|
|
String tmp;
|
|
append_identifier(&tmp, user->user.str, user->user.length);
|
|
tmp.append('@');
|
|
append_identifier(&tmp, user->host.str, user->host.length);
|
|
return std::string(tmp.c_ptr_quick());
|
|
}
|
|
|
|
Auth_id_ref create_authid_from(const LEX_USER *user) {
|
|
Auth_id_ref id;
|
|
id = std::make_pair(user->user, user->host);
|
|
return id;
|
|
}
|
|
|
|
Auth_id_ref create_authid_from(const Role_id &user) {
|
|
Auth_id_ref id;
|
|
LEX_CSTRING lex_user;
|
|
lex_user.str = user.user().c_str();
|
|
lex_user.length = user.user().length();
|
|
LEX_CSTRING lex_host;
|
|
lex_host.str = user.host().c_str();
|
|
lex_host.length = user.host().length();
|
|
id = std::make_pair(lex_user, lex_host);
|
|
return id;
|
|
}
|
|
|
|
Auth_id_ref create_authid_from(const LEX_CSTRING &user,
|
|
const LEX_CSTRING &host) {
|
|
return std::make_pair(user, host);
|
|
}
|
|
|
|
/**
|
|
Helper used for producing a key to a key-value-map
|
|
*/
|
|
std::string create_authid_str_from(const ACL_USER *user) {
|
|
String tmp;
|
|
size_t length = user->user == 0 ? 0 : strlen(user->user);
|
|
append_identifier(&tmp, user->user, length);
|
|
tmp.append("@");
|
|
append_identifier(&tmp, user->host.get_host(), user->host.get_host_len());
|
|
return std::string(tmp.c_ptr_quick());
|
|
}
|
|
|
|
std::string create_authid_str_from(const Auth_id_ref &user) {
|
|
String tmp;
|
|
append_identifier(&tmp, user.first.str, user.first.length);
|
|
tmp.append("@");
|
|
append_identifier(&tmp, user.second.str, user.second.length);
|
|
return std::string(tmp.c_ptr_quick());
|
|
}
|
|
|
|
std::string create_authid_str_from(const LEX_CSTRING &user,
|
|
const LEX_CSTRING &host) {
|
|
String tmp;
|
|
append_identifier(&tmp, user.str, user.length);
|
|
tmp.append('@');
|
|
append_identifier(&tmp, host.str, host.length);
|
|
return std::string(tmp.c_ptr_quick());
|
|
}
|
|
|
|
Auth_id_ref create_authid_from(const ACL_USER *user) {
|
|
Auth_id_ref id;
|
|
LEX_CSTRING username;
|
|
LEX_CSTRING host;
|
|
username.str = user->user;
|
|
if (user->user != 0)
|
|
username.length = strlen(user->user);
|
|
else
|
|
username.length = 0;
|
|
host.str = user->host.get_host();
|
|
host.length = user->host.get_host_len();
|
|
id = std::make_pair(username, host);
|
|
return id;
|
|
}
|
|
|
|
std::string create_authid_str_from(const Role_id &user) {
|
|
std::string tmp;
|
|
user.auth_str(&tmp);
|
|
return tmp;
|
|
}
|
|
|
|
/**
|
|
Reset active roles
|
|
|
|
@param [in] thd THD handle
|
|
|
|
@returns status of resetting active roles
|
|
@retval false Success
|
|
@retval true Error
|
|
*/
|
|
bool mysql_set_active_role_none(THD *thd) {
|
|
DBUG_TRACE;
|
|
bool ret = false;
|
|
Roles::Role_activation role_activation(thd, thd->security_context(),
|
|
role_enum::ROLE_NONE, nullptr, false);
|
|
ret = role_activation.activate();
|
|
if (!ret) my_ok(thd);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
Activates all the default roles in the current security context
|
|
|
|
This function acquires the Acl_cache_lock_guard in read lock.
|
|
|
|
@param thd A valid THD handle
|
|
|
|
@return Error code
|
|
@retval 0 Success; the specified role was activated.
|
|
@retval != 0 Failure. DA is set.
|
|
*/
|
|
bool mysql_set_role_default(THD *thd) {
|
|
DBUG_TRACE;
|
|
bool ret = 0;
|
|
Roles::Role_activation role_activation(thd, thd->security_context(),
|
|
role_enum::ROLE_DEFAULT, nullptr);
|
|
ret = role_activation.activate();
|
|
if (!ret) my_ok(thd);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
Activates all granted role in the current security context
|
|
|
|
This function acquires the acl_user->lock mutex.
|
|
|
|
@param thd A valid THD handle
|
|
@param except_users A pointer to a list of LEX_USER objects which represent
|
|
roles that shouldn't be activated.
|
|
|
|
@return Error code
|
|
@retval 0 Success; the specified role was activated.
|
|
@retval != 0 Failure. DA is set.
|
|
*/
|
|
bool mysql_set_active_role_all(THD *thd, const List<LEX_USER> *except_users) {
|
|
DBUG_TRACE;
|
|
bool ret = 0;
|
|
Roles::Role_activation role_activation(thd, thd->security_context(),
|
|
role_enum::ROLE_ALL, except_users);
|
|
ret = role_activation.activate();
|
|
if (!ret) my_ok(thd);
|
|
return ret;
|
|
}
|
|
|
|
bool mysql_set_active_role(THD *thd, const List<LEX_USER> *role_list) {
|
|
DBUG_TRACE;
|
|
bool ret = false;
|
|
Roles::Role_activation role_activation(thd, thd->security_context(),
|
|
role_enum::ROLE_NAME, role_list);
|
|
ret = role_activation.activate();
|
|
if (!ret) my_ok(thd);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
This function works just like check_if_granted_role, but also guarantees that
|
|
the proper lock is taken so that the function can be used in a wider context.
|
|
@param user The user name part of a authid which should be tested
|
|
@param host The host name part of a authid which should be tested
|
|
@param role The role name part of the role authid
|
|
@param role_host The host name part of the role authid
|
|
|
|
@return success value
|
|
@retval true The value user\@host was previously granted role\@role_host
|
|
@retval false role\@role_host is not granted to user\@host
|
|
*/
|
|
|
|
bool is_granted_role(LEX_CSTRING user, LEX_CSTRING host, LEX_CSTRING role,
|
|
LEX_CSTRING role_host) {
|
|
bool ret = false;
|
|
Acl_cache_lock_guard acl_cache_lock(current_thd,
|
|
Acl_cache_lock_mode::READ_MODE);
|
|
if (!acl_cache_lock.lock(false)) return false;
|
|
|
|
ret = check_if_granted_role(user, host, role, role_host);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
Grant one privilege to one user
|
|
@param str_priv
|
|
@param str_user
|
|
@param str_host
|
|
@param with_grant_option
|
|
@param update_table
|
|
|
|
@return Error state
|
|
@retval true An error occurred. DA must be checked.
|
|
@retval false Success
|
|
|
|
*/
|
|
bool grant_dynamic_privilege(const LEX_CSTRING &str_priv,
|
|
const LEX_CSTRING &str_user,
|
|
const LEX_CSTRING &str_host,
|
|
bool with_grant_option,
|
|
Update_dynamic_privilege_table &update_table) {
|
|
try {
|
|
const std::string priv(str_priv.str, str_priv.length);
|
|
if (!is_dynamic_privilege_registered(priv)) return true;
|
|
const Role_id id(str_user, str_host);
|
|
/*
|
|
Is this grant already present? If so we will make an update by removing
|
|
the previous grant only if the grant_option property has changed.
|
|
*/
|
|
auto range = g_dynamic_privileges_map->equal_range(id);
|
|
for (auto it = range.first; it != range.second; ++it) {
|
|
if (it->second.first == priv) {
|
|
/*
|
|
WITH GRANT OPTION is cumulative which means that if a previous GRANT
|
|
exists we only update it if we're adding GRANT OPTION to it.
|
|
We never remove the GRANT OPTION as a result of GRANT statement.
|
|
*/
|
|
if (with_grant_option == true &&
|
|
it->second.second != with_grant_option) {
|
|
if (update_table(priv, {str_user, str_host}, false,
|
|
Update_dynamic_privilege_table::REVOKE))
|
|
return true;
|
|
g_dynamic_privileges_map->erase(it);
|
|
break;
|
|
}
|
|
/* If the entry already exist we're done */
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (update_table(priv, {str_user, str_host}, with_grant_option,
|
|
Update_dynamic_privilege_table::GRANT))
|
|
return true;
|
|
g_dynamic_privileges_map->insert(
|
|
std::make_pair(id, std::make_pair(priv, with_grant_option)));
|
|
} catch (...) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
Grant grant option to one user for all dynamic privileges
|
|
@param str_user
|
|
@param str_host
|
|
@param update_table
|
|
|
|
@return Error state
|
|
@retval true An error occurred. DA must be checked.
|
|
@retval false Success
|
|
|
|
*/
|
|
bool grant_grant_option_for_all_dynamic_privileges(
|
|
const LEX_CSTRING &str_user, const LEX_CSTRING &str_host,
|
|
Update_dynamic_privilege_table &update_table) {
|
|
try {
|
|
Role_id id(str_user, str_host);
|
|
/*
|
|
For all dynamic privileges associated for a particular user
|
|
grant with grant option.
|
|
*/
|
|
auto range = g_dynamic_privileges_map->equal_range(id);
|
|
std::vector<std::string> priv_list;
|
|
for (auto it = range.first; it != range.second; ++it) {
|
|
std::string priv(it->second.first);
|
|
if (it->second.second != true) {
|
|
/*
|
|
if with grant option is not set reovke this privilege and
|
|
later update the privilege along with "WITH GRANT OPTION".
|
|
*/
|
|
update_table(priv, {str_user, str_host}, false,
|
|
Update_dynamic_privilege_table::REVOKE);
|
|
} else
|
|
continue;
|
|
|
|
if (update_table(priv, {str_user, str_host}, true,
|
|
Update_dynamic_privilege_table::GRANT))
|
|
return true;
|
|
/* keep track of privileges, later used to update cache */
|
|
priv_list.push_back(priv);
|
|
}
|
|
for (auto it = priv_list.begin(); it != priv_list.end(); ++it) {
|
|
g_dynamic_privileges_map->insert(
|
|
std::make_pair(id, std::make_pair(*it, true)));
|
|
}
|
|
} catch (...) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
Revoke grant option to one user for all dynamic privileges
|
|
@param str_user
|
|
@param str_host
|
|
@param update_table
|
|
|
|
@return Error state
|
|
@retval true An error occurred. DA must be checked.
|
|
@retval false Success
|
|
|
|
*/
|
|
bool revoke_grant_option_for_all_dynamic_privileges(
|
|
const LEX_CSTRING &str_user, const LEX_CSTRING &str_host,
|
|
Update_dynamic_privilege_table &update_table) {
|
|
try {
|
|
Role_id id(str_user, str_host);
|
|
/*
|
|
For all dynamic privileges associated for a particular user
|
|
revoke with grant option.
|
|
*/
|
|
auto range = g_dynamic_privileges_map->equal_range(id);
|
|
std::vector<std::string> priv_list;
|
|
for (auto it = range.first; it != range.second; ++it) {
|
|
std::string priv(it->second.first);
|
|
if (it->second.second == true) {
|
|
if (update_table(priv, {str_user, str_host}, true,
|
|
Update_dynamic_privilege_table::REVOKE))
|
|
return true;
|
|
} else
|
|
continue;
|
|
|
|
if (update_table(priv, {str_user, str_host}, false,
|
|
Update_dynamic_privilege_table::GRANT))
|
|
return true;
|
|
/* keep track of privileges, later used to update cache */
|
|
priv_list.push_back(priv);
|
|
}
|
|
for (auto it = priv_list.begin(); it != priv_list.end(); ++it) {
|
|
g_dynamic_privileges_map->insert(
|
|
std::make_pair(id, std::make_pair(*it, false)));
|
|
}
|
|
} catch (...) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
Grant needed dynamic privielges to in memory internal auth id.
|
|
|
|
@param id auth id to which privileges needs to be granted
|
|
@param priv_list List of privileges to be added to internal auth id
|
|
|
|
@return
|
|
True In case privilege is not registered
|
|
False Success
|
|
*/
|
|
bool grant_dynamic_privileges_to_auth_id(
|
|
const Role_id &id, const std::vector<std::string> &priv_list) {
|
|
DBUG_TRACE;
|
|
Update_dynamic_privilege_table update_table;
|
|
|
|
/* --skip-grants */
|
|
if (!initialized) return false;
|
|
for (auto it : priv_list) {
|
|
LEX_CSTRING priv = {it.c_str(), it.length()};
|
|
LEX_CSTRING user = {id.user().c_str(), id.user().length()};
|
|
LEX_CSTRING host = {id.host().c_str(), id.host().length()};
|
|
if (grant_dynamic_privilege(priv, user, host, false, update_table))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
Revoke dynamic privielges from in memory internal auth id.
|
|
|
|
@param id auth id from which privileges needs to be revoked
|
|
@param priv_list List of privileges to be removed for internal auth id
|
|
|
|
@return None
|
|
*/
|
|
void revoke_dynamic_privileges_from_auth_id(
|
|
const Role_id &id, const std::vector<std::string> &priv_list) {
|
|
DBUG_TRACE;
|
|
if (!initialized) return;
|
|
Update_dynamic_privilege_table update_table;
|
|
for (auto priv_it : priv_list) {
|
|
LEX_CSTRING user = {id.user().c_str(), id.user().length()};
|
|
LEX_CSTRING host = {id.host().c_str(), id.host().length()};
|
|
LEX_CSTRING priv = {priv_it.c_str(), priv_it.length()};
|
|
revoke_dynamic_privilege(priv, user, host, update_table);
|
|
}
|
|
}
|
|
|
|
/**
|
|
Revoke one privilege from one user
|
|
@param str_priv
|
|
@param str_user
|
|
@param str_host
|
|
@param update_table
|
|
|
|
@return Error state
|
|
@retval true An error occurred. DA must be checked.
|
|
@retval false Success
|
|
*/
|
|
|
|
bool revoke_dynamic_privilege(const LEX_CSTRING &str_priv,
|
|
const LEX_CSTRING &str_user,
|
|
const LEX_CSTRING &str_host,
|
|
Update_dynamic_privilege_table &update_table) {
|
|
try {
|
|
const std::string priv(str_priv.str, str_priv.length);
|
|
const Role_id id(str_user, str_host);
|
|
if (is_dynamic_privilege_registered(priv)) {
|
|
auto range = g_dynamic_privileges_map->equal_range(id);
|
|
for (auto it = range.first; it != range.second; ++it) {
|
|
if (it->second.first == priv) {
|
|
if (update_table(priv, {str_user, str_host}, false,
|
|
Update_dynamic_privilege_table::REVOKE))
|
|
return true;
|
|
g_dynamic_privileges_map->erase(it);
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
push_warning_printf(
|
|
current_thd, Sql_condition::SL_WARNING,
|
|
ER_WARN_DA_PRIVILEGE_NOT_REGISTERED,
|
|
ER_THD(current_thd, ER_WARN_DA_PRIVILEGE_NOT_REGISTERED),
|
|
str_priv.str);
|
|
}
|
|
} catch (...) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
Revoke all dynamic global privileges.
|
|
@param user The target user name
|
|
@param host The target host name
|
|
@param update_table Functor for updating a table
|
|
|
|
@return Error state
|
|
@retval true An error occurred. DA might not be set.
|
|
@retval false Success
|
|
*/
|
|
bool revoke_all_dynamic_privileges(
|
|
const LEX_CSTRING &user, const LEX_CSTRING &host,
|
|
Update_dynamic_privilege_table &update_table) {
|
|
try {
|
|
if (g_dynamic_privileges_map->size() > 0) {
|
|
Role_id id(user, host);
|
|
auto range = g_dynamic_privileges_map->equal_range(id);
|
|
for (auto it = range.first; it != range.second; ++it) {
|
|
if (update_table(it->second.first, {user, host}, false,
|
|
Update_dynamic_privilege_table::REVOKE)) {
|
|
return true;
|
|
}
|
|
}
|
|
g_dynamic_privileges_map->erase(range.first, range.second);
|
|
}
|
|
} catch (...) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool rename_dynamic_grant(const LEX_CSTRING &old_user,
|
|
const LEX_CSTRING &old_host,
|
|
const LEX_CSTRING &new_user,
|
|
const LEX_CSTRING &new_host,
|
|
Update_dynamic_privilege_table &update_table) {
|
|
try {
|
|
if (g_dynamic_privileges_map->size() > 0) {
|
|
/*
|
|
Revoke all privileges using the old authorization identifier but don't
|
|
update the cache.
|
|
*/
|
|
Role_id id(old_user, old_host);
|
|
auto range = g_dynamic_privileges_map->equal_range(id);
|
|
std::vector<Grant_privilege> privileges(
|
|
std::distance(range.first, range.second));
|
|
int grants_count = 0;
|
|
for (auto it = range.first; it != range.second; ++it, ++grants_count) {
|
|
privileges[grants_count] =
|
|
std::make_pair(it->second.first, it->second.second);
|
|
}
|
|
for (auto it = range.first; it != range.second; ++it) {
|
|
if (update_table(it->second.first, {old_user, old_host}, false,
|
|
Update_dynamic_privilege_table::REVOKE)) {
|
|
return true;
|
|
}
|
|
}
|
|
/*
|
|
Remove the old entries from the cache
|
|
*/
|
|
g_dynamic_privileges_map->erase(range.first, range.second);
|
|
|
|
/*
|
|
Grant the new authorization id the same privileges as the old and update
|
|
the cache with the new entries.
|
|
*/
|
|
while (grants_count > 0) {
|
|
--grants_count;
|
|
LEX_CSTRING priv = {privileges[grants_count].first.c_str(),
|
|
privileges[grants_count].first.length()};
|
|
if (grant_dynamic_privilege(priv, new_user, new_host,
|
|
privileges[grants_count].second,
|
|
update_table)) {
|
|
return true;
|
|
}
|
|
}
|
|
} // end for
|
|
} catch (...) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
/**
|
|
Initialize the default role map that keeps the content from the
|
|
default_roles table.
|
|
*/
|
|
void default_roles_init() { g_default_roles = new Default_roles; }
|
|
|
|
/**
|
|
Delete the default role instance
|
|
*/
|
|
void default_roles_delete() { delete g_default_roles; }
|
|
|
|
/**
|
|
Initialize the roles graph artifacts
|
|
*/
|
|
void roles_graph_init() {
|
|
g_authid_to_vertex = new Role_index_map;
|
|
g_granted_roles = new Granted_roles_graph;
|
|
}
|
|
|
|
/**
|
|
Delete the ACL role graph artifacts
|
|
*/
|
|
void roles_graph_delete() {
|
|
delete g_granted_roles;
|
|
delete g_authid_to_vertex;
|
|
}
|
|
|
|
/**
|
|
Initialize the roles caches that consist of the role graphs related
|
|
artifacts and default role map. In theory, default role map is
|
|
supposed to be a policy which has to be kept in sync with role graphs.
|
|
*/
|
|
void roles_init() {
|
|
roles_graph_init();
|
|
default_roles_init();
|
|
}
|
|
|
|
/**
|
|
Delete the role caches
|
|
*/
|
|
void roles_delete() {
|
|
roles_graph_delete();
|
|
default_roles_delete();
|
|
}
|
|
|
|
void dynamic_privileges_init() {
|
|
g_dynamic_privileges_map = new User_to_dynamic_privileges_map();
|
|
}
|
|
|
|
void dynamic_privileges_delete() {
|
|
if (g_dynamic_privileges_map) delete g_dynamic_privileges_map;
|
|
g_dynamic_privileges_map = 0;
|
|
}
|
|
|
|
User_to_dynamic_privileges_map *get_dynamic_privileges_map() {
|
|
return g_dynamic_privileges_map;
|
|
}
|
|
|
|
void set_dynamic_privileges_map(User_to_dynamic_privileges_map *map) {
|
|
g_dynamic_privileges_map = map;
|
|
}
|
|
|
|
User_to_dynamic_privileges_map *swap_dynamic_privileges_map(
|
|
User_to_dynamic_privileges_map *map) {
|
|
User_to_dynamic_privileges_map *old_map = g_dynamic_privileges_map;
|
|
g_dynamic_privileges_map = map;
|
|
return old_map;
|
|
}
|
|
|
|
bool assert_valid_privilege_id(const List<LEX_USER> *priv_list) {
|
|
/*
|
|
Because we need to combine the parsing rule of roles with the parsing
|
|
rule of dynamic privileges LEX_USER::user is used to carry the name of
|
|
the dynamic privilege.
|
|
*/
|
|
for (const LEX_USER &priv : *priv_list) {
|
|
Dynamic_privilege_register::iterator it =
|
|
get_dynamic_privilege_register()->find(
|
|
std::string(priv.user.str, priv.user.length));
|
|
if (it == get_dynamic_privilege_register()->end()) {
|
|
String error;
|
|
error.append("No such privilege identifier: ");
|
|
error.append(priv.user.str, priv.user.length);
|
|
my_error(ER_UNKNOWN_ERROR, MYF(0), error.c_ptr_quick());
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool check_authorization_id_string(THD *thd, LEX_STRING &mandatory_roles) {
|
|
bool error = false;
|
|
std::string authid_str(mandatory_roles.str, mandatory_roles.length);
|
|
Acl_cache_lock_guard acl_cache_lock(thd, Acl_cache_lock_mode::READ_MODE);
|
|
if (!acl_cache_lock.lock()) {
|
|
error = true;
|
|
} else {
|
|
iterate_comma_separated_quoted_string(
|
|
authid_str, [&thd, &error, &mandatory_roles](const std::string item) {
|
|
auto el = get_authid_from_quoted_string(item);
|
|
if (el.second != "" && el.first == "")
|
|
error = true;
|
|
else if (thd->security_context()
|
|
->has_global_grant({el.first, el.second},
|
|
consts::system_user, true)
|
|
.first) {
|
|
// Send error to both, client and server error log.
|
|
if (mysqld_server_started) {
|
|
my_error(ER_DA_AUTH_ID_WITH_SYSTEM_USER_PRIV_IN_MANDATORY_ROLES,
|
|
MYF(0), el.first.c_str(), el.second.c_str(),
|
|
consts::system_user.c_str());
|
|
LogErr(ERROR_LEVEL,
|
|
ER_AUTH_ID_WITH_SYSTEM_USER_PRIV_IN_MANDATORY_ROLES,
|
|
el.first.c_str(), el.second.c_str(),
|
|
consts::system_user.c_str());
|
|
error = true;
|
|
} else {
|
|
LogErr(WARNING_LEVEL,
|
|
ER_WARN_AUTH_ID_WITH_SYSTEM_USER_PRIV_IN_MANDATORY_ROLES,
|
|
el.first.c_str(), el.second.c_str(),
|
|
consts::system_user.c_str());
|
|
/*
|
|
It is safe to reset the LEX_STRING since it is allocated in
|
|
the memroot of THD.
|
|
*/
|
|
mandatory_roles.str = empty_c_string;
|
|
mandatory_roles.length = 0;
|
|
}
|
|
}
|
|
return error;
|
|
});
|
|
}
|
|
return error;
|
|
}
|
|
|
|
void get_mandatory_roles(std::vector<Role_id> *mandatory_roles) {
|
|
mysql_mutex_lock(&LOCK_mandatory_roles);
|
|
if (opt_mandatory_roles_cache) {
|
|
/* Use pre-parsed auth ids from the cache */
|
|
std::copy(g_mandatory_roles->begin(), g_mandatory_roles->end(),
|
|
std::back_inserter(*mandatory_roles));
|
|
mysql_mutex_unlock(&LOCK_mandatory_roles);
|
|
return;
|
|
}
|
|
g_mandatory_roles->clear();
|
|
/*
|
|
We set this flag to indicate that we've already parsed the mandatory_roles
|
|
option SQL variable.
|
|
*/
|
|
opt_mandatory_roles_cache = true;
|
|
std::string role_str;
|
|
role_str.append(opt_mandatory_roles.str, opt_mandatory_roles.length);
|
|
iterate_comma_separated_quoted_string(role_str, [&mandatory_roles](
|
|
const std::string item) {
|
|
auto el = get_authid_from_quoted_string(item);
|
|
if (el.second == "") el.second = "%";
|
|
Role_id role_id(el.first, el.second);
|
|
if (role_id.user() == "") {
|
|
LogErr(WARNING_LEVEL, ER_ANONYMOUS_AUTH_ID_NOT_ALLOWED_IN_MANDATORY_ROLES,
|
|
role_id.user().c_str(), role_id.host().c_str());
|
|
} else if (find_acl_user(role_id.host().c_str(), role_id.user().c_str(),
|
|
true) != NULL) {
|
|
if (std::find(g_mandatory_roles->begin(), g_mandatory_roles->end(),
|
|
role_id) == g_mandatory_roles->end()) {
|
|
mandatory_roles->push_back(role_id);
|
|
g_mandatory_roles->push_back(role_id);
|
|
}
|
|
} else {
|
|
LogErr(WARNING_LEVEL, ER_UNKNOWN_AUTH_ID_IN_MANDATORY_ROLE,
|
|
role_id.user().c_str(), role_id.host().c_str());
|
|
}
|
|
return false; // continue iterating
|
|
});
|
|
std::sort(g_mandatory_roles->begin(), g_mandatory_roles->end());
|
|
mysql_mutex_unlock(&LOCK_mandatory_roles);
|
|
}
|
|
|
|
void update_mandatory_roles(void) {
|
|
mysql_mutex_assert_owner(&LOCK_mandatory_roles);
|
|
opt_mandatory_roles_cache = false;
|
|
get_global_acl_cache()->increase_version();
|
|
}
|
|
|
|
Default_local_authid::Default_local_authid(const THD *thd) : m_thd(thd) {}
|
|
|
|
/**
|
|
Check if the security context can be created as a local authid
|
|
@param[out] sctx The authid to be checked.
|
|
@return Success status
|
|
@retval true an error occurred
|
|
@retval false success
|
|
*/
|
|
bool Default_local_authid::precheck(
|
|
Security_context *sctx MY_ATTRIBUTE((unused))) {
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
Create a local authid without modifying any tables.
|
|
@param[out] sctx The authid that will be extended with a user profile
|
|
@return Success status
|
|
@retval true an error occurred
|
|
@retval false success
|
|
*/
|
|
bool Default_local_authid::create(
|
|
Security_context *sctx MY_ATTRIBUTE((unused))) {
|
|
return false;
|
|
}
|
|
|
|
Grant_temporary_dynamic_privileges::Grant_temporary_dynamic_privileges(
|
|
const THD *thd, const std::vector<std::string> privs)
|
|
: m_thd(thd), m_privs(privs) {}
|
|
|
|
bool Grant_temporary_dynamic_privileges::precheck(
|
|
Security_context *sctx MY_ATTRIBUTE((unused))) {
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
Grant dynamic privileges to an in-memory global authid
|
|
@param sctx The authid to grant privileges to.
|
|
@return Success status
|
|
@retval true an error occurred
|
|
@retval false success
|
|
*/
|
|
bool Grant_temporary_dynamic_privileges::grant_privileges(
|
|
Security_context *sctx) {
|
|
return grant_dynamic_privileges_to_auth_id(
|
|
Role_id(sctx->priv_user(), sctx->priv_host()), m_privs);
|
|
}
|
|
|
|
void Drop_temporary_dynamic_privileges::operator()(Security_context *sctx) {
|
|
revoke_dynamic_privileges_from_auth_id(
|
|
Role_id(sctx->priv_user(), sctx->priv_host()), m_privs);
|
|
}
|
|
|
|
Grant_temporary_static_privileges::Grant_temporary_static_privileges(
|
|
const THD *thd, ulong privs)
|
|
: m_thd(thd), m_privs(privs) {}
|
|
|
|
bool Grant_temporary_static_privileges::precheck(
|
|
Security_context *sctx MY_ATTRIBUTE((unused))) {
|
|
return false;
|
|
}
|
|
|
|
bool Grant_temporary_static_privileges::grant_privileges(
|
|
Security_context *sctx) {
|
|
sctx->set_master_access(m_privs);
|
|
return false;
|
|
}
|
|
|
|
bool Security_context_factory::apply_pre_constructed_policies(
|
|
Security_context *sctx) {
|
|
bool error = true;
|
|
while (error) {
|
|
if (m_user_profile) {
|
|
// 1. Precheck conditions for creating the authid under current policy
|
|
if (m_user_profile(sctx, Security_context_policy::Precheck)) break;
|
|
// 2. Create the authid under the given policy
|
|
if (m_user_profile(sctx, Security_context_policy::Execute)) break;
|
|
}
|
|
if (m_privileges) {
|
|
// 3. Check preconditions for assigning privileges under the current
|
|
// policy
|
|
if (m_privileges(sctx, Security_context_policy::Precheck)) break;
|
|
// 4. Assign the privileges
|
|
if (m_privileges(sctx, Security_context_policy::Execute)) break;
|
|
}
|
|
if (m_static_privileges) {
|
|
// 5. Check preconditions for assigning privileges under the current
|
|
// policy
|
|
if (m_static_privileges(sctx, Security_context_policy::Precheck)) break;
|
|
// 6. Assign static privileges
|
|
if (m_static_privileges(sctx, Security_context_policy::Execute)) break;
|
|
}
|
|
error = false;
|
|
}
|
|
if (error == false && m_drop_policy) sctx->set_drop_policy(m_drop_policy);
|
|
return error;
|
|
}
|
|
|
|
Sctx_ptr<Security_context> Security_context_factory::create(
|
|
MEM_ROOT *mem_root) {
|
|
/* Setup default Security context */
|
|
Security_context *sctx = new Security_context(mem_root);
|
|
sctx->assign_user(m_user.c_str(), m_user.length());
|
|
sctx->assign_host(m_host.c_str(), m_host.length());
|
|
sctx->assign_priv_user(m_user.c_str(), m_user.length());
|
|
sctx->assign_priv_host(m_host.c_str(), m_host.length());
|
|
|
|
/* check if policies applied successfully */
|
|
if (apply_pre_constructed_policies(sctx)) {
|
|
/* Each specific policy must raise its own errors */
|
|
return nullptr;
|
|
}
|
|
|
|
return Sctx_ptr<Security_context>(sctx, [](Security_context *sctx) {
|
|
if (sctx->has_drop_policy()) {
|
|
sctx->execute_drop_policy();
|
|
if (sctx->has_executed_drop_policy()) delete sctx;
|
|
}
|
|
});
|
|
}
|
|
|
|
void Security_context_factory::apply_policies_to_security_ctx() {
|
|
Security_context *sctx = m_thd->security_context();
|
|
/* Update the security context iff it is not already updated. */
|
|
DBUG_ASSERT(sctx == &(m_thd->m_main_security_ctx));
|
|
|
|
/* check if policies applied successfully */
|
|
apply_pre_constructed_policies(sctx);
|
|
}
|
|
|
|
bool operator==(const Role_id &a, const std::string &b) {
|
|
std::string tmp;
|
|
a.auth_str(&tmp);
|
|
return tmp == b;
|
|
}
|
|
|
|
bool operator==(const std::pair<Role_id, bool> &a, const std::string &b) {
|
|
return a.first == b;
|
|
}
|
|
|
|
bool operator==(const Role_id &a, const Auth_id_ref &b) {
|
|
return ((a.user().length() == b.first.length) &&
|
|
(a.host().length() == b.second.length) &&
|
|
strncmp(a.user().c_str(), b.first.str, b.first.length) == 0 &&
|
|
my_strcasecmp(system_charset_info, a.host().c_str(), b.second.str) ==
|
|
0);
|
|
}
|
|
|
|
bool operator==(const Auth_id_ref &a, const Role_id &b) { return b == a; }
|
|
|
|
bool operator==(const std::pair<const Role_id, const Role_id> &a,
|
|
const Auth_id_ref &b) {
|
|
return ((a.second.user().length() == b.first.length) &&
|
|
(a.second.host().length() == b.second.length) &&
|
|
strncmp(a.second.user().c_str(), b.first.str, b.first.length) == 0 &&
|
|
my_strcasecmp(system_charset_info, a.second.host().c_str(),
|
|
b.second.str) == 0);
|
|
}
|
|
|
|
bool operator==(const Role_id &a, const Role_id &b) {
|
|
return ((a.user() == b.user()) && (a.host().length() == b.host().length()) &&
|
|
(my_strcasecmp(system_charset_info, a.host().c_str(),
|
|
b.host().c_str()) == 0));
|
|
}
|
|
|
|
bool operator<(const Auth_id_ref &a, const Auth_id_ref &b) {
|
|
if (a.first.length != b.first.length) return a.first.length < b.first.length;
|
|
if (a.second.length != b.second.length)
|
|
return a.second.length < b.second.length;
|
|
|
|
int first = memcmp(a.first.str, b.first.str, a.first.length);
|
|
if (first != 0) return first < 0;
|
|
int second = memcmp(a.second.str, b.second.str, a.second.length);
|
|
if (second != 0) return second < 0;
|
|
return false;
|
|
}
|
|
|
|
bool operator==(std::pair<const Role_id, std::pair<std::string, bool>> &a,
|
|
const std::string &b) {
|
|
return a.second.first == b;
|
|
}
|
|
|
|
bool operator==(const LEX_CSTRING &a, const LEX_CSTRING &b) {
|
|
return (a.length == b.length &&
|
|
((a.length == 0) || (memcmp(a.str, b.str, a.length) == 0)));
|
|
}
|
|
|
|
/**
|
|
Checks if current user needs to be changed in case it is same as the LEX_USER.
|
|
This check is useful to take backup of security context in case current user
|
|
renames itself.
|
|
|
|
@param sctx The security context to check
|
|
@param from_user_ptr User name to be renamed
|
|
|
|
@retval true security context need to be updated
|
|
@retval false otherwise
|
|
*/
|
|
bool do_update_sctx(Security_context *sctx, LEX_USER *from_user_ptr) {
|
|
const char *sctx_user = sctx->priv_user().str;
|
|
const char *sctx_host = sctx->priv_host().str;
|
|
const char *from_user = from_user_ptr->user.str;
|
|
const char *from_host = from_user_ptr->host.str;
|
|
|
|
/* If the user is connected as a proxied user, verify against proxy user */
|
|
if (sctx->proxy_user().str && *sctx->proxy_user().str != '\0') {
|
|
sctx_user = sctx->user().str;
|
|
}
|
|
|
|
/* Update the security context if current_user is going to be changed. */
|
|
if (strcmp(from_user, sctx_user) == 0 &&
|
|
my_strcasecmp(system_charset_info, from_host, sctx_host) == 0) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void update_sctx(Security_context *sctx, LEX_USER *to_user_ptr) {
|
|
const char *to_user = to_user_ptr->user.str;
|
|
const char *to_host = to_user_ptr->host.str;
|
|
if (!to_host) to_host = "";
|
|
if (!to_user) to_user = "";
|
|
|
|
sctx->assign_priv_user(to_user_ptr->user.str, to_user_ptr->user.length);
|
|
sctx->assign_priv_host(to_user_ptr->host.str, to_user_ptr->host.length);
|
|
}
|
|
|
|
/**
|
|
Checks if any of the users has SYSTEM_USER privilege then current user
|
|
must also have SYSTEM_USER privilege.
|
|
It is a wrapper over the Privilege_checker class that does
|
|
privilege checks for one user at a time.
|
|
|
|
@param [in] thd Thread handle for security context
|
|
@param [in] list List of user being processed
|
|
|
|
@returns If needed, whether current user has SYSTEM_USER privilege or not
|
|
@retval false Either none of the users in list has SYSTEM_USER
|
|
privilege or current user has SYSTEM_USER privilege
|
|
@retval true Failed in get_current_user() OR one of the user in the
|
|
list has SYSTEM_USER privilege but current user does not.
|
|
*/
|
|
bool check_system_user_privilege(THD *thd, List<LEX_USER> list) {
|
|
LEX_USER *user, *tmp_user;
|
|
Security_context *sctx = thd->security_context();
|
|
List_iterator<LEX_USER> user_list(list);
|
|
DBUG_ASSERT(assert_acl_cache_read_lock(thd));
|
|
if (list.size() == 0) return (false);
|
|
while ((tmp_user = user_list++)) {
|
|
if (!(user = get_current_user(thd, tmp_user))) {
|
|
my_error(ER_SPECIFIC_ACCESS_DENIED_ERROR, MYF(0),
|
|
consts::system_user.c_str());
|
|
return (true);
|
|
}
|
|
if (sctx->can_operate_with({user}, consts::system_user)) return (true);
|
|
}
|
|
return (false);
|
|
}
|