Skip to content

File authenticate_cache.cpp

File List > cubrid > src > object > authenticate_cache.cpp

Go to the documentation of this file

/*
 *
 * Copyright 2016 CUBRID Corporation
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 */

/*
 * authenticate_cache.cpp - Authorization manager's cache structure and their routine
 */

#include "authenticate_cache.hpp"

#include "authenticate.h"
#include "authenticate_grant.hpp" /* apply_grants */
#include "dbtype_function.h"
#include "schema_manager.h"
#include "set_object.h"

au_class_cache::au_class_cache (int depth)
{
  next = NULL;
  class_ = NULL;
  data = new unsigned int[depth];
  std::fill_n (data, depth, AU_CACHE_INVALID);
}

au_class_cache::~au_class_cache ()
{
  if (data)
    {
      delete [] data;
      data = nullptr;
    }
}

authenticate_cache::authenticate_cache ()
{
  init ();
}

/*
 * init_caches - Called during au_init().  Initialize all of the cache
 *               related variables.
 *   return: none
 */
void
authenticate_cache::init (void)
{
  user_cache = NULL;
  class_caches = NULL;
  cache_depth = 0;
  cache_max = 0;
  cache_increment = 4;
  cache_index = -1;
}

/*
 * flush_caches - Called during au_final(). Free the authorization cache
 *                structures and initialize the global variables
 *                to their default state.
 *   return : none
 */
void
authenticate_cache::flush (void)
{
  AU_USER_CACHE *u, *nextu;
  AU_CLASS_CACHE *c, *nextc;

  for (c = class_caches, nextc = NULL; c != NULL; c = nextc)
    {
      nextc = c->next;
      c->class_->auth_cache = NULL;
      free_class_cache (c);
    }
  for (u = user_cache, nextu = NULL; u != NULL; u = nextu)
    {
      nextu = u->next;
      free_user_cache (u);
    }
  user_name_cache.clear ();

  for (auto &it : procedure_cache)
    {
      if (it.second)
    {
      delete it.second;
    }
    }
  procedure_cache.clear ();

  /* clear the associated globals */
  init ();
}

/*
 * update_cache -  This is the main function for parsing all of
 *                 the authorization information for a particular class and
 *                 caching it in the class structure.
 *                 This will be called once after each successful
 *                 read/write lock. It needs to be fast.
 *   return: error code
 *   classop(in):  class MOP to authorize
 *   sm_class(in): direct pointer to class structure
 *   cache(in):
 */
