Drizzled Public API Documentation

module.cc
1 /* -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
2  * vim:expandtab:shiftwidth=2:tabstop=2:smarttab:
3  *
4  * Copyright (C) 2010 Monty Taylor <mordred@inaugust.com>
5  * Copyright (C) 2011 Canonical, Ltd.
6  * Author: Clint Byrum <clint.byrum@canonical.com>
7  *
8  * Copied from simple_user_policy
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; version 2 of the License.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
22  */
23 
24 #include <config.h>
25 
26 #include <boost/unordered_set.hpp>
27 #include <boost/thread/locks.hpp>
28 
29 #include <drizzled/plugin/authorization.h>
31 
32 #include "policy.h"
33 
34 #include <fstream>
35 
36 namespace po= boost::program_options;
37 
38 using namespace std;
39 using namespace drizzled;
40 
41 namespace regex_policy
42 {
43 
44 uint64_t max_cache_buckets= DEFAULT_MAX_CACHE_BUCKETS;
45 uint64_t max_lru_length= DEFAULT_MAX_LRU_LENGTH;
46 
47 static int init(module::Context &context)
48 {
49  const module::option_map &vm= context.getOptions();
50 
51  max_cache_buckets= vm["max-cache-buckets"].as<uint64_t>();
52  if (max_cache_buckets < 1)
53  {
54  errmsg_printf(error::ERROR, _("max-cache-buckets is too low, must be greater than 0"));
55  return 1;
56  }
57  max_lru_length= vm["max-lru-length"].as<uint64_t>();
58  if (max_lru_length < 1)
59  {
60  errmsg_printf(error::ERROR, _("max-lru-length is too low, must be greater than 0"));
61  return 1;
62  }
63  Policy *policy= new Policy(fs::path(vm["policy"].as<string>()));
64  if (not policy->loadFile())
65  {
66  errmsg_printf(error::ERROR, _("Could not load regex policy file: %s\n"),
67  (policy ? policy->getError().str().c_str() : _("Unknown")));
68  delete policy;
69  return 1;
70  }
71 
72  context.add(policy);
73  context.registerVariable(new sys_var_const_string_val("policy", vm["policy"].as<string>()));
74 
75  return 0;
76 }
77 
78 static void init_options(drizzled::module::option_context &context)
79 {
80  context("policy",
81  po::value<string>()->default_value(DEFAULT_POLICY_FILE.string()),
82  N_("File to load for regex authorization policies"));
83  context("max-cache-buckets",
84  po::value<uint64_t>()->default_value(DEFAULT_MAX_CACHE_BUCKETS),
85  N_("Maximum buckets for authorization cache"));
86  context("max-lru-length",
87  po::value<uint64_t>()->default_value(DEFAULT_MAX_LRU_LENGTH),
88  N_("Maximum number of LRU entries to track at once"));
89 }
90 
91 bool Policy::loadFile()
92 {
93  ifstream file(policy_file.string().c_str());
94  boost::regex comment_re;
95  boost::regex empty_re;
96  boost::regex table_matches_re;
97  boost::regex process_matches_re;
98  boost::regex schema_matches_re;
99 
100  try
101  {
102  comment_re= comment_regex;
103  empty_re= empty_regex;
104  table_matches_re= table_match_regex;
105  process_matches_re= process_match_regex;
106  schema_matches_re= schema_match_regex;
107  }
108  catch (const std::exception &e)
109  {
110  error << e.what();
111  return false;
112  }
113 
114  if (! file.is_open())
115  {
116  error << "Unable to open regex policy file: " << policy_file.string();
117  return false;
118  }
119 
120  int lines= 0;
121  try
122  {
123  while (! file.eof())
124  {
125  ++lines;
126  string line;
127  getline(file, line);
128  if (boost::regex_match(line, comment_re))
129  {
130  continue;
131  }
132  if (boost::regex_match(line, empty_re))
133  {
134  continue;
135  }
136  boost::smatch matches;
137  PolicyItemList *policies;
138  if (boost::regex_match(line, matches, table_matches_re, boost::match_extra))
139  {
140  policies= &table_policies;
141  }
142  else if (boost::regex_match(line, matches, process_matches_re, boost::match_extra))
143  {
144  policies= &process_policies;
145  }
146  else if (boost::regex_match(line, matches, schema_matches_re, boost::match_extra))
147  {
148  policies= &schema_policies;
149  }
150  else
151  {
152  throw std::exception();
153  }
154  string user_regex;
155  string object_regex;
156  string action;
157  user_regex= matches[MATCH_REGEX_USER_POS];
158  object_regex= matches[MATCH_REGEX_OBJECT_POS];
159  action= matches[MATCH_REGEX_ACTION_POS];
160  PolicyItem *i;
161  try
162  {
163  i= new PolicyItem(user_regex, object_regex, action);
164  }
165  catch (const std::exception &e)
166  {
167  error << "Bad policy item: user=" << user_regex << " object=" << object_regex << " action=" << action;
168  throw std::exception();
169  }
170  policies->push_back(i);
171  }
172  return true;
173  }
174  catch (const std::exception &e)
175  {
176  /* On any non-EOF break, unparseable line */
177  error << "Unable to parse line " << lines << " of policy file " << policy_file.string() << ":" << e.what();
178  return false;
179  }
180 }
181 
182 void clearPolicyItemList(PolicyItemList policies)
183 {
184  for (PolicyItemList::iterator x= policies.begin() ; x != policies.end() ; ++x)
185  {
186  delete *x;
187  *x= NULL;
188  }
189 }
190 
191 Policy::~Policy()
192 {
193  clearPolicyItemList(table_policies);
194  clearPolicyItemList(process_policies);
195  clearPolicyItemList(schema_policies);
196 }
197 
198 bool Policy::restrictObject(const drizzled::identifier::User &user_ctx,
199  const string &obj, const PolicyItemList &policies,
200  CheckMap &check_cache)
201 {
202  CheckItem c(user_ctx.username(), obj, check_cache);
203  if (!c.hasCachedResult())
204  {
205  PolicyItemList::const_iterator m= find_if(policies.begin(), policies.end(), c);
206  if (m != policies.end())
207  {
208  c.setCachedResult((*m)->isRestricted());
209  }
210  else
211  {
212  /* TODO: make default action configurable */
213  c.setCachedResult(false);
214  }
215  }
216  return c.getCachedResult();
217 }
218 
219 bool Policy::restrictSchema(const drizzled::identifier::User &user_ctx,
220  const drizzled::identifier::Schema& schema)
221 {
222  return restrictObject(user_ctx, schema.getSchemaName(), schema_policies, schema_check_cache);
223 }
224 
225 bool Policy::restrictProcess(const drizzled::identifier::User &user_ctx,
226  const drizzled::identifier::User &session_ctx)
227 {
228  return restrictObject(user_ctx, session_ctx.username(), process_policies, process_check_cache);
229 }
230 
231 bool Policy::restrictTable(const drizzled::identifier::User& user_ctx,
232  const drizzled::identifier::Table& table)
233 {
234  return restrictObject(user_ctx, table.getTableName(), table_policies, table_check_cache);
235 }
236 
237 bool CheckItem::operator()(PolicyItem *p)
238 {
239  if (p->userMatches(user))
240  {
241  errmsg_printf(error::INSPECT, _("User %s matches regex\n"), user.c_str());
242  if (p->objectMatches(object))
243  {
244  errmsg_printf(error::INSPECT, _("Object %s matches regex %s (%s)\n"),
245  object.c_str(),
246  p->getObject().c_str(),
247  p->getAction());
248  return true;
249  }
250  errmsg_printf(error::INSPECT, _("Object %s NOT restricted by regex %s (%s)\n"),
251  object.c_str(),
252  p->getObject().c_str(),
253  p->getAction());
254  }
255  return false;
256 }
257 
258 CheckItem::CheckItem(const std::string &user_in, const std::string &obj_in, CheckMap &check_cache_in)
259  : user(user_in), object(obj_in), has_cached_result(false), check_cache(check_cache_in)
260 {
261  UnorderedCheckMap::iterator check_val;
262  key= user + "_" + object;
263 
264  if ((check_val= check_cache.find(key)) != check_cache.end())
265  {
266  /* It was in the cache, no need to do any more lookups */
267  cached_result= check_val->second;
268  has_cached_result= true;
269  }
270 }
271 
272 UnorderedCheckMap::iterator CheckMap::find(std::string const &k)
273 {
274  /* tack on to LRU list */
275  boost::mutex::scoped_lock lock(lru_mutex);
276  lru.push_back(k);
277  if (lru.size() > max_lru_length)
278  {
279  /* Fold all of the oldest entries into a single list at the front */
280  uint64_t size_halfway= lru.size() / 2;
281  LruList::iterator halfway= lru.begin();
282  halfway += size_halfway;
283  boost::unordered_set<std::string> uniqs;
284  uniqs.insert(lru.begin(), halfway);
285 
286  /* If we can save space, drop the oldest half */
287  if (size_halfway < uniqs.size())
288  {
289  lru.erase(lru.begin(), halfway);
290 
291  /* Re-add set elements to front */
292  lru.insert(lru.begin(), uniqs.begin(), uniqs.end());
293  }
294  }
295  lock.unlock();
296  boost::shared_lock<boost::shared_mutex> map_lock(map_mutex);
297  return map.find(k);
298 }
299 
300 void CheckMap::insert(std::string const &k, bool v)
301 {
302  boost::unique_lock<boost::shared_mutex> map_lock(map_mutex);
303  /* add our new hotness to the map */
304  map[k]=v;
305  /* Now prune if necessary */
306  if (map.bucket_count() > max_cache_buckets)
307  {
308  /* Determine LRU key by running through the LRU list */
309  boost::unordered_set<std::string> found;
310 
311  /* Must unfortunately lock the LRU list while we traverse it */
312  boost::mutex::scoped_lock lock(lru_mutex);
313  for (LruList::reverse_iterator x= lru.rbegin(); x < lru.rend(); ++x)
314  {
315  if (found.find(*x) == found.end())
316  {
317  /* Newly found key */
318  if (found.size() >= max_cache_buckets)
319  {
320  /* Since found is already as big as the cache can be, anything else
321  is LRU */
322  map.erase(*x);
323  }
324  else
325  {
326  found.insert(*x);
327  }
328  }
329  }
330  if (map.bucket_count() > max_cache_buckets)
331  {
332  /* Still too big. */
333  if (lru.size())
334  {
335  /* Just delete the oldest item */
336  map.erase(*(lru.begin()));
337  }
338  else
339  {
340  /* Nothing to delete, warn */
341  errmsg_printf(error::WARN,
342  _("Unable to reduce size of cache below max buckets (current buckets=%" PRIu64 ")"),
343  static_cast<uint64_t>(map.bucket_count()));
344  }
345  }
346  lru.clear();
347  lock.unlock();
348  }
349 }
350 
351 void CheckItem::setCachedResult(bool result)
352 {
353  check_cache.insert(key, result);
354  has_cached_result= true;
355  cached_result= result;
356 }
357 
358 } /* namespace regex_policy */
359 
360 DRIZZLE_DECLARE_PLUGIN
361 {
362  DRIZZLE_VERSION_ID,
363  "regex_policy",
364  "1.0",
365  "Clint Byrum",
366  N_("Authorization using a regex-matched policy file"),
367  PLUGIN_LICENSE_GPL,
368  regex_policy::init,
369  NULL,
370  regex_policy::init_options
371 }
372 DRIZZLE_DECLARE_PLUGIN_END;