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