int
authenticate_cache::update (DB_OBJECT_TYPE obj_type, MOP mop, void *ptr)
{
  int error = NO_ERROR;
  DB_SET *groups = NULL;
  int i, save, card;
  DB_VALUE value;
  MOP group, auth;
  bool is_member = false;
  unsigned int *bits = NULL;
  bool need_pop_er_stack = false;
  MOP owner;

  const unsigned int FULL_AUTH = (obj_type == DB_OBJECT_CLASS) ? AU_FULL_AUTHORIZATION : AU_EXECUTE;

  /*
   * must disable here because we may be updating the cache of the system
   * objects and we need to read them in order to update etc.
   */
  AU_DISABLE (save);

  er_stack_push ();

  need_pop_er_stack = true;

  if (obj_type == DB_OBJECT_CLASS)
    {
      SM_CLASS *sm_class = (SM_CLASS *) ptr;
      bits = get_cache_bits ((SM_CLASS *) ptr);
      owner = sm_class->owner;
    }
  else if (obj_type == DB_OBJECT_PROCEDURE)
    {
      bits = get_procedure_cache_bits (mop);
      owner = (MOP) ptr;
    }

  if (bits == NULL)
    {
      assert (false);
      return er_errid ();
    }

  /* initialize the cache bits */
  *bits = AU_NO_AUTHORIZATION;

  if (owner == NULL)
    {
      /* This shouldn't happen - assign it to the DBA */
      error = ER_AU_CLASS_WITH_NO_OWNER;
      er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, error, 0);

      goto end;
    }

  is_member = au_is_dba_group_member (Au_user);
  if (is_member)
    {
      *bits = FULL_AUTH;
      goto end;
    }

  /* there might be errors */
  error = er_errid ();
  if (error != NO_ERROR)
    {
      goto end;
    }

  if (ws_is_same_object (Au_user, owner) || ws_is_same_object (Au_public_user, owner))
    {
      /* might want to allow grant/revoke on self */
      *bits = FULL_AUTH;
      goto end;
    }

  if (au_get_set (Au_user, "groups", &groups) != NO_ERROR)
    {
      error = ER_AU_ACCESS_ERROR;
      er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, error, 2, "groups", AU_USER_CLASS_NAME);

      goto end;
    }

  db_make_object (&value, owner);

  is_member = set_ismember (groups, &value);

  /* there might be errors */
  error = er_errid ();
  if (error != NO_ERROR)
    {
      goto end;
    }

  if (is_member)
    {
      /* we're a member of the owning group */
      *bits = FULL_AUTH;
    }
  else if (au_get_object (Au_user, "authorization", &auth) != NO_ERROR)
    {
      error = ER_AU_ACCESS_ERROR;
      er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, error, 2, "authorization", AU_USER_CLASS_NAME);
    }
  else
    {
      /* apply local grants */
      error = apply_grants (auth, mop, bits);
      if (error != NO_ERROR)
    {
      goto end;
    }

      /* apply grants from all groups */
      card = set_cardinality (groups);

      /* there might be errors */
      error = er_errid ();
      if (error != NO_ERROR)
    {
      goto end;
    }

      for (i = 0; i < card; i++)
    {
      /* will have to handle deleted groups here ! */
      error = au_set_get_obj (groups, i, &group);
      if (error != NO_ERROR)
        {
          goto end;
        }

      if (ws_is_same_object (group, Au_dba_user))
        {
          /* someones on the DBA member list, give them power */
          *bits = FULL_AUTH;
        }
      else
        {
          error = au_get_object (group, "authorization", &auth);
          if (error != NO_ERROR)
        {
          goto end;
        }

          error = apply_grants (auth, mop, bits);
          if (error != NO_ERROR)
        {
          goto end;
        }
        }
    }
    }

end:

  if (groups != NULL)
    {
      set_free (groups);
    }

  if (need_pop_er_stack)
    {
      if (error == NO_ERROR)
    {
      er_stack_pop ();
    }
      else
    {
      er_stack_pop_and_keep_error ();
    }
    }

  AU_ENABLE (save);

  return (error);
}

// setter/getter of cache_index
int
authenticate_cache::get_cache_index ()
{
  return cache_index;
}

void
authenticate_cache::set_cache_index (int idx)
{
  cache_index = idx;
}

/*
 * au_free_authorization_cache -  This removes a class cache from the global
 *                                cache list, detaches it from the class
 *                                and frees it.
 *   return: none
 *   cache(in): class cache
 */
void
authenticate_cache::free_authorization_cache (void *cache)
{
  AU_CLASS_CACHE *c, *prev;

  prev = NULL;
  if (cache != NULL)
    {
      for (c = class_caches, prev = NULL; c != NULL && c != cache; c = c->next)
    {
      prev = c;
    }
      if (c != NULL)
    {
      if (prev == NULL)
        {
          class_caches = c->next;
        }
      else
        {
          prev->next = c->next;
        }
      free_class_cache ((AU_CLASS_CACHE *) cache);
    }
    }
}

/*
 * AUTHORIZATION CACHES
 */

/*
 * au_make_class_cache - Allocates and initializes a new class cache
 *    return: new cache structure
 *    depth(in): number of elements to include in the cache
 */
AU_CLASS_CACHE *
authenticate_cache::make_class_cache (int depth)
{
  AU_CLASS_CACHE *new_class_cache = NULL;
  int i;
  size_t size;

  if (depth <= 0)
    {
      er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, ER_GENERIC_ERROR, 0);
    }
  else
    {
      new_class_cache = new (std::nothrow) AU_CLASS_CACHE (depth);
      if (new_class_cache == NULL)
    {
      er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, ER_OUT_OF_VIRTUAL_MEMORY, 1, sizeof (AU_CLASS_CACHE));
      return NULL;
    }
    }

  return new_class_cache;
}

/*
 * au_free_class_cache - Frees a class cache
 *    return: none
 *    cache(in): cache to free
 */
void
authenticate_cache::free_class_cache (AU_CLASS_CACHE *cache)
{
  if (cache != NULL)
    {
      delete cache;
    }
}

/*
 * au_install_class_cache - This allocates a new class cache and attaches
 *                          it to a class.
 *   return: new class cache
 *   class(in): class structure to get the new cache
 *
 * Note: Once a cache/class association has been made, we also put the
 *       cache on the global cache list so we can maintain it consistently.
 */
AU_CLASS_CACHE *
authenticate_cache::install_class_cache (SM_CLASS *sm_class)
{
  AU_CLASS_CACHE *new_class_cache;

  new_class_cache = make_class_cache (cache_max);
  if (new_class_cache != NULL)
    {
      new_class_cache->next = class_caches;
      class_caches = new_class_cache;
      new_class_cache->class_ = sm_class;
      sm_class->auth_cache = new_class_cache;
    }

  return new_class_cache;
}

unsigned int *
authenticate_cache::get_cache_bits (SM_CLASS *sm_class)
{
  AU_CLASS_CACHE *cache = (AU_CLASS_CACHE *) sm_class->auth_cache;
  if (cache == NULL)
    {
      cache = install_class_cache (sm_class);
      if (cache == NULL)
    {
      assert (er_errid () != NO_ERROR);
      return NULL;
    }
    }

  return &cache->data[cache_index];
}

unsigned int *
authenticate_cache::get_procedure_cache_bits (MOP proc_mop)
{
  std::vector<unsigned int> *bits = nullptr;

  auto it = procedure_cache.find (proc_mop);
  if (it == procedure_cache.end ())
    {
      bits = new std::vector<unsigned int> (cache_max, AU_CACHE_INVALID);
      procedure_cache[proc_mop] = bits;
    }
  else
    {
      bits = it->second;
    }

  bits->resize (cache_max, AU_CACHE_INVALID);
  return & (*bits)[cache_index];
}

/*
 * au_extend_class_caches - This extends the all existing class caches so they
 *                          can contain an additional element.
 *   return: error code
 *   index(out): next available index
 *
 * Note: If we have already preallocated some extra elements it will use one
 *       and avoid reallocating all the caches. If we have no extra elements,
 *       we grow all the caches by a certain amount.
 */
int
authenticate_cache::extend_class_caches (int *index)
{
  int error = NO_ERROR;
  AU_CLASS_CACHE *c, *new_list, *new_entry, *next;
  int new_max, i;

  if (cache_depth < cache_max)
    {
      *index = cache_depth;
      cache_depth++;
    }
  else
    {
      new_list = NULL;
      new_max = cache_max + cache_increment;

      for (c = class_caches; c != NULL && !error; c = c->next)
    {
      new_entry = make_class_cache (new_max);
      if (new_entry == NULL)
        {
          assert (er_errid () != NO_ERROR);
          error = er_errid ();
        }
      else
        {
          for (i = 0; i < cache_depth; i++)
        {
          new_entry->data[i] = c->data[i];
        }
          new_entry->class_ = c->class_;
          new_entry->next = new_list;
          new_list = new_entry;
        }
    }

      if (!error)
    {
      for (c = class_caches, next = NULL; c != NULL; c = next)
        {
          next = c->next;
          c->class_->auth_cache = NULL;
          free_class_cache (c);
        }
      for (c = new_list; c != NULL; c = c->next)
        {
          c->class_->auth_cache = c;
        }

      class_caches = new_list;
      cache_max = new_max;
      *index = cache_depth;
      cache_depth++;
    }
    }

  return error;
}

/*
 * make_user_cache - This creates a new user cache and appends it to the user cache list
 *   return: user cache
 *   name(in): user name
 *   user(in): user MOP
 *
 */
AU_USER_CACHE *
authenticate_cache::make_user_cache (const char *name, DB_OBJECT *user, bool sanity_check)
{
  AU_USER_CACHE *new_user_cache = nullptr;
  assert (name != nullptr);
  assert (user != nullptr);

  if (sanity_check)
    {
      /*
      * User wasn't in the cache, add it and extend the existing class
      * caches.  First do a little sanity check just to make sure this
      * is a user object.
      */
      DB_OBJECT *class_mop = sm_get_class (user);
      if (class_mop == NULL)
    {
      assert (er_errid () != NO_ERROR);
      return NULL;
    }
      else if (class_mop != Au_user_class)
    {
      er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, ER_AU_CORRUPTED, 0); /* need a better error */
      return NULL;
    }
    }

  std::string upper_case_name (name);
  std::transform (upper_case_name.begin(), upper_case_name.end(), upper_case_name.begin(), ::toupper);

  if ((new_user_cache = find_user_cache_by_name (upper_case_name.c_str ())) != nullptr)
    {
      return new_user_cache;
    }

  new_user_cache = new AU_USER_CACHE;
  if (new_user_cache != nullptr)
    {
      new_user_cache->next = user_cache;
      user_cache = new_user_cache;

      new_user_cache->name = upper_case_name;
      new_user_cache->user = user;
      new_user_cache->index = -1;

      user_name_cache.insert (std::pair<std::string, AU_USER_CACHE *> (upper_case_name, new_user_cache));
    }

  return new_user_cache;
}

/*
 * find_user_cache_by_name - This determines the cache for the given user name.
 *   return: user cache
 *   name(in): user name
 *
 */
AU_USER_CACHE *
authenticate_cache::find_user_cache_by_name (const char *name)
{
  if (name == nullptr)
    {
      return nullptr;
    }

  AU_USER_CACHE *user_cache = nullptr;

  const auto &it = user_name_cache.find (name);
  if (it != user_name_cache.end())
    {
      user_cache = it->second;
    }

  return user_cache;
}

/*
 * find_user_cache_by_mop - This determines the cache for the given user MOP.
 *   return: user cache
 *   user(in): user mop
 *
 */
AU_USER_CACHE *
authenticate_cache::find_user_cache_by_mop (DB_OBJECT *user)
{
  AU_USER_CACHE *u;

  for (u = user_cache; u != NULL && !ws_is_same_object (u->user, user); u = u->next)
    ;

  return u;
}

int
authenticate_cache::get_user_cache_index (AU_USER_CACHE *cache, int *index)
{
  int error = NO_ERROR;

  if (cache->index == -1)
    {
      error = extend_class_caches (index);
      if (error == NO_ERROR)
    {
      cache->index = *index;
    }
    }
  else
    {
      *index = cache->index;
    }

  return error;
}

/*
 * free_user_cache - Frees a user cache. Make sure to clear the MOP pointer.
 *   returns: none
 *   u(in): user cache
 */
void
authenticate_cache::free_user_cache (AU_USER_CACHE *u)
{
  if (u != NULL)
    {
      u->next = NULL;
      u->user = NULL;       /* clear GC roots */

      if (!u->name.empty ())
    {
      user_name_cache.erase (u->name);
    }

      delete u;
    }
}

/*
 * reset_cache_for_user_and_class - This is called whenever a grant or revoke
 *                                  operation is performed. It resets the
 *                                  caches only for a particular
 *                                  user/class pair
 *   return : none
 *   class(in): class structure
 *
 * Note: This was originally written so that only the authorization
 *       cache for the given user was cleared.  This does not work however
 *       if we're changing authorization for a group and there are members
 *       of that group already cached.
 *       We could be smart and try to invalidate the caches of all
 *       members of this user but instead just go ahead and invalidate
 *       everyone's cache for this class.  This isn't optimal but doesn't really
 *       matter that much.  grant/revoke don't happen very often.
 */
void
authenticate_cache::reset_cache_for_user_and_class (SM_CLASS *sm_class)
{
  AU_USER_CACHE *u;
  AU_CLASS_CACHE *c;

  for (c = class_caches; c != NULL && c->class_ != sm_class; c = c->next);
  if (c != NULL)
    {
      /*
       * invalide every user's cache for this class_, could be more
       * selective and do only the given user and its members
       */
      for (u = user_cache; u != NULL; u = u->next)
    {
      // NOTE: u->index is initalized as -1
      // This means that the user is cached in a context that is independent of the class,
      // for example, obtaining the user object in the execution context of another database object,
      // such as the owner's right of procedure or a user management method.
      if (u->index >= 0)
        {
          c->data[u->index] = AU_CACHE_INVALID;
        }
    }
    }
}

