aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/clickhouse/src/Interpreters/Access/InterpreterGrantQuery.cpp
blob: 1a8268b9b1b08dcd5839e7df7a3c72b04c03d576 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
#include <Interpreters/Access/InterpreterGrantQuery.h>
#include <Parsers/Access/ASTGrantQuery.h>
#include <Parsers/Access/ASTRolesOrUsersSet.h>
#include <Access/AccessControl.h>
#include <Access/ContextAccess.h>
#include <Access/Role.h>
#include <Access/RolesOrUsersSet.h>
#include <Access/User.h>
#include <Interpreters/Context.h>
#include <Interpreters/QueryLog.h>
#include <Interpreters/executeDDLQueryOnCluster.h>
#include <boost/range/algorithm/copy.hpp>
#include <boost/range/algorithm/set_algorithm.hpp>
#include <boost/range/algorithm_ext/erase.hpp>

namespace DB
{
namespace ErrorCodes
{
    extern const int BAD_ARGUMENTS;
    extern const int LOGICAL_ERROR;
}

namespace
{
    /// Extracts access rights elements which are going to be granted or revoked from a query.
    void collectAccessRightsElementsToGrantOrRevoke(
        const ASTGrantQuery & query,
        AccessRightsElements & elements_to_grant,
        AccessRightsElements & elements_to_revoke)
    {
        elements_to_grant.clear();
        elements_to_revoke.clear();

        if (query.is_revoke)
        {
            /// REVOKE
            elements_to_revoke = query.access_rights_elements;
        }
        else if (query.replace_access)
        {
            /// GRANT WITH REPLACE OPTION
            elements_to_grant = query.access_rights_elements;
            elements_to_revoke.emplace_back(AccessType::ALL);
        }
        else
        {
            /// GRANT
            elements_to_grant = query.access_rights_elements;
        }
    }

    /// Extracts roles which are going to be granted or revoked from a query.
    void collectRolesToGrantOrRevoke(
        const AccessControl & access_control,
        const ASTGrantQuery & query,
        std::vector<UUID> & roles_to_grant,
        RolesOrUsersSet & roles_to_revoke)
    {
        roles_to_grant.clear();
        roles_to_revoke.clear();

        RolesOrUsersSet roles_to_grant_or_revoke;
        if (query.roles)
            roles_to_grant_or_revoke = RolesOrUsersSet{*query.roles, access_control};

        if (query.is_revoke)
        {
            /// REVOKE
            roles_to_revoke = std::move(roles_to_grant_or_revoke);
        }
        else if (query.replace_granted_roles)
        {
            /// GRANT WITH REPLACE OPTION
            roles_to_grant = roles_to_grant_or_revoke.getMatchingIDs(access_control);
            roles_to_revoke = RolesOrUsersSet::AllTag{};
        }
        else
        {
            /// GRANT
            roles_to_grant = roles_to_grant_or_revoke.getMatchingIDs(access_control);
        }
    }

    /// Extracts roles which are going to be granted or revoked from a query.
    void collectRolesToGrantOrRevoke(
        const ASTGrantQuery & query,
        std::vector<UUID> & roles_to_grant,
        RolesOrUsersSet & roles_to_revoke)
    {
        roles_to_grant.clear();
        roles_to_revoke.clear();

        RolesOrUsersSet roles_to_grant_or_revoke;
        if (query.roles)
            roles_to_grant_or_revoke = RolesOrUsersSet{*query.roles};

        if (query.is_revoke)
        {
            /// REVOKE
            roles_to_revoke = std::move(roles_to_grant_or_revoke);
        }
        else if (query.replace_granted_roles)
        {
            /// GRANT WITH REPLACE OPTION
            roles_to_grant = roles_to_grant_or_revoke.getMatchingIDs();
            roles_to_revoke = RolesOrUsersSet::AllTag{};
        }
        else
        {
            /// GRANT
            roles_to_grant = roles_to_grant_or_revoke.getMatchingIDs();
        }
    }

