1/* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */ 2/* 3 * Copyright 2017 Couchbase, Inc. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17#pragma once 18 19/** 20 * This file contains the definitions of the privilege system used 21 * by the memcached core. For more information see rbac.md in the 22 * docs directory. 23 */ 24#include <cJSON.h> 25#include <memcached/rbac/privileges.h> 26 27#include <cbsasl/cbsasl.h> 28#include <bitset> 29#include <cstdint> 30#include <limits> 31#include <memory> 32#include <string> 33#include <unordered_map> 34#include <vector> 35 36namespace cb { 37namespace rbac { 38 39/** 40 * An array containing all of the possible privileges we've got. It is 41 * tightly coupled with the Privilege enum class, and when entries is 42 * added to the Privilege enum class the size of the mask needs to 43 * be updated. 44 */ 45using PrivilegeMask = std::bitset<size_t(Privilege::Impersonate) + 1>; 46 47/** 48 * The UserEntry object is in an in-memory representation of the per-user 49 * privileges. 50 */ 51class RBAC_PUBLIC_API UserEntry { 52public: 53 /** 54 * Create a new UserEntry from the provided JSON 55 * 56 * @param json A JSON representation of the user. 57 * @throws std::invalid_argument if the provided JSON isn't according 58 * to the specification. 59 * @throws std::bad_alloc if we run out of memory 60 */ 61 explicit UserEntry(const cJSON& json); 62 63 /** 64 * Get a map containing all of the buckets and the privileges in those 65 * buckets that the user have access to. 66 */ 67 const std::unordered_map<std::string, PrivilegeMask>& getBuckets() const { 68 return buckets; 69 } 70 71 /** 72 * Get all of the "global" (not related to a bucket) privileges the user 73 * have in its effective set. 74 */ 75 const PrivilegeMask& getPrivileges() const { 76 return privileges; 77 } 78 79 /** 80 * Get the domain where the user is defined. 81 */ 82 cb::sasl::Domain getDomain() const { 83 return domain; 84 } 85 86 /** 87 * Is this a system internal user or not? A system internal user is a 88 * user one of the system components use. 89 */ 90 bool isInternal() const { 91 return internal; 92 } 93 94protected: 95 /** 96 * Parse a JSON array containing a set of privileges. 97 * 98 * @param priv The JSON array to parse 99 * @param buckets Set to true if this is for the bucket list (which 100 * will mask out some of the privileges you can't 101 * specify for a bucket) 102 * @return A vector of all of the privileges found in the specified JSON 103 */ 104 PrivilegeMask parsePrivileges(const cJSON* priv, bool buckets); 105 106 std::unordered_map<std::string, PrivilegeMask> buckets; 107 PrivilegeMask privileges; 108 cb::sasl::Domain domain; 109 bool internal; 110}; 111 112/** 113 * The PrivilegeContext is the current context (selected bucket). 114 * The reason for this class is to provide a fast lookup for all 115 * of the privileges. It is used (possibly multiple times) for every 116 * command being executed. 117 */ 118class RBAC_PUBLIC_API PrivilegeContext { 119public: 120 /** 121 * Create a new (empty) instance of the privilege context. 122 * 123 * The generation is set to "max" which will cause the the access 124 * check to return stale if being used. This is the initial 125 * context being used. 126 */ 127 PrivilegeContext() 128 : generation(std::numeric_limits<uint32_t>::max()), mask() { 129 } 130 131 /** 132 * Create a new instance of the privilege context from the 133 * given generation and assign it the given mask. 134 * 135 * @param gen the generation of the privilege database 136 * @param m the mask to set it to. 137 */ 138 PrivilegeContext(uint32_t gen, const PrivilegeMask& m) 139 : generation(gen), mask(m) { 140 // empty 141 } 142 143 /** 144 * Check if the given privilege is part of the context 145 * 146 * @param privilege the privilege to check 147 * @return if access is granted or not. 148 */ 149 PrivilegeAccess check(Privilege privilege) const; 150 151 /** 152 * Get the generation of the Privilege Database this context maps 153 * to. If there is a mismatch with this number and the current number 154 * of the privilege database this context is no longer valid. 155 */ 156 uint32_t getGeneration() const { 157 return generation; 158 } 159 160 /** 161 * Get a textual representation of this object in the format: 162 * 163 * [privilege,privilege,privilege] 164 * 165 * An empty set is written as [none], and a full set is written 166 * as [all]. 167 */ 168 std::string to_string() const; 169 170 /** 171 * Clear all of the privileges in this context which contains 172 * bucket privileges. 173 */ 174 void clearBucketPrivileges(); 175 176 /** 177 * Set all of the privileges in this context which contains 178 * bucket privileges. 179 */ 180 void setBucketPrivileges(); 181 182 /** 183 * Drop the named privilege from the privilege mask 184 * 185 * @param privilege the privilege to drop 186 * @return true if the privilege was dropped 187 * false if the requested privilege wasn't set in the mask 188 */ 189 bool dropPrivilege(Privilege privilege); 190 191protected: 192 void setBucketPrivilegeBits(bool value); 193 194 uint32_t generation; 195 PrivilegeMask mask; 196}; 197 198/** 199 * Base class for exceptions thrown by the cb::rbac module in 200 * case you want to handle all of them with the same catch block. 201 */ 202class RBAC_PUBLIC_API Exception : public std::runtime_error { 203protected: 204 explicit Exception(const char* msg) : std::runtime_error(msg) { 205 } 206}; 207 208/** 209 * An exception class representing that the user doesn't exist in the 210 * PrivilegeDatabase. 211 */ 212class RBAC_PUBLIC_API NoSuchUserException : public Exception { 213public: 214 explicit NoSuchUserException(const char* msg) : Exception(msg) { 215 } 216}; 217 218/** 219 * An exception class representing that the bucket doesn't exist in the 220 * PrivilegeDatabase. 221 */ 222class RBAC_PUBLIC_API NoSuchBucketException : public Exception { 223public: 224 explicit NoSuchBucketException(const char* msg) : Exception(msg) { 225 } 226}; 227 228/** 229 * The PrivilegeDatabase is a container for all of the RBAC configuration 230 * of the system. 231 */ 232class RBAC_PUBLIC_API PrivilegeDatabase { 233public: 234 /** 235 * Create a new instance of the PrivilegeDatabase and initialize 236 * it to the provided JSON 237 * 238 * @param json A JSON representation of the privilege database as 239 * specified above (or null to create an empty database) 240 * @throws std::invalid_argument for invalid syntax 241 * @throws std::bad_alloc if we run out of memory 242 */ 243 explicit PrivilegeDatabase(const cJSON* json); 244 245 /** 246 * Try to look up a user in the privilege database 247 * 248 * @param user The name of the user to look up 249 * @param domain The domain where the user is defined (not used) 250 * @return The user entry for that user 251 * @throws cb::rbac::NoSuchUserException if the user doesn't exist 252 */ 253 const UserEntry& lookup(const std::string& user) const { 254 auto iter = userdb.find(user); 255 if (iter == userdb.cend()) { 256 throw NoSuchUserException(user.c_str()); 257 } 258 259 return iter->second; 260 } 261 262 /** 263 * Check if the provided context contains the requested privilege 264 * 265 * @param context The privilege context for the user 266 * @param privilege The privilege to check 267 * @return PrivilegeAccess::Stale If the context was created by a 268 * different generation of the database 269 * PrivilegeAccess::Ok If the context contains the privilege 270 * PrivilegeAccess::Fail If the context lacks the privilege 271 */ 272 PrivilegeAccess check(const PrivilegeContext& context, 273 Privilege privilege) { 274 if (context.getGeneration() != generation) { 275 return PrivilegeAccess::Stale; 276 } 277 278 return context.check(privilege); 279 } 280 281 /** 282 * Create a new PrivilegeContext for the specified user in the specified 283 * bucket. 284 * 285 * @param user The name of the user 286 * @param bucket The name of the bucket (may be "" if you're not 287 * connecting to a bucket (aka the no bucket)). 288 * @return The privilege context representing the user in that bucket 289 * @throws cb::rbac::NoSuchUserException if the user doesn't exist 290 * @throws cb::rbac::NoSuchBucketException if the user doesn't have access 291 * to that bucket. 292 */ 293 PrivilegeContext createContext(const std::string& user, 294 const std::string& bucket) const; 295 296 /** 297 * Create the initial context for a given user 298 * 299 * @param user The username to look up 300 * @param domain The domain where the user exists 301 * @return A pair with a privilege context as the first element, and 302 * a boolean indicating if this is a system user as the second 303 * element. 304 * @throws cb::rbac::NoSuchUserException if the user doesn't exist 305 */ 306 std::pair<PrivilegeContext, bool> createInitialContext( 307 const std::string& user, cb::sasl::Domain domain); 308 309 /** 310 * The generation for this PrivilegeDatabase (a privilege context must 311 * match this generation in order to be valid) 312 */ 313 const uint32_t generation; 314 315protected: 316 std::unordered_map<std::string, UserEntry> userdb; 317}; 318 319/** 320 * Create a new PrivilegeContext for the specified user in the specified 321 * bucket. 322 * 323 * @todo this might starve the writers? 324 * 325 * @param user The name of the user 326 * @param bucket The name of the bucket (may be "" if you're not 327 * connecting to a bucket (aka the no bucket)). 328 * @return The privilege context representing the user in that bucket 329 * @throws cb::rbac::NoSuchUserException if the user doesn't exist 330 * @throws cb::rbac::NoSuchBucketException if the user doesn't have access 331 * to that bucket. 332 */ 333RBAC_PUBLIC_API 334PrivilegeContext createContext(const std::string& user, 335 const std::string& bucket); 336 337/** 338 * Create the initial context for a given user 339 * 340 * @param user The username to look up 341 * @param domain The domain where the user exists 342 * @return A pair with a privilege context as the first element, and 343 * a boolean indicating if this is a system user as the second 344 * element. 345 * @throws cb::rbac::NoSuchUserException if the user doesn't exist 346 */ 347RBAC_PUBLIC_API 348std::pair<PrivilegeContext, bool> createInitialContext(const std::string& user, 349 cb::sasl::Domain domain); 350 351/** 352 * Load the named file and install it as the current privilege database 353 * 354 * @param filename the name of the new file 355 * @throws std::runtime_error 356 */ 357RBAC_PUBLIC_API 358void loadPrivilegeDatabase(const std::string& filename); 359 360/** 361 * Check if the specified user have access to the specified bucket 362 */ 363RBAC_PUBLIC_API 364bool mayAccessBucket(const std::string& user, const std::string& bucket); 365 366/** 367 * Initialize the RBAC module 368 */ 369RBAC_PUBLIC_API 370void initialize(); 371 372/** 373 * Destroy the RBAC module 374 */ 375RBAC_PUBLIC_API 376void destroy(); 377} 378} 379