void
authenticate_cache::reset_cache_for_user_and_procedure (MOP obj)
{
  AU_USER_CACHE *u;
  AU_CLASS_CACHE *c;

  for (auto &it : procedure_cache)
    {
      if (it.first == obj)
    {
      /*
       * invalide every user's cache for this procedure mop, could be more
       * selective and do only the given user and its members
       */
      for (u = user_cache; u != NULL; u = u->next)
        {
          // NOTE: u->index is initalized as -1
          // This means that the user is cached in a context that is independent of the class,
          // for example, obtaining the user object in the execution context of another database object,
          // such as the owner's right of procedure or a user management method.
          if (u->index >= 0)
        {
          it.second->resize (u->index + 1, AU_CACHE_INVALID); // safe guard
          it.second->at (u->index) = AU_CACHE_INVALID;
        }
        }
    }
    }
}

/*
 * au_reset_authorization_caches - This is called by ws_clear_all_hints()
 *                                 and ws_abort_mops() on transaction
 *                                 boundaries.
 *   return: none
 *
 * Note: We reset all the authorization caches at this point.
 *       This sets all of the authorization entries in a cache to be invalid.
 *       Normally this is done when the authorization for this
 *       class changes in some way.  The next time the cache is used, it
 *       will force the recomputation of the authorization bits.
 *       We should try to be smarter and flush the caches only when we know
 *       that the authorization catalogs have changed in some way.
 */

void
authenticate_cache::reset_authorization_caches (void)
{
  AU_CLASS_CACHE *c;
  int i;

  // reset class cache
  for (c = class_caches; c != NULL; c = c->next)
    {
      for (i = 0; i < cache_depth; i++)
    {
      c->data[i] = AU_CACHE_INVALID;
    }
    }

  // reset procedure cache
  for (auto &it : procedure_cache)
    {
      std::vector <unsigned int> *data = it.second;
      std::fill (data->begin (), data->end(), AU_CACHE_INVALID);
    }
}

/*
 * remove_user_cache - This is called when a user object is deleted.
 *   return: none
 *   user(in): user object
 *
 */
void
authenticate_cache::remove_user_cache (MOP user)
{
  AU_USER_CACHE *u, *prevu, *nextu;

  prevu = NULL;
  for (u = user_cache; u != NULL; u = nextu)
    {
      nextu = u->next;
      if (ws_is_same_object (u->user, user))
    {
      if (prevu == NULL)
        {
          user_cache = nextu;
        }
      else
        {
          prevu->next = nextu;
        }
      free_user_cache (u);
      break;
    }
      else
    {
      prevu = u;
    }
    }
}

/*
 * reset_user_cache - This is called when a user object is invalidated.
 *   return: none
 */
void
authenticate_cache::reset_user_cache (void)
{
  AU_USER_CACHE *u = user_cache;
  while (u != NULL)
    {
      AU_USER_CACHE *nextu = u->next;
      free_user_cache (u);
      u = nextu;
    }
  user_cache = NULL;
}

/*
 * au_print_cache() -
 *   return: none
 *   cache(in):
 *   fp(in):
 */
void
authenticate_cache::print_cache (int cache, FILE *fp)
{
  int i, mask, auth, option;
  const char *auth_type_name[] =
  {
    "select", "insert", "update", "delete", "alter", "index", "execute"
  };

  if (cache < 0)
    {
      fprintf (fp, msgcat_message (MSGCAT_CATALOG_CUBRID, MSGCAT_SET_AUTHORIZATION, MSGCAT_AUTH_INVALID_CACHE));
    }
  else
    {
      for (i = 0; i < 7; i++)
    {
      mask = 1 << i;
      auth = cache & mask;
      option = cache & (mask << AU_GRANT_SHIFT);
      if (option)
        {
          fprintf (fp, "*");
        }
      if (auth)
        {
          fprintf (fp, "%s ", auth_type_name[i]);
        }
    }
    }
  fprintf (fp, "\n");
}