    /// Checks if the current user has enough access rights granted with grant option to grant or revoke specified access rights.
    void checkGrantOption(
        const AccessControl & access_control,
        const ContextAccess & current_user_access,
        const std::vector<UUID> & grantees_from_query,
        bool & need_check_grantees_are_allowed,
        const AccessRightsElements & elements_to_grant,
        AccessRightsElements & elements_to_revoke)
    {
        /// Check access rights which are going to be granted.
        /// To execute the command GRANT the current user needs to have the access granted with GRANT OPTION.
        current_user_access.checkGrantOption(elements_to_grant);

        if (current_user_access.hasGrantOption(elements_to_revoke))
        {
            /// Simple case: the current user has the grant option for all the access rights specified for REVOKE.
            return;
        }

        /// Special case for the command REVOKE: it's possible that the current user doesn't have
        /// the access granted with GRANT OPTION but it's still ok because the roles or users
        /// from whom the access rights will be revoked don't have the specified access granted either.
        ///
        /// For example, to execute
        /// GRANT ALL ON mydb.* TO role1
        /// REVOKE ALL ON *.* FROM role1
        /// the current user needs to have the grants only on the 'mydb' database.
        AccessRights all_granted_access;
        for (const auto & id : grantees_from_query)
        {
            auto entity = access_control.tryRead(id);
            if (auto role = typeid_cast<RolePtr>(entity))
            {
                if (need_check_grantees_are_allowed)
                    current_user_access.checkGranteeIsAllowed(id, *role);
                all_granted_access.makeUnion(role->access);
            }
            else if (auto user = typeid_cast<UserPtr>(entity))
            {
                if (need_check_grantees_are_allowed)
                    current_user_access.checkGranteeIsAllowed(id, *user);
                all_granted_access.makeUnion(user->access);
            }
        }

        need_check_grantees_are_allowed = false; /// already checked

        if (!elements_to_revoke.empty() && elements_to_revoke[0].is_partial_revoke)
            std::for_each(elements_to_revoke.begin(), elements_to_revoke.end(), [&](AccessRightsElement & element) { element.is_partial_revoke = false; });
        AccessRights access_to_revoke;
        access_to_revoke.grant(elements_to_revoke);
        access_to_revoke.makeIntersection(all_granted_access);

        /// Build more accurate list of elements to revoke, now we use an intesection of the initial list of elements to revoke
        /// and all the granted access rights to these grantees.
        bool grant_option = !elements_to_revoke.empty() && elements_to_revoke[0].grant_option;
        elements_to_revoke.clear();
        for (auto & element_to_revoke : access_to_revoke.getElements())
        {
            if (!element_to_revoke.is_partial_revoke && (element_to_revoke.grant_option || !grant_option))
                elements_to_revoke.emplace_back(std::move(element_to_revoke));
        }

        current_user_access.checkGrantOption(elements_to_revoke);
    }

    /// Checks if the current user has enough roles granted with admin option to grant or revoke specified roles.
    void checkAdminOption(
        const AccessControl & access_control,
        const ContextAccess & current_user_access,
        const std::vector<UUID> & grantees_from_query,
        bool & need_check_grantees_are_allowed,
        const std::vector<UUID> & roles_to_grant,
        RolesOrUsersSet & roles_to_revoke,
        bool admin_option)
    {
        /// Check roles which are going to be granted.
        /// To execute the command GRANT the current user needs to have the roles granted with ADMIN OPTION.
        current_user_access.checkAdminOption(roles_to_grant);

        /// Check roles which are going to be revoked.
        std::vector<UUID> roles_to_revoke_ids;
        if (!roles_to_revoke.all)
        {
            roles_to_revoke_ids = roles_to_revoke.getMatchingIDs();
            if (current_user_access.hasAdminOption(roles_to_revoke_ids))
            {
                /// Simple case: the current user has the admin option for all the roles specified for REVOKE.
                return;
            }
        }

        /// Special case for the command REVOKE: it's possible that the current user doesn't have the admin option
        /// for some of the specified roles but it's still ok because the roles or users from whom the roles will be
        /// revoked from don't have the specified roles granted either.
        ///
        /// For example, to execute
        /// GRANT role2 TO role1
        /// REVOKE ALL FROM role1
        /// the current user needs to have only 'role2' to be granted with admin option (not all the roles).
        GrantedRoles all_granted_roles;
        for (const auto & id : grantees_from_query)
        {
            auto entity = access_control.tryRead(id);
            if (auto role = typeid_cast<RolePtr>(entity))
            {
                if (need_check_grantees_are_allowed)
                    current_user_access.checkGranteeIsAllowed(id, *role);
                all_granted_roles.makeUnion(role->granted_roles);
            }
            else if (auto user = typeid_cast<UserPtr>(entity))
            {
                if (need_check_grantees_are_allowed)
                    current_user_access.checkGranteeIsAllowed(id, *user);
                all_granted_roles.makeUnion(user->granted_roles);
            }
        }

        need_check_grantees_are_allowed = false; /// already checked

        const auto & all_granted_roles_set = admin_option ? all_granted_roles.getGrantedWithAdminOption() : all_granted_roles.getGranted();
        if (roles_to_revoke.all)
            boost::range::set_difference(all_granted_roles_set, roles_to_revoke.except_ids, std::back_inserter(roles_to_revoke_ids));
        else
            boost::range::remove_erase_if(roles_to_revoke_ids, [&](const UUID & id) { return !all_granted_roles_set.count(id); });

        roles_to_revoke = roles_to_revoke_ids;
        current_user_access.checkAdminOption(roles_to_revoke_ids);
    }

    /// Returns access rights which should be checked for executing GRANT/REVOKE on cluster.
    /// This function is less accurate than checkGrantOption() because it cannot use any information about
    /// access rights the grantees currently have (due to those grantees are located on multiple nodes,
    /// we just don't have the full information about them).
    AccessRightsElements getRequiredAccessForExecutingOnCluster(const AccessRightsElements & elements_to_grant, const AccessRightsElements & elements_to_revoke)
    {
        auto required_access = elements_to_grant;
        required_access.insert(required_access.end(), elements_to_revoke.begin(), elements_to_revoke.end());
        std::for_each(required_access.begin(), required_access.end(), [&](AccessRightsElement & element) { element.grant_option = true; });
        return required_access;
    }

    /// Checks if the current user has enough roles granted with admin option to grant or revoke specified roles on cluster.
    /// This function is less accurate than checkAdminOption() because it cannot use any information about
    /// granted roles the grantees currently have (due to those grantees are located on multiple nodes,
    /// we just don't have the full information about them).
    void checkAdminOptionForExecutingOnCluster(const ContextAccess & current_user_access,
                                               const std::vector<UUID> roles_to_grant,
                                               const RolesOrUsersSet & roles_to_revoke)
    {
        if (roles_to_revoke.all)
        {
            /// Revoking all the roles on cluster always requires ROLE_ADMIN privilege
            /// because when we send the query REVOKE ALL to each shard we don't know at this point
            /// which roles exactly this is going to revoke on each shard.
            /// However ROLE_ADMIN just allows to revoke every role, that's why we check it here.
            current_user_access.checkAccess(AccessType::ROLE_ADMIN);
            return;
        }

        if (current_user_access.isGranted(AccessType::ROLE_ADMIN))
            return;

        for (const auto & role_id : roles_to_grant)
            current_user_access.checkAdminOption(role_id);


        for (const auto & role_id : roles_to_revoke.getMatchingIDs())
            current_user_access.checkAdminOption(role_id);
    }

