Commit db9f71f0 authored by Vicențiu Ciorbaru's avatar Vicențiu Ciorbaru Committed by Sergei Golubchik

Implemented the detection of the final access bits of a role via a DEPTH FIRST SEARCH

from the grant role to role graph.
parent 015d913c
...@@ -221,6 +221,7 @@ class ACL_USER :public ACL_ACCESS ...@@ -221,6 +221,7 @@ class ACL_USER :public ACL_ACCESS
LEX_STRING user; LEX_STRING user;
uint8 salt[SCRAMBLE_LENGTH + 1]; // scrambled password in binary form uint8 salt[SCRAMBLE_LENGTH + 1]; // scrambled password in binary form
uint8 salt_len; // 0 - no password, 4 - 3.20, 8 - 4.0, 20 - 4.1.1 uint8 salt_len; // 0 - no password, 4 - 3.20, 8 - 4.0, 20 - 4.1.1
uchar flags; // field used to store various state information
enum SSL_type ssl_type; enum SSL_type ssl_type;
const char *ssl_cipher, *x509_issuer, *x509_subject; const char *ssl_cipher, *x509_issuer, *x509_subject;
LEX_STRING plugin; LEX_STRING plugin;
...@@ -550,6 +551,15 @@ typedef struct st_role_grant ...@@ -550,6 +551,15 @@ typedef struct st_role_grant
char *r_uname; char *r_uname;
LEX_STRING hashkey; LEX_STRING hashkey;
} ROLE_GRANT_PAIR; } ROLE_GRANT_PAIR;
/*
Struct to hold the state of a node during a Depth First Search exploration
*/
template <class T> class NODE_STATE
{
public:
T *node_data; /* pointer to the node data */
uint neigh_idx; /* the neighbour that needs to be evaluated next */
};
static uchar* acl_role_map_get_key(ROLE_GRANT_PAIR *entry, size_t *length, static uchar* acl_role_map_get_key(ROLE_GRANT_PAIR *entry, size_t *length,
my_bool not_used __attribute__((unused))) my_bool not_used __attribute__((unused)))
...@@ -619,6 +629,11 @@ static void init_role_grant_pair(MEM_ROOT *mem, ROLE_GRANT_PAIR *entry, ...@@ -619,6 +629,11 @@ static void init_role_grant_pair(MEM_ROOT *mem, ROLE_GRANT_PAIR *entry,
#define NORMAL_HANDSHAKE_SIZE 6 #define NORMAL_HANDSHAKE_SIZE 6
#define ROLE_ASSIGN_COLUMN_IDX 42 #define ROLE_ASSIGN_COLUMN_IDX 42
/* various flags valid for ACL_USER */
#define IS_ROLE (1L << 0)
#define ROLE_VISITED (1L << 1)
#define ROLE_GRANTS_FINAL (1L << 2)
static DYNAMIC_ARRAY acl_hosts, acl_users, acl_dbs, acl_proxy_users; static DYNAMIC_ARRAY acl_hosts, acl_users, acl_dbs, acl_proxy_users;
static HASH acl_roles; static HASH acl_roles;
...@@ -661,7 +676,7 @@ static my_bool acl_role_reset_grant(ACL_USER *role, ...@@ -661,7 +676,7 @@ static my_bool acl_role_reset_grant(ACL_USER *role,
static my_bool acl_role_propagate_grants(ACL_USER *role, static my_bool acl_role_propagate_grants(ACL_USER *role,
void * not_used __attribute__((unused))); void * not_used __attribute__((unused)));
static int add_role_user_mapping(ROLE_GRANT_PAIR *mapping); static int add_role_user_mapping(ROLE_GRANT_PAIR *mapping);
static my_bool get_role_access(ACL_USER *role, ulong *access, my_bool use_initial); static my_bool get_role_access(ACL_USER *role, ulong *access);
/* /*
Enumeration of various ACL's and Hashes used in handle_grant_struct() Enumeration of various ACL's and Hashes used in handle_grant_struct()
...@@ -2129,8 +2144,7 @@ static my_bool acl_role_propagate_grants(ACL_USER *role, ...@@ -2129,8 +2144,7 @@ static my_bool acl_role_propagate_grants(ACL_USER *role,
void * not_used __attribute__((unused))) void * not_used __attribute__((unused)))
{ {
ulong access; ulong access;
get_role_access(role, &access, TRUE); get_role_access(role, &access);
role->access= access;
return 0; return 0;
} }
...@@ -2146,6 +2160,7 @@ my_bool acl_role_reset_grant(ACL_USER *role, ...@@ -2146,6 +2160,7 @@ my_bool acl_role_reset_grant(ACL_USER *role,
reset_dynamic(&role->role_grants); reset_dynamic(&role->role_grants);
/* Also reset the role access bits */ /* Also reset the role access bits */
role->access= role->initial_role_access; role->access= role->initial_role_access;
role->flags&= ~ROLE_GRANTS_FINAL;
return 0; return 0;
} }
...@@ -2169,49 +2184,122 @@ my_bool acl_user_reset_grant(ACL_USER *user, ...@@ -2169,49 +2184,122 @@ my_bool acl_user_reset_grant(ACL_USER *user,
TRUE: Error or invalid parameteres TRUE: Error or invalid parameteres
FALSE: All ok; FALSE: All ok;
*/ */
my_bool get_role_access(ACL_USER *role, ulong *access, my_bool use_initial) my_bool get_role_access(ACL_USER *role, ulong *access)
{ {
DBUG_ENTER("get_role_access"); DBUG_ENTER("get_role_access");
if (!role || !access) DBUG_ASSERT(role);
DBUG_RETURN(1); DBUG_ASSERT(access);
/* the search operation should always leave the ROLE_VISITED flag clean
for all nodes involved in the search */
DBUG_ASSERT(!(role->flags & ROLE_VISITED));
/*
There exists the possibility that the role's access bits are final
and we can just get the access bits without doing the more expensive
search operation
*/
if (role->flags & ROLE_GRANTS_FINAL)
{
*access= role->access;
DBUG_RETURN(FALSE);
}
ulong result= 0; DYNAMIC_ARRAY stack; /* stack used to simulate the recursive calls of DFS
HASH explored; /* temporary hash table to hold all explored roles */ * used a DYNAMIC_ARRAY to reduce the number of
List<ACL_USER *> queue; * malloc calls to a minimum */
NODE_STATE<ACL_USER> state; /* variable used to insert elements in the stack */
(void) my_hash_init2(&explored,50,system_charset_info, state.neigh_idx= 0;
0,0,0, (my_hash_get_key) acl_role_get_key, state.node_data= role;
NULL, 0); role->flags|= ROLE_VISITED;
my_hash_insert(&explored, (uchar*) role);
queue.push_back(&role); (void) my_init_dynamic_array(&stack,sizeof(NODE_STATE<ACL_USER>),
while (!queue.is_empty()) 20, 50, MYF(0));
insert_dynamic(&stack, &state);
while (stack.elements)
{ {
ACL_USER *current= *queue.pop(); NODE_STATE<ACL_USER> *curr_state= dynamic_element(&stack,
result|= (use_initial) ? current->initial_role_access : current->access; stack.elements - 1,
for (uint i=0 ; i < current->role_grants.elements ; i++) NODE_STATE<ACL_USER> *);
DBUG_ASSERT(curr_state->node_data->flags & ROLE_VISITED);
ACL_USER *current= state.node_data;
ACL_USER *neighbour= NULL;
/* iterate through the neighbours until a first valid jump-to
neighbour is found */
my_bool found= FALSE;
uint i;
for (i= curr_state->neigh_idx;
i < current->role_grants.elements && found == FALSE; i++)
{ {
ACL_USER *neighbour= *(dynamic_element(&current->role_grants, neighbour= *(dynamic_element(&current->role_grants, i, ACL_USER**));
i, ACL_USER**));
/* check if the neighbour is a role; pass if not*/ /* check if the neighbour is a role; pass if not*/
if (neighbour->host.hostname || if (!(neighbour->flags & ~IS_ROLE))
!find_acl_role(neighbour->user.str ? current->user.str : ""))
continue; continue;
/* check if it was already explored */ /* check if it forms a cycle */
HASH_SEARCH_STATE t; if (neighbour->flags & ROLE_VISITED)
if (my_hash_first(&explored, (uchar *)neighbour->user.str, {
neighbour->user.length, &t)) /* TODO the edge needs to be ignored */
continue;
}
/* check if it was already explored, in that case, just set the rights
and move on */
if (neighbour->flags & ROLE_GRANTS_FINAL)
{
current->access|= neighbour->access;
continue; continue;
}
/* set the current state search index to the next index
this needs to be done before inserting, so as to make sure that the
pointer is valid
*/
found= TRUE;
}
if (found)
{
/* we're going to have to take a look at the same neighbour again
once it is done being explored, thus, set the neigh_idx to "i"
which is the current neighbour that will be added on the stack*/
curr_state->neigh_idx= i;
/* add it to the TO-DO exploration queue */ /* some sanity checks */
queue.push_back(&neighbour); DBUG_ASSERT(!(neighbour->flags & ROLE_VISITED));
my_hash_insert(&explored, (uchar *)neighbour); DBUG_ASSERT(!(neighbour->flags & ROLE_GRANTS_FINAL));
/* add the neighbour on the stack */
neighbour->flags|= ROLE_VISITED;
state.neigh_idx= 0;
state.node_data= neighbour;
insert_dynamic(&stack, &state);
}
else
{
/* make sure we got a correct node */
DBUG_ASSERT(!(curr_state->node_data->flags & ROLE_GRANTS_FINAL));
DBUG_ASSERT(curr_state->node_data->flags & ROLE_VISITED);
/*
if we have finished with exploring the current node, pop it off the
stack
*/
curr_state= (NODE_STATE<ACL_USER> *)pop_dynamic(&stack);
curr_state->node_data->flags&= ~ROLE_VISITED; /* clear the visited bit */
curr_state->node_data->flags|= ROLE_GRANTS_FINAL;
/* add the own role's rights once it's finished exploring */
curr_state->node_data->access|= curr_state->node_data->initial_role_access;
} }
} }
my_hash_free(&explored);
*access= result; /* cleanup */
delete_dynamic(&stack);
/* finally set the access */
*access= role->access;
DBUG_RETURN(0); DBUG_RETURN(0);
} }
...@@ -2253,6 +2341,12 @@ int add_role_user_mapping(ROLE_GRANT_PAIR *mapping) ...@@ -2253,6 +2341,12 @@ int add_role_user_mapping(ROLE_GRANT_PAIR *mapping)
} }
push_dynamic(&user->role_grants, (uchar*) &role); push_dynamic(&user->role_grants, (uchar*) &role);
/*
Only add the other link if the grant is between a user
and a role, otherwise, the grant is unidirectional,
so as to prevent cycles in the grant role to role graph.
*/
if (!result)
push_dynamic(&role->role_grants, (uchar*) &user); push_dynamic(&role->role_grants, (uchar*) &user);
DBUG_PRINT("info", ("Found %s %s@%s having role granted %s@%s\n", DBUG_PRINT("info", ("Found %s %s@%s having role granted %s@%s\n",
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment