diff --git a/configure.py b/configure.py index d795dbd47b..5744fd5ee1 100755 --- a/configure.py +++ b/configure.py @@ -403,6 +403,7 @@ scylla_core = (['database.cc', 'cql3/statements/revoke_statement.cc', 'cql3/statements/alter_type_statement.cc', 'cql3/statements/alter_keyspace_statement.cc', + 'cql3/statements/role-management-statements.cc', 'cql3/update_parameters.cc', 'cql3/ut_name.cc', 'cql3/user_options.cc', diff --git a/cql3/Cql.g b/cql3/Cql.g index ac8c2f51df..35fdada6d3 100644 --- a/cql3/Cql.g +++ b/cql3/Cql.g @@ -63,6 +63,7 @@ options { #include "cql3/statements/grant_statement.hh" #include "cql3/statements/revoke_statement.hh" #include "cql3/statements/list_permissions_statement.hh" +#include "cql3/statements/list_roles_statement.hh" #include "cql3/statements/index_target.hh" #include "cql3/statements/ks_prop_defs.hh" #include "cql3/selection/raw_selector.hh" @@ -346,6 +347,7 @@ cqlStatement returns [shared_ptr stmt] | st32=createViewStatement { $stmt = st32; } | st33=alterViewStatement { $stmt = st33; } | st34=dropViewStatement { $stmt = st34; } + | st35=listRolesStatement { $stmt = st35; } ; /* @@ -1078,6 +1080,20 @@ userOption[::shared_ptr opts] : k=K_PASSWORD v=STRING_LITERAL { opts->put($k.text, $v.text); } ; +/** + * LIST ROLES [OF ] [NORECURSIVE] + */ +listRolesStatement returns [::shared_ptr stmt] + @init { + bool recursive = true; + std::experimental::optional grantee; + } + : K_LIST K_ROLES + (K_OF g=userOrRoleName { grantee = std::move(g); })? + (K_NORECURSIVE { recursive = false; })? + { $stmt = ::make_shared(grantee, recursive); } + ; + /** DEFINITIONS **/ // Column Identifiers. These need to be treated differently from other @@ -1526,6 +1542,7 @@ basic_unreserved_keyword returns [sstring str] | K_ALL | K_USER | K_USERS + | K_ROLES | K_SUPERUSER | K_NOSUPERUSER | K_PASSWORD @@ -1623,6 +1640,7 @@ K_NORECURSIVE: N O R E C U R S I V E; K_USER: U S E R; K_USERS: U S E R S; +K_ROLES: R O L E S; K_SUPERUSER: S U P E R U S E R; K_NOSUPERUSER: N O S U P E R U S E R; K_PASSWORD: P A S S W O R D; diff --git a/cql3/statements/list_roles_statement.hh b/cql3/statements/list_roles_statement.hh new file mode 100644 index 0000000000..e9d528068a --- /dev/null +++ b/cql3/statements/list_roles_statement.hh @@ -0,0 +1,74 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Copyright 2017 ScyllaDB + * + * Modified by ScyllaDB + */ + +/* + * This file is part of Scylla. + * + * Scylla is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Scylla is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Scylla. If not, see . + */ + +#pragma once + +#include + +#include + +#include "cql3/statements/authorization_statement.hh" +#include "cql3/role_name.hh" +#include "stdx.hh" +#include "seastarx.hh" + +namespace cql3 { + +namespace statements { + +class list_roles_statement final : public authorization_statement { + stdx::optional _grantee; + + bool _recursive; + +public: + list_roles_statement(const stdx::optional& grantee, bool recursive) + : _grantee(grantee ? sstring(grantee->to_string()) : stdx::optional()), _recursive(recursive) {} + + virtual future<> check_access(const service::client_state&) override; + + virtual future<::shared_ptr> + execute(distributed&, service::query_state&, const query_options&) override; +}; + +} + +} diff --git a/cql3/statements/role-management-statements.cc b/cql3/statements/role-management-statements.cc new file mode 100644 index 0000000000..6cb65e93a1 --- /dev/null +++ b/cql3/statements/role-management-statements.cc @@ -0,0 +1,166 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Copyright 2017 ScyllaDB + * + * Modified by ScyllaDB + */ + +/* + * This file is part of Scylla. + * + * Scylla is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Scylla is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Scylla. If not, see . + */ + +#include + +#include "auth/common.hh" +#include "auth/role_manager.hh" +#include "cql3/column_specification.hh" +#include "cql3/query_processor.hh" +#include "cql3/statements/list_roles_statement.hh" +#include "cql3/statements/request_validations.hh" +#include "exceptions/exceptions.hh" +#include "transport/messages/result_message.hh" +#include "unimplemented.hh" + +namespace cql3 { + +namespace statements { + +static future<::shared_ptr> void_result_message() { + return make_ready_future<::shared_ptr>(nullptr); +} + +// +// `list_roles_statement` +// + +future<> list_roles_statement::check_access(const service::client_state& state) { + state.ensure_not_anonymous(); + + return async([this, &state] { + const auto user_has_grantee = [this, &state] { + auto& rm = state.get_auth_service()->underlying_role_manager(); + const auto roles = rm.query_granted(state.user()->name(), auth::recursive_role_query::yes).get0(); + return roles.count(*_grantee) != 0; + }; + + if (!auth::is_super_user(*state.get_auth_service(), *state.user()).get0() && _grantee && !user_has_grantee()) { + throw exceptions::unauthorized_exception( + sprint("You are not authorized to view the roles granted to role '%s'.", *_grantee)); + } + }).handle_exception_type([](const auth::roles_argument_exception& e) { + throw exceptions::invalid_request_exception(e.what()); + }); +} + +future<::shared_ptr> +list_roles_statement::execute(distributed&, service::query_state& state, const query_options&) { + unimplemented::warn(unimplemented::cause::ROLES); + + static const auth::data_resource virtual_table(auth::meta::AUTH_KS, "role"); + + static const auto make_column_spec = [](const sstring& name, const ::shared_ptr& ty) { + return ::make_shared( + virtual_table.keyspace(), + virtual_table.column_family(), + ::make_shared(name, true), + ty); + }; + + static const auto metadata = ::make_shared( + std::vector<::shared_ptr>{ + make_column_spec("role", utf8_type), + make_column_spec("super", boolean_type), + make_column_spec("login", boolean_type)}); + + static const auto make_results = [](auth::role_manager& rm, std::unordered_set&& roles) + -> future<::shared_ptr> { + using cql_transport::messages::result_message; + + auto results = std::make_unique(metadata); + + if (roles.empty()) { + return make_ready_future<::shared_ptr>( + ::make_shared(std::move(results))); + } + + std::vector sorted_roles(roles.cbegin(), roles.cend()); + std::sort(sorted_roles.begin(), sorted_roles.end()); + + return do_with( + std::move(sorted_roles), + std::move(results), + [&rm](const std::vector& sorted_roles, std::unique_ptr& results) { + return do_for_each(sorted_roles, [&results, &rm](const sstring& role) { + return when_all_succeed( + rm.can_login(role), + rm.is_superuser(role)).then([&results, &role](bool login, bool super) { + results->add_column_value(utf8_type->decompose(role)); + results->add_column_value(boolean_type->decompose(super)); + results->add_column_value(boolean_type->decompose(login)); + }); + }).then([&results] { + return make_ready_future<::shared_ptr>( + ::make_shared(std::move(results))); + }); + }); + }; + + auto& cs = state.get_client_state(); + auto& as = *cs.get_auth_service(); + + return auth::is_super_user(as, *cs.user()).then([this, &state, &cs, &as](bool super) { + auto& rm = as.underlying_role_manager(); + const auto query_mode = _recursive ? auth::recursive_role_query::yes : auth::recursive_role_query::no; + + if (!_grantee) { + if (super) { + return rm.query_all().then([&rm](auto&& roles) { return make_results(rm, std::move(roles)); }); + } + + return rm.query_granted(cs.user()->name(), query_mode).then([&rm](std::unordered_set roles) { + return make_results(rm, std::move(roles)); + }); + } + + return rm.query_granted(*_grantee, query_mode).then([&rm](std::unordered_set roles) { + return make_results(rm, std::move(roles)); + }); + }).handle_exception_type([](const auth::roles_argument_exception& e) { + throw exceptions::invalid_request_exception(e.what()); + return void_result_message(); + }); +} + +} + +}