    template <typename T>
    void updateGrantedAccessRightsAndRolesTemplate(
        T & grantee,
        const AccessRightsElements & elements_to_grant,
        const AccessRightsElements & elements_to_revoke,
        const std::vector<UUID> & roles_to_grant,
        const RolesOrUsersSet & roles_to_revoke,
        bool admin_option)
    {
        if (!elements_to_revoke.empty())
            grantee.access.revoke(elements_to_revoke);

        if (!elements_to_grant.empty())
            grantee.access.grant(elements_to_grant);

        if (!roles_to_revoke.empty())
        {
            if (admin_option)
                grantee.granted_roles.revokeAdminOption(grantee.granted_roles.findGrantedWithAdminOption(roles_to_revoke));
            else
                grantee.granted_roles.revoke(grantee.granted_roles.findGranted(roles_to_revoke));
        }

        if (!roles_to_grant.empty())
        {
            if (admin_option)
                grantee.granted_roles.grantWithAdminOption(roles_to_grant);
            else
                grantee.granted_roles.grant(roles_to_grant);
        }
    }

    /// Updates grants of a specified user or role.
    void updateGrantedAccessRightsAndRoles(
        IAccessEntity & grantee,
        const AccessRightsElements & elements_to_grant,
        const AccessRightsElements & elements_to_revoke,
        const std::vector<UUID> & roles_to_grant,
        const RolesOrUsersSet & roles_to_revoke,
        bool admin_option)
    {
        if (auto * user = typeid_cast<User *>(&grantee))
            updateGrantedAccessRightsAndRolesTemplate(*user, elements_to_grant, elements_to_revoke, roles_to_grant, roles_to_revoke, admin_option);
        else if (auto * role = typeid_cast<Role *>(&grantee))
            updateGrantedAccessRightsAndRolesTemplate(*role, elements_to_grant, elements_to_revoke, roles_to_grant, roles_to_revoke, admin_option);
    }

    template <typename T>
    void grantCurrentGrantsTemplate(
        T & grantee,
        const AccessRights & rights_to_grant,
        const AccessRightsElements & elements_to_revoke)
    {
        if (!elements_to_revoke.empty())
            grantee.access.revoke(elements_to_revoke);

        grantee.access.makeUnion(rights_to_grant);
    }

    /// Grants current user's grants with grant options to specified user.
    void grantCurrentGrants(
        IAccessEntity & grantee,
        const AccessRights & new_rights,
        const AccessRightsElements & elements_to_revoke)
    {
        if (auto * user = typeid_cast<User *>(&grantee))
            grantCurrentGrantsTemplate(*user, new_rights, elements_to_revoke);
        else if (auto * role = typeid_cast<Role *>(&grantee))
            grantCurrentGrantsTemplate(*role, new_rights, elements_to_revoke);
    }

    /// Calculates all available rights to grant with current user intersection.
    void calculateCurrentGrantRightsWithIntersection(
        AccessRights & rights,
        std::shared_ptr<const ContextAccess> current_user_access,
        const AccessRightsElements & elements_to_grant)
    {
        AccessRightsElements current_user_grantable_elements;
        auto available_grant_elements = current_user_access->getAccessRights()->getElements();
        AccessRights current_user_rights;
        for (auto & element : available_grant_elements)
        {
            if (!element.grant_option && !element.is_partial_revoke)
                continue;

            if (element.is_partial_revoke)
                current_user_rights.revoke(element);
            else
                current_user_rights.grant(element);
        }

        rights.grant(elements_to_grant);
        rights.makeIntersection(current_user_rights);
    }

