diff --git a/auth/allow_all_authenticator.hh b/auth/allow_all_authenticator.hh index e5da1953a0..e22cbf2bce 100644 --- a/auth/allow_all_authenticator.hh +++ b/auth/allow_all_authenticator.hh @@ -84,9 +84,9 @@ public: return make_ready_future(); } - const resource_ids& protected_resources() const override { - static const resource_ids ids; - return ids; + const resource_set& protected_resources() const override { + static const resource_set resources; + return resources; } ::shared_ptr new_sasl_challenge() const override { diff --git a/auth/allow_all_authorizer.hh b/auth/allow_all_authorizer.hh index a03535ec43..eb2a19b70e 100644 --- a/auth/allow_all_authorizer.hh +++ b/auth/allow_all_authorizer.hh @@ -56,15 +56,15 @@ public: return allow_all_authorizer_name(); } - future authorize(service&, ::shared_ptr, data_resource) const override { + future authorize(service&, ::shared_ptr, resource) const override { return make_ready_future(permissions::ALL); } - future<> grant(::shared_ptr, permission_set, data_resource, sstring) override { + future<> grant(::shared_ptr, permission_set, resource, sstring) override { throw exceptions::invalid_request_exception("GRANT operation is not supported by AllowAllAuthorizer"); } - future<> revoke(::shared_ptr, permission_set, data_resource, sstring) override { + future<> revoke(::shared_ptr, permission_set, resource, sstring) override { throw exceptions::invalid_request_exception("REVOKE operation is not supported by AllowAllAuthorizer"); } @@ -72,7 +72,7 @@ public: service&, ::shared_ptr performer, permission_set, - stdx::optional, + stdx::optional, stdx::optional) const override { throw exceptions::invalid_request_exception("LIST PERMISSIONS operation is not supported by AllowAllAuthorizer"); } @@ -81,13 +81,13 @@ public: return make_ready_future(); } - future<> revoke_all(data_resource) override { + future<> revoke_all(resource) override { return make_ready_future(); } - const resource_ids& protected_resources() override { - static const resource_ids ids; - return ids; + const resource_set& protected_resources() override { + static const resource_set resources; + return resources; } future<> validate_configuration() const override { diff --git a/auth/authenticator.hh b/auth/authenticator.hh index 55c2c42737..78217bbd0e 100644 --- a/auth/authenticator.hh +++ b/auth/authenticator.hh @@ -53,9 +53,9 @@ #include #include "bytes.hh" -#include "data_resource.hh" #include "enum_set.hh" #include "exceptions/exceptions.hh" +#include "resource.hh" namespace db { class config; @@ -160,9 +160,9 @@ public: * Set of resources that should be made inaccessible to users and only accessible internally. * * @return Keyspaces, column families that will be unmodifiable by users; other resources. - * @see resource_ids + * @see resource_set */ - virtual const resource_ids& protected_resources() const = 0; + virtual const resource_set& protected_resources() const = 0; class sasl_challenge { public: diff --git a/auth/authorizer.cc b/auth/authorizer.cc index 77ae3b8741..3814c5f888 100644 --- a/auth/authorizer.cc +++ b/auth/authorizer.cc @@ -74,27 +74,27 @@ auth::authorizer::setup(const sstring& type) { const sstring& qualified_java_name() const override { return allow_all_authorizer_name(); } - future authorize(::shared_ptr, data_resource) const override { + future authorize(::shared_ptr, resource) const override { return make_ready_future(permissions::ALL); } - future<> grant(::shared_ptr, permission_set, data_resource, sstring) override { + future<> grant(::shared_ptr, permission_set, resource, sstring) override { throw exceptions::invalid_request_exception("GRANT operation is not supported by AllowAllAuthorizer"); } - future<> revoke(::shared_ptr, permission_set, data_resource, sstring) override { + future<> revoke(::shared_ptr, permission_set, resource, sstring) override { throw exceptions::invalid_request_exception("REVOKE operation is not supported by AllowAllAuthorizer"); } - future> list(::shared_ptr performer, permission_set, optional, optional) const override { + future> list(::shared_ptr performer, permission_set, optional, optional) const override { throw exceptions::invalid_request_exception("LIST PERMISSIONS operation is not supported by AllowAllAuthorizer"); } future<> revoke_all(sstring dropped_user) override { return make_ready_future(); } - future<> revoke_all(data_resource) override { + future<> revoke_all(resource) override { return make_ready_future(); } - const resource_ids& protected_resources() override { - static const resource_ids ids; - return ids; + const resource_set& protected_resources() override { + static const resource_set resources; + return resources; } future<> validate_configuration() const override { return make_ready_future(); diff --git a/auth/authorizer.hh b/auth/authorizer.hh index e3ace8fb0d..a8e3ecbe95 100644 --- a/auth/authorizer.hh +++ b/auth/authorizer.hh @@ -49,7 +49,7 @@ #include #include "permission.hh" -#include "data_resource.hh" +#include "resource.hh" #include "seastarx.hh" @@ -61,7 +61,7 @@ class authenticated_user; struct permission_details { sstring user; - data_resource resource; + ::auth::resource resource; permission_set permissions; bool operator<(const permission_details& v) const { @@ -88,7 +88,7 @@ public: * @param resource Resource for which the authorization is being requested. @see DataResource. * @return Set of permissions of the user on the resource. Should never return empty. Use permission.NONE instead. */ - virtual future authorize(service&, ::shared_ptr, data_resource) const = 0; + virtual future authorize(service&, ::shared_ptr, resource) const = 0; /** * Grants a set of permissions on a resource to a user. @@ -102,7 +102,7 @@ public: * @throws RequestValidationException * @throws RequestExecutionException */ - virtual future<> grant(::shared_ptr performer, permission_set, data_resource, sstring to) = 0; + virtual future<> grant(::shared_ptr performer, permission_set, resource, sstring to) = 0; /** * Revokes a set of permissions on a resource from a user. @@ -116,7 +116,7 @@ public: * @throws RequestValidationException * @throws RequestExecutionException */ - virtual future<> revoke(::shared_ptr performer, permission_set, data_resource, sstring from) = 0; + virtual future<> revoke(::shared_ptr performer, permission_set, resource, sstring from) = 0; /** * Returns a list of permissions on a resource of a user. @@ -132,7 +132,7 @@ public: * @throws RequestValidationException * @throws RequestExecutionException */ - virtual future> list(service&, ::shared_ptr performer, permission_set, optional, optional) const = 0; + virtual future> list(service&, ::shared_ptr performer, permission_set, optional, optional) const = 0; /** * This method is called before deleting a user with DROP USER query so that a new user with the same @@ -147,14 +147,14 @@ public: * * @param droppedResource The resource to revoke all permissions on. */ - virtual future<> revoke_all(data_resource) = 0; + virtual future<> revoke_all(resource) = 0; /** * Set of resources that should be made inaccessible to users and only accessible internally. * * @return Keyspaces, column families that will be unmodifiable by users; other resources. */ - virtual const resource_ids& protected_resources() = 0; + virtual const resource_set& protected_resources() = 0; /** * Validates configuration of IAuthorizer implementation (if configurable). diff --git a/auth/data_resource.cc b/auth/data_resource.cc deleted file mode 100644 index 7af829d7c9..0000000000 --- a/auth/data_resource.cc +++ /dev/null @@ -1,171 +0,0 @@ -/* - * 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 (C) 2016 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 "data_resource.hh" - -#include -#include "service/storage_proxy.hh" - -const sstring auth::data_resource::ROOT_NAME("data"); - -auth::data_resource::data_resource(level l, const sstring& ks, const sstring& cf) - : _level(l), _ks(ks), _cf(cf) -{ -} - -auth::data_resource::data_resource() - : data_resource(level::ROOT) -{} - -auth::data_resource::data_resource(const sstring& ks) - : data_resource(level::KEYSPACE, ks) -{} - -auth::data_resource::data_resource(const sstring& ks, const sstring& cf) - : data_resource(level::COLUMN_FAMILY, ks, cf) -{} - -auth::data_resource::level auth::data_resource::get_level() const { - return _level; -} - -auth::data_resource auth::data_resource::from_name( - const sstring& s) { - - static std::regex slash_regex("/"); - - auto i = std::regex_token_iterator(s.begin(), - s.end(), slash_regex, -1); - auto e = std::regex_token_iterator(); - auto n = std::distance(i, e); - - if (n > 3 || ROOT_NAME != sstring(*i++)) { - throw std::invalid_argument(sprint("%s is not a valid data resource name", s)); - } - - if (n == 1) { - return data_resource(); - } - auto ks = *i++; - if (n == 2) { - return data_resource(ks.str()); - } - auto cf = *i++; - return data_resource(ks.str(), cf.str()); -} - -sstring auth::data_resource::name() const { - switch (get_level()) { - case level::ROOT: - return ROOT_NAME; - case level::KEYSPACE: - return sprint("%s/%s", ROOT_NAME, _ks); - case level::COLUMN_FAMILY: - default: - return sprint("%s/%s/%s", ROOT_NAME, _ks, _cf); - } -} - -auth::data_resource auth::data_resource::get_parent() const { - switch (get_level()) { - case level::KEYSPACE: - return data_resource(); - case level::COLUMN_FAMILY: - return data_resource(_ks); - default: - throw std::invalid_argument("Root-level resource can't have a parent"); - } -} - -const sstring& auth::data_resource::keyspace() const { - if (is_root_level()) { - throw std::invalid_argument("ROOT data resource has no keyspace"); - } - return _ks; -} - -const sstring& auth::data_resource::column_family() const { - if (!is_column_family_level()) { - throw std::invalid_argument(sprint("%s data resource has no column family", name())); - } - return _cf; -} - -bool auth::data_resource::has_parent() const { - return !is_root_level(); -} - -bool auth::data_resource::exists() const { - switch (get_level()) { - case level::ROOT: - return true; - case level::KEYSPACE: - return service::get_local_storage_proxy().get_db().local().has_keyspace(_ks); - case level::COLUMN_FAMILY: - default: - return service::get_local_storage_proxy().get_db().local().has_schema(_ks, _cf); - } -} - -sstring auth::data_resource::to_string() const { - switch (get_level()) { - case level::ROOT: - return ""; - case level::KEYSPACE: - return sprint("", _ks); - case level::COLUMN_FAMILY: - default: - return sprint("", _ks, _cf); - } -} - -bool auth::data_resource::operator==(const data_resource& v) const { - return _ks == v._ks && _cf == v._cf; -} - -bool auth::data_resource::operator<(const data_resource& v) const { - return _ks < v._ks ? true : (v._ks < _ks ? false : _cf < v._cf); -} - -std::ostream& auth::operator<<(std::ostream& os, const data_resource& r) { - return os << r.to_string(); -} - diff --git a/auth/data_resource.hh b/auth/data_resource.hh deleted file mode 100644 index 3b365fbda3..0000000000 --- a/auth/data_resource.hh +++ /dev/null @@ -1,159 +0,0 @@ -/* - * 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 (C) 2016 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 "utils/hash.hh" -#include -#include -#include -#include "seastarx.hh" - -namespace auth { - -class data_resource { -private: - enum class level { - ROOT, KEYSPACE, COLUMN_FAMILY - }; - - static const sstring ROOT_NAME; - - level _level; - sstring _ks; - sstring _cf; - - data_resource(level, const sstring& ks = {}, const sstring& cf = {}); - - level get_level() const; -public: - /** - * Creates a DataResource representing the root-level resource. - * @return the root-level resource. - */ - data_resource(); - /** - * Creates a DataResource representing a keyspace. - * - * @param keyspace Name of the keyspace. - */ - data_resource(const sstring& ks); - /** - * Creates a DataResource instance representing a column family. - * - * @param keyspace Name of the keyspace. - * @param columnFamily Name of the column family. - */ - data_resource(const sstring& ks, const sstring& cf); - - /** - * Parses a data resource name into a DataResource instance. - * - * @param name Name of the data resource. - * @return DataResource instance matching the name. - */ - static data_resource from_name(const sstring&); - - /** - * @return Printable name of the resource. - */ - sstring name() const; - - /** - * @return Parent of the resource, if any. Throws IllegalStateException if it's the root-level resource. - */ - data_resource get_parent() const; - - bool is_root_level() const { - return get_level() == level::ROOT; - } - - bool is_keyspace_level() const { - return get_level() == level::KEYSPACE; - } - - bool is_column_family_level() const { - return get_level() == level::COLUMN_FAMILY; - } - - /** - * @return keyspace of the resource. - * @throws std::invalid_argument if it's the root-level resource. - */ - const sstring& keyspace() const; - - /** - * @return column family of the resource. - * @throws std::invalid_argument if it's not a cf-level resource. - */ - const sstring& column_family() const; - - /** - * @return Whether or not the resource has a parent in the hierarchy. - */ - bool has_parent() const; - - /** - * @return Whether or not the resource exists in scylla. - */ - bool exists() const; - - sstring to_string() const; - - bool operator==(const data_resource&) const; - bool operator<(const data_resource&) const; - - size_t hash_value() const { - return utils::tuple_hash()(_ks, _cf); - } -}; - -/** - * Resource id mappings, i.e. keyspace and/or column families. - */ -using resource_ids = std::set; - -std::ostream& operator<<(std::ostream&, const data_resource&); - -} - - - diff --git a/auth/default_authorizer.cc b/auth/default_authorizer.cc index 95d950d73d..fd3c1a4c06 100644 --- a/auth/default_authorizer.cc +++ b/auth/default_authorizer.cc @@ -106,7 +106,7 @@ future<> auth::default_authorizer::stop() { } future auth::default_authorizer::authorize( - service& ser, ::shared_ptr user, data_resource resource) const { + service& ser, ::shared_ptr user, resource resource) const { return auth::is_super_user(ser, *user).then([this, user, resource = std::move(resource)](bool is_super) { if (is_super) { return make_ready_future(permissions::ALL); @@ -139,7 +139,7 @@ future auth::default_authorizer::authorize( future<> auth::default_authorizer::modify( ::shared_ptr performer, permission_set set, - data_resource resource, sstring user, sstring op) { + resource resource, sstring user, sstring op) { // TODO: why does this not check super user? auto query = sprint("UPDATE %s.%s SET %s = %s %s ? WHERE %s = ? AND %s = ?", meta::AUTH_KS, PERMISSIONS_CF, PERMISSIONS_NAME, @@ -151,19 +151,19 @@ future<> auth::default_authorizer::modify( future<> auth::default_authorizer::grant( ::shared_ptr performer, permission_set set, - data_resource resource, sstring to) { + resource resource, sstring to) { return modify(std::move(performer), std::move(set), std::move(resource), std::move(to), "+"); } future<> auth::default_authorizer::revoke( ::shared_ptr performer, permission_set set, - data_resource resource, sstring from) { + resource resource, sstring from) { return modify(std::move(performer), std::move(set), std::move(resource), std::move(from), "-"); } future> auth::default_authorizer::list( service& ser, ::shared_ptr performer, permission_set set, - optional resource, optional user) const { + optional resource, optional user) const { return auth::is_super_user(ser, *performer).then([this, performer, set = std::move(set), resource = std::move(resource), user = std::move(user)](bool is_super) { if (!is_super && (!user || performer->name() != *user)) { throw exceptions::unauthorized_exception(sprint("You are not authorized to view %s's permissions", user ? *user : "everyone")); @@ -194,7 +194,7 @@ future> auth::default_authorizer::list( for (auto& row : *res) { if (row.has(PERMISSIONS_NAME)) { auto username = row.get_as(USER_NAME); - auto resource = data_resource::from_name(row.get_as(RESOURCE_NAME)); + auto resource = resource::from_name(row.get_as(RESOURCE_NAME)); auto ps = permissions::from_strings(row.get_set(PERMISSIONS_NAME)); ps = permission_set::from_mask(ps.mask() & set.mask()); @@ -219,7 +219,7 @@ future<> auth::default_authorizer::revoke_all(sstring dropped_user) { }); } -future<> auth::default_authorizer::revoke_all(data_resource resource) { +future<> auth::default_authorizer::revoke_all(resource resource) { auto query = sprint("SELECT %s FROM %s.%s WHERE %s = ? ALLOW FILTERING", USER_NAME, meta::AUTH_KS, PERMISSIONS_CF, RESOURCE_NAME); return _qp.process(query, db::consistency_level::LOCAL_ONE, { resource.name() }) @@ -246,10 +246,9 @@ future<> auth::default_authorizer::revoke_all(data_resource resource) { }); } - -const auth::resource_ids& auth::default_authorizer::protected_resources() { - static const resource_ids ids({ data_resource(meta::AUTH_KS, PERMISSIONS_CF) }); - return ids; +const auth::resource_set& auth::default_authorizer::protected_resources() { + static const resource_set resources({ resource::data(meta::AUTH_KS, PERMISSIONS_CF) }); + return resources; } future<> auth::default_authorizer::validate_configuration() const { diff --git a/auth/default_authorizer.hh b/auth/default_authorizer.hh index 5294f730c5..09e6e70d30 100644 --- a/auth/default_authorizer.hh +++ b/auth/default_authorizer.hh @@ -68,24 +68,24 @@ public: return default_authorizer_name(); } - future authorize(service&, ::shared_ptr, data_resource) const override; + future authorize(service&, ::shared_ptr, resource) const override; - future<> grant(::shared_ptr, permission_set, data_resource, sstring) override; + future<> grant(::shared_ptr, permission_set, resource, sstring) override; - future<> revoke(::shared_ptr, permission_set, data_resource, sstring) override; + future<> revoke(::shared_ptr, permission_set, resource, sstring) override; - future> list(service&, ::shared_ptr, permission_set, optional, optional) const override; + future> list(service&, ::shared_ptr, permission_set, optional, optional) const override; future<> revoke_all(sstring) override; - future<> revoke_all(data_resource) override; + future<> revoke_all(resource) override; - const resource_ids& protected_resources() override; + const resource_set& protected_resources() override; future<> validate_configuration() const override; private: - future<> modify(::shared_ptr, permission_set, data_resource, sstring, sstring); + future<> modify(::shared_ptr, permission_set, resource, sstring, sstring); }; } /* namespace auth */ diff --git a/auth/password_authenticator.cc b/auth/password_authenticator.cc index e58e3407a3..9519ac7c6e 100644 --- a/auth/password_authenticator.cc +++ b/auth/password_authenticator.cc @@ -294,9 +294,9 @@ future<> auth::password_authenticator::drop(sstring username) { } } -const auth::resource_ids& auth::password_authenticator::protected_resources() const { - static const resource_ids ids({ data_resource(meta::AUTH_KS, CREDENTIALS_CF) }); - return ids; +const auth::resource_set& auth::password_authenticator::protected_resources() const { + static const resource_set resources({ resource::data(meta::AUTH_KS, CREDENTIALS_CF) }); + return resources; } ::shared_ptr auth::password_authenticator::new_sasl_challenge() const { diff --git a/auth/password_authenticator.hh b/auth/password_authenticator.hh index 8d1c045df3..7b82f4cb41 100644 --- a/auth/password_authenticator.hh +++ b/auth/password_authenticator.hh @@ -76,7 +76,7 @@ public: future<> create(sstring username, const option_map& options) override; future<> alter(sstring username, const option_map& options) override; future<> drop(sstring username) override; - const resource_ids& protected_resources() const override; + const resource_set& protected_resources() const override; ::shared_ptr new_sasl_challenge() const override; diff --git a/auth/permissions_cache.cc b/auth/permissions_cache.cc index f86d0134e7..b1f1244c86 100644 --- a/auth/permissions_cache.cc +++ b/auth/permissions_cache.cc @@ -44,7 +44,7 @@ permissions_cache::permissions_cache(const permissions_cache_config& c, service& }) { } -future permissions_cache::get(::shared_ptr user, data_resource r) { +future permissions_cache::get(::shared_ptr user, resource r) { return _cache.get(key_type(*user, r)); } diff --git a/auth/permissions_cache.hh b/auth/permissions_cache.hh index d4d24ec0ba..5fc919e2ae 100644 --- a/auth/permissions_cache.hh +++ b/auth/permissions_cache.hh @@ -30,20 +30,14 @@ #include #include "auth/authenticated_user.hh" -#include "auth/data_resource.hh" #include "auth/permission.hh" +#include "auth/resource.hh" #include "log.hh" +#include "utils/hash.hh" #include "utils/loading_cache.hh" namespace std { -template <> -struct hash final { - size_t operator()(const auth::data_resource & v) const { - return v.hash_value(); - } -}; - template <> struct hash final { size_t operator()(const auth::authenticated_user & v) const { @@ -51,8 +45,8 @@ struct hash final { } }; -inline std::ostream& operator<<(std::ostream& os, const std::pair& p) { - os << "{user: " << p.first.name() << ", data_resource: " << p.second << "}"; +inline std::ostream& operator<<(std::ostream& os, const std::pair& p) { + os << "{user: " << p.first.name() << ", resource: " << p.second << "}"; return os; } @@ -76,7 +70,7 @@ struct permissions_cache_config final { class permissions_cache final { using cache_type = utils::loading_cache< - std::pair, + std::pair, permission_set, utils::loading_cache_reload_enabled::yes, utils::simple_entry_size, @@ -97,7 +91,7 @@ public: return _cache.stop(); } - future get(::shared_ptr, data_resource); + future get(::shared_ptr, resource); }; } diff --git a/auth/resource.cc b/auth/resource.cc new file mode 100644 index 0000000000..7238830496 --- /dev/null +++ b/auth/resource.cc @@ -0,0 +1,212 @@ +/* + * 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 (C) 2016 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 "auth/resource.hh" + +#include +#include +#include + +#include +#include + +#include "service/storage_proxy.hh" + +namespace auth { + +std::ostream& operator<<(std::ostream& os, resource_kind kind) { + switch (kind) { + case resource_kind::data: os << "data"; break; + } + + return os; +} + +static const std::unordered_map roots{ + {resource_kind::data, "data"}, +}; + +static const std::unordered_map max_parts{ + {resource_kind::data, 2}, +}; + +resource::resource(resource_kind kind) : _kind(kind), _parts{sstring(roots.at(kind))} { +} + +resource::resource(resource_kind kind, std::vector parts) : resource(kind) { + _parts.reserve(parts.size() + 1); + _parts.insert(_parts.end(), std::make_move_iterator(parts.begin()), std::make_move_iterator(parts.end())); +} + +resource resource::data(stdx::string_view keyspace) { + return resource(resource_kind::data, std::vector{sstring(keyspace)}); +} + +resource resource::data(stdx::string_view keyspace, stdx::string_view table) { + return resource(resource_kind::data, std::vector{sstring(keyspace), sstring(table)}); +} + +resource resource::from_name(stdx::string_view name) { + static const std::unordered_map reverse_roots = [] { + std::unordered_map result; + + for (const auto& pair : roots) { + result.emplace(pair.second, pair.first); + } + + return result; + }(); + + std::vector parts; + boost::split(parts, name, [](char ch) { return ch == '/'; }); + + if (parts.empty()) { + throw invalid_resource_name(name); + } + + const auto iter = reverse_roots.find(parts[0]); + if (iter == reverse_roots.end()) { + throw invalid_resource_name(name); + } + + const auto kind = iter->second; + parts.erase(parts.begin()); + + if (parts.size() > max_parts.at(kind)) { + throw invalid_resource_name(name); + } + + return resource(kind, std::move(parts)); +} + +resource resource::root_of(resource_kind kind) { + return resource(kind); +} + +sstring resource::name() const { + return boost::algorithm::join(_parts, "/"); +} + +stdx::optional resource::parent() const { + if (_parts.size() == 1) { + return {}; + } + + resource copy = *this; + copy._parts.pop_back(); + return copy; +} + +bool operator<(const resource& r1, const resource& r2) { + if (r1._kind != r2._kind) { + return r1._kind < r2._kind; + } + + return std::lexicographical_compare( + r1._parts.cbegin() + 1, + r1._parts.cend(), + r2._parts.cbegin() + 1, + r2._parts.cend()); +} + +std::ostream& operator<<(std::ostream& os, const resource& r) { + switch (r.kind()) { + case resource_kind::data: return os << data_resource_view(r); + } + + return os; +} + +data_resource_view::data_resource_view(const resource& r) : _resource(r) { + if (r._kind != resource_kind::data) { + throw resource_kind_mismatch(resource_kind::data, r._kind); + } +} + +stdx::optional data_resource_view::keyspace() const { + if (_resource._parts.size() == 1) { + return {}; + } + + return _resource._parts[1]; +} + +stdx::optional data_resource_view::table() const { + if (_resource._parts.size() <= 2) { + return {}; + } + + return _resource._parts[2]; +} + +std::ostream& operator<<(std::ostream& os, const data_resource_view& v) { + const auto keyspace = v.keyspace(); + const auto table = v.table(); + + if (!keyspace) { + os << ""; + } else if (!table) { + os << "'; + } else { + os << "
'; + } + + return os; +} + +bool resource_exists(const data_resource_view& v) { + // TODO(jhaberku) This dependency on global data is a remnant from the previous version, and needs to be fixed in a + // dedicated patch. + auto& local_db = service::get_local_storage_proxy().get_db().local(); + + const auto keyspace = v.keyspace(); + const auto table = v.table(); + + if (table) { + return local_db.has_schema(sstring(*keyspace), sstring(*table)); + } else if (keyspace) { + return local_db.has_keyspace(sstring(*keyspace)); + } else { + return true; + } +} + +} diff --git a/auth/resource.hh b/auth/resource.hh new file mode 100644 index 0000000000..f992b76b31 --- /dev/null +++ b/auth/resource.hh @@ -0,0 +1,199 @@ +/* + * 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 (C) 2016 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 +#include +#include +#include +#include +#include + +#include +#include + +#include "seastarx.hh" +#include "stdx.hh" +#include "utils/hash.hh" + +namespace auth { + +// +// Resources are entities that users can be granted permissions on. +// +// Currently, the only known resources are keyspaces and tables. However, we shortly anticipate other kinds of resources +// like roles. +// +// When they are stored as system metadata, resources have the form `root/part_0/part_1/.../part_n`. +// Each kind of resource has a specific root prefix, followed by a maximum of `n` parts (where `n` is distinct for each +// kind of resource as well). In this code, this form is called the "name". +// +// Since all resources have this same structure, all the different kinds are stored in instances of the same class: +// `resource`. When we wish to query a resource for kind-specific data (like the table of a `data` resource), we create +// a kind-specific "view" of the resource. +// + +class invalid_resource_name : public std::invalid_argument { + std::shared_ptr _name; + +public: + explicit invalid_resource_name(stdx::string_view name) + : std::invalid_argument(sprint("The resource name '%s' is invalid.", name)) + , _name(std::make_shared(name)) { + } + + stdx::string_view name() const noexcept { + return *_name; + } +}; + +enum class resource_kind { + data +}; + +std::ostream& operator<<(std::ostream&, resource_kind); + +class resource final { + resource_kind _kind; + + std::vector _parts; + +public: + static resource root_of(resource_kind); + + static resource data(stdx::string_view keyspace); + static resource data(stdx::string_view keyspace, stdx::string_view table); + + // Throws `invalid_resource_name` when the name is malformed. + static resource from_name(stdx::string_view); + + resource_kind kind() const noexcept { + return _kind; + } + + // A machine-friendly identifier unique to each resource. + sstring name() const; + + stdx::optional parent() const; + +private: + // A root resource. + explicit resource(resource_kind kind); + + resource(resource_kind, std::vector parts); + + friend class std::hash; + friend class data_resource_view; + + friend bool operator<(const resource&, const resource&); + friend bool operator==(const resource&, const resource&); +}; + +bool operator<(const resource&, const resource&); + +inline bool operator==(const resource& r1, const resource& r2) { + return (r1._kind == r2._kind) && (r1._parts == r2._parts); +} + +inline bool operator!=(const resource& r1, const resource& r2) { + return !(r1 == r2); +} + +std::ostream& operator<<(std::ostream&, const resource&); + +class resource_kind_mismatch : public std::invalid_argument { +public: + explicit resource_kind_mismatch(resource_kind expected, resource_kind actual) + : std::invalid_argument( + sprint("This resource has kind '%s', but was expected to have kind '%s'.", actual, expected)) { + } +}; + +// A `data` view of `resource`. +// +// If neither `keyspace` nor `table` is present, this is the root resource. +class data_resource_view final { + const resource& _resource; + +public: + // Throws `resource_kind_mismatch` if the argument is not a `data` resource. + explicit data_resource_view(const resource& r); + + stdx::optional keyspace() const; + + stdx::optional table() const; +}; + +std::ostream& operator<<(std::ostream&, const data_resource_view&); + +bool resource_exists(const data_resource_view&); + +} + +namespace std { + +template <> +struct hash { + static size_t hash_data(const auth::data_resource_view& dv) { + return utils::tuple_hash()(std::make_tuple(auth::resource_kind::data, dv.keyspace(), dv.table())); + } + + size_t operator()(const auth::resource& r) const { + std::size_t value; + + switch (r._kind) { + case auth::resource_kind::data: value = hash_data(auth::data_resource_view(r)); break; + } + + return value; + } +}; + +} + +namespace auth { + +using resource_set = std::unordered_set; + +} diff --git a/auth/role_manager.hh b/auth/role_manager.hh index 188cab70dc..52eb28c01d 100644 --- a/auth/role_manager.hh +++ b/auth/role_manager.hh @@ -31,7 +31,7 @@ #include #include -#include "auth/data_resource.hh" +#include "auth/resource.hh" #include "seastarx.hh" #include "stdx.hh" diff --git a/auth/service.cc b/auth/service.cc index fd510fe06c..45b4168914 100644 --- a/auth/service.cc +++ b/auth/service.cc @@ -73,11 +73,11 @@ private: void on_update_view(const sstring& ks_name, const sstring& view_name, bool columns_changed) override {} void on_drop_keyspace(const sstring& ks_name) override { - _authorizer.revoke_all(auth::data_resource(ks_name)); + _authorizer.revoke_all(auth::resource::data(ks_name)); } void on_drop_column_family(const sstring& ks_name, const sstring& cf_name) override { - _authorizer.revoke_all(auth::data_resource(ks_name, cf_name)); + _authorizer.revoke_all(auth::resource::data(ks_name, cf_name)); } void on_drop_user_type(const sstring& ks_name, const sstring& type_name) override {} @@ -343,7 +343,7 @@ future<> service::delete_user(const sstring& name) { { name }).discard_result(); } -future service::get_permissions(::shared_ptr u, data_resource r) const { +future service::get_permissions(::shared_ptr u, resource r) const { return sharded_permissions_cache.local().get(std::move(u), std::move(r)); } diff --git a/auth/service.hh b/auth/service.hh index 051e64e190..f32247c0a8 100644 --- a/auth/service.hh +++ b/auth/service.hh @@ -105,7 +105,7 @@ public: future<> delete_user(const sstring& name); - future get_permissions(::shared_ptr, data_resource) const; + future get_permissions(::shared_ptr, resource) const; authenticator& underlying_authenticator() { return *_authenticator; diff --git a/auth/transitional.cc b/auth/transitional.cc index 519fe58e30..253cc610f0 100644 --- a/auth/transitional.cc +++ b/auth/transitional.cc @@ -120,7 +120,7 @@ public: future<> drop(sstring username) override { return _authenticator->drop(username); } - const resource_ids& protected_resources() const override { + const resource_set& protected_resources() const override { return _authenticator->protected_resources(); } ::shared_ptr new_sasl_challenge() const override { @@ -171,7 +171,7 @@ public: const sstring& qualified_java_name() const override { return transitional_authorizer_name(); } - future authorize(service& ser, ::shared_ptr user, data_resource resource) const override { + future authorize(service& ser, ::shared_ptr user, resource resource) const override { return is_super_user(ser, *user).then([](bool s) { static const permission_set transitional_permissions = permission_set::of(s ? permissions::ALL : transitional_permissions); }); } - future<> grant(::shared_ptr user, permission_set ps, data_resource r, sstring s) override { + future<> grant(::shared_ptr user, permission_set ps, resource r, sstring s) override { return _authorizer->grant(std::move(user), std::move(ps), std::move(r), std::move(s)); } - future<> revoke(::shared_ptr user, permission_set ps, data_resource r, sstring s) override { + future<> revoke(::shared_ptr user, permission_set ps, resource r, sstring s) override { return _authorizer->revoke(std::move(user), std::move(ps), std::move(r), std::move(s)); } - future> list(service& ser, ::shared_ptr user, permission_set ps, optional r, optional s) const override { + future> list(service& ser, ::shared_ptr user, permission_set ps, optional r, optional s) const override { return _authorizer->list(ser, std::move(user), std::move(ps), std::move(r), std::move(s)); } future<> revoke_all(sstring s) override { return _authorizer->revoke_all(std::move(s)); } - future<> revoke_all(data_resource r) override { + future<> revoke_all(resource r) override { return _authorizer->revoke_all(std::move(r)); } - const resource_ids& protected_resources() override { + const resource_set& protected_resources() override { return _authorizer->protected_resources(); } future<> validate_configuration() const override { diff --git a/configure.py b/configure.py index b642ceb753..727f7782e3 100755 --- a/configure.py +++ b/configure.py @@ -252,6 +252,7 @@ scylla_tests = [ 'tests/aggregate_fcts_test', 'tests/role_manager_test', 'tests/caching_options_test', + 'tests/auth_resource_test', ] apps = [ @@ -534,7 +535,7 @@ scylla_core = (['database.cc', 'auth/authenticator.cc', 'auth/common.cc', 'auth/default_authorizer.cc', - 'auth/data_resource.cc', + 'auth/resource.cc', 'auth/password_authenticator.cc', 'auth/permission.cc', 'auth/permissions_cache.cc', @@ -652,6 +653,7 @@ pure_boost_tests = set([ 'tests/chunked_vector_test', 'tests/big_decimal_test', 'tests/caching_options_test', + 'tests/auth_resource_test', ]) tests_not_using_seastar_test_framework = set([ diff --git a/cql3/Cql.g b/cql3/Cql.g index 07bbbfe951..afa3a93b92 100644 --- a/cql3/Cql.g +++ b/cql3/Cql.g @@ -1022,7 +1022,7 @@ revokeRoleStatement returns [::shared_ptr stmt] listPermissionsStatement returns [::shared_ptr stmt] @init { - std::experimental::optional r; + std::experimental::optional r; std::experimental::optional u; bool recursive = true; } @@ -1044,15 +1044,15 @@ permissionOrAll returns [auth::permission_set perms] | p=permission ( K_PERMISSION )? { $perms = auth::permission_set::from_mask(auth::permission_set::mask_for($p.perm)); } ; -resource returns [auth::data_resource res] +resource returns [uninitialized res] : r=dataResource { $res = $r.res; } ; -dataResource returns [auth::data_resource res] - : K_ALL K_KEYSPACES { $res = auth::data_resource(); } - | K_KEYSPACE ks = keyspaceName { $res = auth::data_resource($ks.id); } +dataResource returns [uninitialized res] + : K_ALL K_KEYSPACES { $res = auth::resource::root_of(auth::resource_kind::data); } + | K_KEYSPACE ks = keyspaceName { $res = auth::resource::data($ks.id); } | ( K_COLUMNFAMILY )? cf = columnFamilyName - { $res = auth::data_resource($cf.name->get_keyspace(), $cf.name->get_column_family()); } + { $res = auth::resource::data($cf.name->get_keyspace(), $cf.name->get_column_family()); } ; /** diff --git a/cql3/statements/authorization_statement.cc b/cql3/statements/authorization_statement.cc index ba881dd819..c3115833e6 100644 --- a/cql3/statements/authorization_statement.cc +++ b/cql3/statements/authorization_statement.cc @@ -82,9 +82,15 @@ future<::shared_ptr> cql3::statements:: throw std::runtime_error("unsupported operation"); } -void cql3::statements::authorization_statement::mayme_correct_resource(auth::data_resource& resource, const service::client_state& state) { - if (resource.is_column_family_level() && resource.keyspace().empty()) { - resource = auth::data_resource(state.get_keyspace(), resource.column_family()); +void cql3::statements::authorization_statement::maybe_correct_resource(auth::resource& resource, const service::client_state& state) { + if (resource.kind() == auth::resource_kind::data) { + const auto data_view = auth::data_resource_view(resource); + const auto keyspace = data_view.keyspace(); + const auto table = data_view.table(); + + if (table && keyspace->empty()) { + resource = auth::resource::data(state.get_keyspace(), *table); + } } } diff --git a/cql3/statements/authorization_statement.hh b/cql3/statements/authorization_statement.hh index 36ec2b46bb..4ae9569483 100644 --- a/cql3/statements/authorization_statement.hh +++ b/cql3/statements/authorization_statement.hh @@ -47,7 +47,7 @@ #include "transport/messages_fwd.hh" namespace auth { -class data_resource; +class resource; } namespace cql3 { @@ -74,7 +74,7 @@ public: execute_internal(distributed& proxy, service::query_state& state, const query_options& options) override; protected: - static void mayme_correct_resource(auth::data_resource&, const service::client_state&); + static void maybe_correct_resource(auth::resource&, const service::client_state&); }; } diff --git a/cql3/statements/list_permissions_statement.cc b/cql3/statements/list_permissions_statement.cc index 1b745b5d60..4f831680a1 100644 --- a/cql3/statements/list_permissions_statement.cc +++ b/cql3/statements/list_permissions_statement.cc @@ -50,7 +50,7 @@ cql3::statements::list_permissions_statement::list_permissions_statement( auth::permission_set permissions, - std::experimental::optional resource, + std::experimental::optional resource, std::experimental::optional username, bool recursive) : _permissions(permissions), _resource(std::move(resource)), _username( std::move(username)), _recursive(recursive) { @@ -72,8 +72,10 @@ future<> cql3::statements::list_permissions_statement::check_access(const servic } return f.then([this, &state] { if (_resource) { - mayme_correct_resource(*_resource, state); - if (!_resource->exists()) { + maybe_correct_resource(*_resource, state); + + if ((_resource->kind() == auth::resource_kind::data) + && !auth::resource_exists(auth::data_resource_view(*_resource))) { throw exceptions::invalid_request_exception(sprint("%s doesn't exist", *_resource)); } } @@ -90,17 +92,23 @@ cql3::statements::list_permissions_statement::execute(distributed opt_resource; + typedef std::experimental::optional opt_resource; std::vector resources; auto r = _resource; for (;;) { resources.emplace_back(r); - if (!r || !r->has_parent() || !_recursive) { + if (!r || !_recursive) { break; } - r = r->get_parent(); + + auto parent = r->parent(); + if (!parent) { + break; + } + + r = std::move(parent); } return map_reduce(resources, [&state, this](opt_resource r) { @@ -121,7 +129,7 @@ cql3::statements::list_permissions_statement::execute(distributedadd_row( std::vector { utf8_type->decompose( v.user), utf8_type->decompose( - v.resource.to_string()), + sstring(sprint("%s", v.resource))), utf8_type->decompose(p), }); } } diff --git a/cql3/statements/list_permissions_statement.hh b/cql3/statements/list_permissions_statement.hh index f3239e4970..802af7f9f1 100644 --- a/cql3/statements/list_permissions_statement.hh +++ b/cql3/statements/list_permissions_statement.hh @@ -45,7 +45,7 @@ #include "authorization_statement.hh" #include "auth/permission.hh" -#include "auth/data_resource.hh" +#include "auth/resource.hh" namespace cql3 { @@ -54,12 +54,12 @@ namespace statements { class list_permissions_statement : public authorization_statement { private: auth::permission_set _permissions; - std::experimental::optional _resource; + std::experimental::optional _resource; std::experimental::optional _username; bool _recursive; public: - list_permissions_statement(auth::permission_set, std::experimental::optional, std::experimental::optional, bool); + list_permissions_statement(auth::permission_set, std::experimental::optional, std::experimental::optional, bool); void validate(distributed&, const service::client_state&) override; future<> check_access(const service::client_state&) override; diff --git a/cql3/statements/permission_altering_statement.cc b/cql3/statements/permission_altering_statement.cc index a4099c4f29..56028dd4bc 100644 --- a/cql3/statements/permission_altering_statement.cc +++ b/cql3/statements/permission_altering_statement.cc @@ -48,7 +48,7 @@ #include "cql3/selection/selection.hh" cql3::statements::permission_altering_statement::permission_altering_statement( - auth::permission_set permissions, auth::data_resource resource, + auth::permission_set permissions, auth::resource resource, sstring username) : _permissions(permissions), _resource(std::move(resource)), _username( std::move(username)) { @@ -64,8 +64,10 @@ future<> cql3::statements::permission_altering_statement::check_access(const ser if (!exists) { throw exceptions::invalid_request_exception(sprint("User %s doesn't exist", _username)); } - mayme_correct_resource(_resource, state); - if (!_resource.exists()) { + maybe_correct_resource(_resource, state); + + if ((_resource.kind() == auth::resource_kind::data) + && !auth::resource_exists(auth::data_resource_view(_resource))) { throw exceptions::invalid_request_exception(sprint("%s doesn't exist", _resource)); } diff --git a/cql3/statements/permission_altering_statement.hh b/cql3/statements/permission_altering_statement.hh index f8c5e36b92..587f65504b 100644 --- a/cql3/statements/permission_altering_statement.hh +++ b/cql3/statements/permission_altering_statement.hh @@ -43,7 +43,7 @@ #include "authorization_statement.hh" #include "auth/permission.hh" -#include "auth/data_resource.hh" +#include "auth/resource.hh" namespace cql3 { @@ -52,11 +52,11 @@ namespace statements { class permission_altering_statement : public authorization_statement { protected: auth::permission_set _permissions; - auth::data_resource _resource; + auth::resource _resource; sstring _username; public: - permission_altering_statement(auth::permission_set, auth::data_resource, sstring); + permission_altering_statement(auth::permission_set, auth::resource, sstring); void validate(distributed&, const service::client_state&) override; future<> check_access(const service::client_state&) override; diff --git a/cql3/statements/role-management-statements.cc b/cql3/statements/role-management-statements.cc index 04c4afcf62..fc1ce32cd9 100644 --- a/cql3/statements/role-management-statements.cc +++ b/cql3/statements/role-management-statements.cc @@ -235,12 +235,12 @@ 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 sstring virtual_table_name("roles"); static const auto make_column_spec = [](const sstring& name, const ::shared_ptr& ty) { return ::make_shared( - virtual_table.keyspace(), - virtual_table.column_family(), + auth::meta::AUTH_KS, + virtual_table_name, ::make_shared(name, true), ty); }; diff --git a/service/client_state.cc b/service/client_state.cc index 12bb114d94..107bf414d1 100644 --- a/service/client_state.cc +++ b/service/client_state.cc @@ -100,25 +100,25 @@ future<> service::client_state::has_all_keyspaces_access( return make_ready_future(); } validate_login(); - return ensure_has_permission(p, auth::data_resource()); + return ensure_has_permission(p, auth::resource::root_of(auth::resource_kind::data)); } future<> service::client_state::has_keyspace_access(const sstring& ks, auth::permission p) const { - return has_access(ks, p, auth::data_resource(ks)); + return has_access(ks, p, auth::resource::data(ks)); } future<> service::client_state::has_column_family_access(const sstring& ks, const sstring& cf, auth::permission p) const { validation::validate_column_family(ks, cf); - return has_access(ks, p, auth::data_resource(ks, cf)); + return has_access(ks, p, auth::resource::data(ks, cf)); } future<> service::client_state::has_schema_access(const schema& s, auth::permission p) const { - return has_access(s.ks_name(), p, auth::data_resource(s.ks_name(), s.cf_name())); + return has_access(s.ks_name(), p, auth::resource::data(s.ks_name(), s.cf_name())); } -future<> service::client_state::has_access(const sstring& ks, auth::permission p, auth::data_resource resource) const { +future<> service::client_state::has_access(const sstring& ks, auth::permission p, auth::resource resource) const { if (ks.empty()) { throw exceptions::invalid_request_exception("You have not set a keyspace for this session"); } @@ -145,13 +145,13 @@ future<> service::client_state::has_access(const sstring& ks, auth::permission p } } - static thread_local std::set readable_system_resources = [] { - std::set tmp; + static thread_local std::set readable_system_resources = [] { + std::set tmp; for (auto cf : { db::system_keyspace::LOCAL, db::system_keyspace::PEERS }) { - tmp.emplace(db::system_keyspace::NAME, cf); + tmp.insert(auth::resource::data(db::system_keyspace::NAME, cf)); } for (auto cf : db::schema_tables::ALL) { - tmp.emplace(db::schema_tables::NAME, cf); + tmp.insert(auth::resource::data(db::schema_tables::NAME, cf)); } return tmp; }(); @@ -173,15 +173,12 @@ future<> service::client_state::has_access(const sstring& ks, auth::permission p return ensure_has_permission(p, std::move(resource)); } -future service::client_state::check_has_permission(auth::permission p, auth::data_resource resource) const { +future service::client_state::check_has_permission(auth::permission p, auth::resource resource) const { if (_is_internal) { return make_ready_future(true); } - std::experimental::optional parent; - if (resource.has_parent()) { - parent = resource.get_parent(); - } + std::experimental::optional parent = resource.parent(); return _auth_service->get_permissions(_user, resource).then([this, p, parent = std::move(parent)](auth::permission_set set) { if (set.contains(p)) { @@ -194,7 +191,7 @@ future service::client_state::check_has_permission(auth::permission p, aut }); } -future<> service::client_state::ensure_has_permission(auth::permission p, auth::data_resource resource) const { +future<> service::client_state::ensure_has_permission(auth::permission p, auth::resource resource) const { return check_has_permission(p, resource).then([this, p, resource](bool ok) { if (!ok) { throw exceptions::unauthorized_exception(sprint("User %s has no %s permission on %s or any of its parents", diff --git a/service/client_state.hh b/service/client_state.hh index a18f45656f..1d4aa311d8 100644 --- a/service/client_state.hh +++ b/service/client_state.hh @@ -253,10 +253,10 @@ public: future<> has_schema_access(const schema& s, auth::permission p) const; private: - future<> has_access(const sstring&, auth::permission, auth::data_resource) const; - future check_has_permission(auth::permission, auth::data_resource) const; + future<> has_access(const sstring&, auth::permission, auth::resource) const; + future check_has_permission(auth::permission, auth::resource) const; public: - future<> ensure_has_permission(auth::permission, auth::data_resource) const; + future<> ensure_has_permission(auth::permission, auth::resource) const; void validate_login() const; void ensure_not_anonymous() const; // unauthorized_exception on error diff --git a/test.py b/test.py index 6eadc5ce1e..5e4bda9ce1 100755 --- a/test.py +++ b/test.py @@ -95,6 +95,7 @@ boost_tests = [ 'aggregate_fcts_test', 'role_manager_test', 'caching_options_test', + 'auth_resource_test', ] other_tests = [ diff --git a/tests/auth_resource_test.cc b/tests/auth_resource_test.cc new file mode 100644 index 0000000000..486effc5d6 --- /dev/null +++ b/tests/auth_resource_test.cc @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2017 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 . + */ + +#define BOOST_TEST_MODULE core + +#include "auth/resource.hh" + +#include + +#include + +BOOST_AUTO_TEST_CASE(root_of) { + const auto r = auth::resource::root_of(auth::resource_kind::data); + BOOST_REQUIRE_EQUAL(r.kind(), auth::resource_kind::data); + + const auto v = auth::data_resource_view(r); + BOOST_REQUIRE(!v.keyspace()); + BOOST_REQUIRE(!v.table()); +} + +BOOST_AUTO_TEST_CASE(data) { + const auto r1 = auth::resource::data("my_keyspace"); + BOOST_REQUIRE_EQUAL(r1.kind(), auth::resource_kind::data); + const auto v1 = auth::data_resource_view(r1); + + BOOST_REQUIRE_EQUAL(*v1.keyspace(), "my_keyspace"); + BOOST_REQUIRE(!v1.table()); + + const auto r2 = auth::resource::data("my_keyspace", "my_table"); + BOOST_REQUIRE_EQUAL(r2.kind(), auth::resource_kind::data); + const auto v2 = auth::data_resource_view(r2); + + BOOST_REQUIRE_EQUAL(*v2.keyspace(), "my_keyspace"); + BOOST_REQUIRE_EQUAL(*v2.table(), "my_table"); +} + +BOOST_AUTO_TEST_CASE(from_name) { + const auto r1 = auth::resource::from_name("data"); + BOOST_REQUIRE_EQUAL(r1, auth::resource::root_of(auth::resource_kind::data)); + + const auto r2 = auth::resource::from_name("data/my_keyspace"); + BOOST_REQUIRE_EQUAL(r2, auth::resource::data("my_keyspace")); + + const auto r3 = auth::resource::from_name("data/my_keyspace/my_table"); + BOOST_REQUIRE_EQUAL(r3, auth::resource::data("my_keyspace", "my_table")); + + BOOST_REQUIRE_THROW(auth::resource::from_name("animal/horse"), auth::invalid_resource_name); + BOOST_REQUIRE_THROW(auth::resource::from_name(""), auth::invalid_resource_name); + BOOST_REQUIRE_THROW(auth::resource::from_name("data/foo/bar/baz"), auth::invalid_resource_name); +} + +BOOST_AUTO_TEST_CASE(name) { + BOOST_REQUIRE_EQUAL(auth::resource::root_of(auth::resource_kind::data).name(), "data"); + BOOST_REQUIRE_EQUAL(auth::resource::data("my_keyspace").name(), "data/my_keyspace"); + BOOST_REQUIRE_EQUAL(auth::resource::data("my_keyspace", "my_table").name(), "data/my_keyspace/my_table"); +} + +BOOST_AUTO_TEST_CASE(parent) { + const auto r1 = auth::resource::data("my_keyspace", "my_table"); + + const auto r2 = r1.parent(); + BOOST_REQUIRE(r2); + BOOST_REQUIRE_EQUAL(*r2, auth::resource::data("my_keyspace")); + + const auto r3 = r2->parent(); + BOOST_REQUIRE(r3); + BOOST_REQUIRE_EQUAL(*r3, auth::resource::root_of(auth::resource_kind::data)); + + const auto r4 = r3->parent(); + BOOST_REQUIRE(!r4); +} + +BOOST_AUTO_TEST_CASE(output) { + BOOST_REQUIRE_EQUAL(sprint("%s", auth::resource::root_of(auth::resource_kind::data)), ""); + BOOST_REQUIRE_EQUAL(sprint("%s", auth::resource::data("my_keyspace")), ""); + BOOST_REQUIRE_EQUAL(sprint("%s", auth::resource::data("my_keyspace", "my_table")), "
"); +} diff --git a/tests/auth_test.cc b/tests/auth_test.cc index 84d130b35c..3af8d0722a 100644 --- a/tests/auth_test.cc +++ b/tests/auth_test.cc @@ -35,39 +35,15 @@ #include "tests/cql_assertions.hh" #include "auth/allow_all_authenticator.hh" -#include "auth/data_resource.hh" #include "auth/authenticator.hh" #include "auth/password_authenticator.hh" #include "auth/service.hh" #include "auth/authenticated_user.hh" +#include "auth/resource.hh" #include "db/config.hh" #include "cql3/query_processor.hh" -SEASTAR_TEST_CASE(test_data_resource) { - auth::data_resource root, keyspace("fisk"), column_family("fisk", "notter"); - - BOOST_REQUIRE_EQUAL(root.is_root_level(), true); - BOOST_REQUIRE_EQUAL(keyspace.is_keyspace_level(), true); - BOOST_REQUIRE_EQUAL(column_family.is_column_family_level(), true); - - BOOST_REQUIRE_EQUAL(root.has_parent(), false); - BOOST_REQUIRE_EQUAL(keyspace.has_parent(), true); - BOOST_REQUIRE_EQUAL(column_family.has_parent(), true); - - try { - root.get_parent(); - BOOST_FAIL("Should not reach"); - } catch (...) { - // ok - } - - BOOST_REQUIRE_EQUAL(keyspace.get_parent(), root); - BOOST_REQUIRE_EQUAL(column_family.get_parent(), keyspace); - - return make_ready_future(); -} - SEASTAR_TEST_CASE(test_default_authenticator) { return do_with_cql_env([](cql_test_env& env) { auto& a = env.local_auth_service().underlying_authenticator();