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 
36 namespace cb {
37 namespace 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  */
45 using 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  */
51 class RBAC_PUBLIC_API UserEntry {
52 public:
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      */
getBuckets()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      */
getPrivileges()75     const PrivilegeMask& getPrivileges() const {
76         return privileges;
77     }
78 
79     /**
80      * Get the domain where the user is defined.
81      */
getDomain()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      */
isInternal()90     bool isInternal() const {
91         return internal;
92     }
93 
94 protected:
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  */
118 class RBAC_PUBLIC_API PrivilegeContext {
119 public:
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      */
PrivilegeContext()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      */
PrivilegeContext(uint32_t gen,const PrivilegeMask & m)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      */
getGeneration()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 
191 protected:
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  */
202 class RBAC_PUBLIC_API Exception : public std::runtime_error {
203 protected:
Exception(const char * msg)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  */
212 class RBAC_PUBLIC_API NoSuchUserException : public Exception {
213 public:
NoSuchUserException(const char * msg)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  */
222 class RBAC_PUBLIC_API NoSuchBucketException : public Exception {
223 public:
NoSuchBucketException(const char * msg)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  */
232 class RBAC_PUBLIC_API PrivilegeDatabase {
233 public:
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      */
lookup(const std::string & user)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      */
check(const PrivilegeContext & context,Privilege privilege)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 
315 protected:
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  */
333 RBAC_PUBLIC_API
334 PrivilegeContext 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  */
347 RBAC_PUBLIC_API
348 std::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  */
357 RBAC_PUBLIC_API
358 void loadPrivilegeDatabase(const std::string& filename);
359 
360 /**
361  * Check if the specified user have access to the specified bucket
362  */
363 RBAC_PUBLIC_API
364 bool mayAccessBucket(const std::string& user, const std::string& bucket);
365 
366 /**
367  * Initialize the RBAC module
368  */
369 RBAC_PUBLIC_API
370 void initialize();
371 
372 /**
373  * Destroy the RBAC module
374  */
375 RBAC_PUBLIC_API
376 void destroy();
377 }
378 }
379