    /// Updates grants of a specified user or role.
    void updateFromQuery(IAccessEntity & grantee, const ASTGrantQuery & query)
    {
        AccessRightsElements elements_to_grant, elements_to_revoke;
        collectAccessRightsElementsToGrantOrRevoke(query, elements_to_grant, elements_to_revoke);

        std::vector<UUID> roles_to_grant;
        RolesOrUsersSet roles_to_revoke;
        collectRolesToGrantOrRevoke(query, roles_to_grant, roles_to_revoke);

        updateGrantedAccessRightsAndRoles(grantee, elements_to_grant, elements_to_revoke, roles_to_grant, roles_to_revoke, query.admin_option);
    }
}


BlockIO InterpreterGrantQuery::execute()
{
    auto & query = query_ptr->as<ASTGrantQuery &>();

    query.replaceCurrentUserTag(getContext()->getUserName());
    query.access_rights_elements.eraseNonGrantable();

    if (!query.access_rights_elements.sameOptions())
        throw Exception(ErrorCodes::LOGICAL_ERROR, "Elements of an ASTGrantQuery are expected to have the same options");
    if (!query.access_rights_elements.empty() && query.access_rights_elements[0].is_partial_revoke && !query.is_revoke)
        throw Exception(ErrorCodes::LOGICAL_ERROR, "A partial revoke should be revoked, not granted");

    auto & access_control = getContext()->getAccessControl();
    auto current_user_access = getContext()->getAccess();

    std::vector<UUID> grantees = RolesOrUsersSet{*query.grantees, access_control, getContext()->getUserID()}.getMatchingIDs(access_control);

    /// Collect access rights and roles we're going to grant or revoke.
    AccessRightsElements elements_to_grant, elements_to_revoke;
    collectAccessRightsElementsToGrantOrRevoke(query, elements_to_grant, elements_to_revoke);

    std::vector<UUID> roles_to_grant;
    RolesOrUsersSet roles_to_revoke;
    collectRolesToGrantOrRevoke(access_control, query, roles_to_grant, roles_to_revoke);

    /// Executing on cluster.
    if (!query.cluster.empty())
    {
        if (query.current_grants)
            throw Exception(ErrorCodes::BAD_ARGUMENTS, "GRANT CURRENT GRANTS can't be executed on cluster.");

        auto required_access = getRequiredAccessForExecutingOnCluster(elements_to_grant, elements_to_revoke);
        checkAdminOptionForExecutingOnCluster(*current_user_access, roles_to_grant, roles_to_revoke);
        current_user_access->checkGranteesAreAllowed(grantees);
        DDLQueryOnClusterParams params;
        params.access_to_check = std::move(required_access);
        return executeDDLQueryOnCluster(query_ptr, getContext(), params);
    }

    /// Check if the current user has corresponding access rights granted with grant option.
    String current_database = getContext()->getCurrentDatabase();
    elements_to_grant.replaceEmptyDatabase(current_database);
    elements_to_revoke.replaceEmptyDatabase(current_database);
    bool need_check_grantees_are_allowed = true;
    if (!query.current_grants)
        checkGrantOption(access_control, *current_user_access, grantees, need_check_grantees_are_allowed, elements_to_grant, elements_to_revoke);

    /// Check if the current user has corresponding roles granted with admin option.
    checkAdminOption(access_control, *current_user_access, grantees, need_check_grantees_are_allowed, roles_to_grant, roles_to_revoke, query.admin_option);

    if (need_check_grantees_are_allowed)
        current_user_access->checkGranteesAreAllowed(grantees);

    AccessRights new_rights;
    if (query.current_grants)
        calculateCurrentGrantRightsWithIntersection(new_rights, current_user_access, elements_to_grant);

    /// Update roles and users listed in `grantees`.
    auto update_func = [&](const AccessEntityPtr & entity) -> AccessEntityPtr
    {
        auto clone = entity->clone();
        if (query.current_grants)
            grantCurrentGrants(*clone, new_rights, elements_to_revoke);
        else
            updateGrantedAccessRightsAndRoles(*clone, elements_to_grant, elements_to_revoke, roles_to_grant, roles_to_revoke, query.admin_option);
        return clone;
    };

    access_control.update(grantees, update_func);

    return {};
}


void InterpreterGrantQuery::updateUserFromQuery(User & user, const ASTGrantQuery & query)
{
    updateFromQuery(user, query);
}

void InterpreterGrantQuery::updateRoleFromQuery(Role & role, const ASTGrantQuery & query)
{
    updateFromQuery(role, query);
}

}