Merge "Generalize data_resource" from Jesse

"Soon we will have resources beyond just keyspaces and table names. There
will be resources for roles, for user-defined functions (UDFs), and
possible resources for REST end-points. This change generalizes the
implementation of a `data_resource` to many different kinds of
resources, though there is still only one kind (`data`).

The most important patch is 2/5 ("auth/resource: Generalize to different
kinds"), which re-writes `auth::data_resource`. The patch message should
sufficiently explain the design decisions involved.

The other patches rename files and identifiers based on the expanded
role of this class, except for 5/5 ("auth/resource.hh: Rename
`resource_ids`"): this patch gives a more appropriate name to a type
alias.

Fixes #3027."

* 'jhk/generalize_resource/v3' of https://github.com/hakuch/scylla:
  auth/resource.hh: Rename `resource_ids`
  auth: Rename `data_resource` files
  cql3/authorization_statement: Fix typo
  auth/resource: Generalize to different kinds
  auth: Rename `data_resource` to `resource`
This commit is contained in:
Avi Kivity
2017-12-07 10:25:58 +02:00
33 changed files with 642 additions and 481 deletions

View File

@@ -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<sasl_challenge> new_sasl_challenge() const override {

View File

@@ -56,15 +56,15 @@ public:
return allow_all_authorizer_name();
}
future<permission_set> authorize(service&, ::shared_ptr<authenticated_user>, data_resource) const override {
future<permission_set> authorize(service&, ::shared_ptr<authenticated_user>, resource) const override {
return make_ready_future<permission_set>(permissions::ALL);
}
future<> grant(::shared_ptr<authenticated_user>, permission_set, data_resource, sstring) override {
future<> grant(::shared_ptr<authenticated_user>, permission_set, resource, sstring) override {
throw exceptions::invalid_request_exception("GRANT operation is not supported by AllowAllAuthorizer");
}
future<> revoke(::shared_ptr<authenticated_user>, permission_set, data_resource, sstring) override {
future<> revoke(::shared_ptr<authenticated_user>, 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<authenticated_user> performer,
permission_set,
stdx::optional<data_resource>,
stdx::optional<resource>,
stdx::optional<sstring>) 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 {

View File

@@ -53,9 +53,9 @@
#include <seastar/core/enum.hh>
#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:

View File

@@ -74,27 +74,27 @@ auth::authorizer::setup(const sstring& type) {
const sstring& qualified_java_name() const override {
return allow_all_authorizer_name();
}
future<permission_set> authorize(::shared_ptr<authenticated_user>, data_resource) const override {
future<permission_set> authorize(::shared_ptr<authenticated_user>, resource) const override {
return make_ready_future<permission_set>(permissions::ALL);
}
future<> grant(::shared_ptr<authenticated_user>, permission_set, data_resource, sstring) override {
future<> grant(::shared_ptr<authenticated_user>, permission_set, resource, sstring) override {
throw exceptions::invalid_request_exception("GRANT operation is not supported by AllowAllAuthorizer");
}
future<> revoke(::shared_ptr<authenticated_user>, permission_set, data_resource, sstring) override {
future<> revoke(::shared_ptr<authenticated_user>, permission_set, resource, sstring) override {
throw exceptions::invalid_request_exception("REVOKE operation is not supported by AllowAllAuthorizer");
}
future<std::vector<permission_details>> list(::shared_ptr<authenticated_user> performer, permission_set, optional<data_resource>, optional<sstring>) const override {
future<std::vector<permission_details>> list(::shared_ptr<authenticated_user> performer, permission_set, optional<resource>, optional<sstring>) 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();

View File

@@ -49,7 +49,7 @@
#include <seastar/core/shared_ptr.hh>
#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<permission_set> authorize(service&, ::shared_ptr<authenticated_user>, data_resource) const = 0;
virtual future<permission_set> authorize(service&, ::shared_ptr<authenticated_user>, 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<authenticated_user> performer, permission_set, data_resource, sstring to) = 0;
virtual future<> grant(::shared_ptr<authenticated_user> 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<authenticated_user> performer, permission_set, data_resource, sstring from) = 0;
virtual future<> revoke(::shared_ptr<authenticated_user> 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<std::vector<permission_details>> list(service&, ::shared_ptr<authenticated_user> performer, permission_set, optional<data_resource>, optional<sstring>) const = 0;
virtual future<std::vector<permission_details>> list(service&, ::shared_ptr<authenticated_user> performer, permission_set, optional<resource>, optional<sstring>) 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).

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
#include "data_resource.hh"
#include <regex>
#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<sstring::const_iterator>(s.begin(),
s.end(), slash_regex, -1);
auto e = std::regex_token_iterator<sstring::const_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 "<all keyspaces>";
case level::KEYSPACE:
return sprint("<keyspace %s>", _ks);
case level::COLUMN_FAMILY:
default:
return sprint("<table %s.%s>", _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();
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "utils/hash.hh"
#include <iosfwd>
#include <set>
#include <seastar/core/sstring.hh>
#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<data_resource>;
std::ostream& operator<<(std::ostream&, const data_resource&);
}

View File

@@ -106,7 +106,7 @@ future<> auth::default_authorizer::stop() {
}
future<auth::permission_set> auth::default_authorizer::authorize(
service& ser, ::shared_ptr<authenticated_user> user, data_resource resource) const {
service& ser, ::shared_ptr<authenticated_user> 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<permission_set>(permissions::ALL);
@@ -139,7 +139,7 @@ future<auth::permission_set> auth::default_authorizer::authorize(
future<> auth::default_authorizer::modify(
::shared_ptr<authenticated_user> 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<authenticated_user> 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<authenticated_user> 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<std::vector<auth::permission_details>> auth::default_authorizer::list(
service& ser, ::shared_ptr<authenticated_user> performer, permission_set set,
optional<data_resource> resource, optional<sstring> user) const {
optional<resource> resource, optional<sstring> 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<std::vector<auth::permission_details>> auth::default_authorizer::list(
for (auto& row : *res) {
if (row.has(PERMISSIONS_NAME)) {
auto username = row.get_as<sstring>(USER_NAME);
auto resource = data_resource::from_name(row.get_as<sstring>(RESOURCE_NAME));
auto resource = resource::from_name(row.get_as<sstring>(RESOURCE_NAME));
auto ps = permissions::from_strings(row.get_set<sstring>(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 {

View File

@@ -68,24 +68,24 @@ public:
return default_authorizer_name();
}
future<permission_set> authorize(service&, ::shared_ptr<authenticated_user>, data_resource) const override;
future<permission_set> authorize(service&, ::shared_ptr<authenticated_user>, resource) const override;
future<> grant(::shared_ptr<authenticated_user>, permission_set, data_resource, sstring) override;
future<> grant(::shared_ptr<authenticated_user>, permission_set, resource, sstring) override;
future<> revoke(::shared_ptr<authenticated_user>, permission_set, data_resource, sstring) override;
future<> revoke(::shared_ptr<authenticated_user>, permission_set, resource, sstring) override;
future<std::vector<permission_details>> list(service&, ::shared_ptr<authenticated_user>, permission_set, optional<data_resource>, optional<sstring>) const override;
future<std::vector<permission_details>> list(service&, ::shared_ptr<authenticated_user>, permission_set, optional<resource>, optional<sstring>) 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<authenticated_user>, permission_set, data_resource, sstring, sstring);
future<> modify(::shared_ptr<authenticated_user>, permission_set, resource, sstring, sstring);
};
} /* namespace auth */

View File

@@ -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::authenticator::sasl_challenge> auth::password_authenticator::new_sasl_challenge() const {

View File

@@ -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<sasl_challenge> new_sasl_challenge() const override;

View File

@@ -44,7 +44,7 @@ permissions_cache::permissions_cache(const permissions_cache_config& c, service&
}) {
}
future<permission_set> permissions_cache::get(::shared_ptr<authenticated_user> user, data_resource r) {
future<permission_set> permissions_cache::get(::shared_ptr<authenticated_user> user, resource r) {
return _cache.get(key_type(*user, r));
}

View File

@@ -30,20 +30,14 @@
#include <seastar/core/shared_ptr.hh>
#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<auth::data_resource> final {
size_t operator()(const auth::data_resource & v) const {
return v.hash_value();
}
};
template <>
struct hash<auth::authenticated_user> final {
size_t operator()(const auth::authenticated_user & v) const {
@@ -51,8 +45,8 @@ struct hash<auth::authenticated_user> final {
}
};
inline std::ostream& operator<<(std::ostream& os, const std::pair<auth::authenticated_user, auth::data_resource>& p) {
os << "{user: " << p.first.name() << ", data_resource: " << p.second << "}";
inline std::ostream& operator<<(std::ostream& os, const std::pair<auth::authenticated_user, auth::resource>& 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<authenticated_user, data_resource>,
std::pair<authenticated_user, resource>,
permission_set,
utils::loading_cache_reload_enabled::yes,
utils::simple_entry_size<permission_set>,
@@ -97,7 +91,7 @@ public:
return _cache.stop();
}
future<permission_set> get(::shared_ptr<authenticated_user>, data_resource);
future<permission_set> get(::shared_ptr<authenticated_user>, resource);
};
}

212
auth/resource.cc Normal file
View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
#include "auth/resource.hh"
#include <algorithm>
#include <iterator>
#include <unordered_map>
#include <boost/algorithm/string/join.hpp>
#include <boost/algorithm/string/split.hpp>
#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<resource_kind, stdx::string_view> roots{
{resource_kind::data, "data"},
};
static const std::unordered_map<resource_kind, std::size_t> 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<sstring> 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>{sstring(keyspace)});
}
resource resource::data(stdx::string_view keyspace, stdx::string_view table) {
return resource(resource_kind::data, std::vector<sstring>{sstring(keyspace), sstring(table)});
}
resource resource::from_name(stdx::string_view name) {
static const std::unordered_map<sstring, resource_kind> reverse_roots = [] {
std::unordered_map<sstring, resource_kind> result;
for (const auto& pair : roots) {
result.emplace(pair.second, pair.first);
}
return result;
}();
std::vector<sstring> 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> 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<stdx::string_view> data_resource_view::keyspace() const {
if (_resource._parts.size() == 1) {
return {};
}
return _resource._parts[1];
}
stdx::optional<stdx::string_view> 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 << "<all keyspaces>";
} else if (!table) {
os << "<keyspace " << *keyspace << '>';
} else {
os << "<table " << *keyspace << '.' << *table << '>';
}
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;
}
}
}

199
auth/resource.hh Normal file
View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <experimental/optional>
#include <experimental/string_view>
#include <iostream>
#include <memory>
#include <stdexcept>
#include <tuple>
#include <vector>
#include <unordered_set>
#include <seastar/core/print.hh>
#include <seastar/core/sstring.hh>
#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<sstring> _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<sstring>(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<sstring> _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<resource> parent() const;
private:
// A root resource.
explicit resource(resource_kind kind);
resource(resource_kind, std::vector<sstring> parts);
friend class std::hash<resource>;
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<stdx::string_view> keyspace() const;
stdx::optional<stdx::string_view> table() const;
};
std::ostream& operator<<(std::ostream&, const data_resource_view&);
bool resource_exists(const data_resource_view&);
}
namespace std {
template <>
struct hash<auth::resource> {
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<resource>;
}

View File

@@ -31,7 +31,7 @@
#include <seastar/core/print.hh>
#include <seastar/core/sstring.hh>
#include "auth/data_resource.hh"
#include "auth/resource.hh"
#include "seastarx.hh"
#include "stdx.hh"

View File

@@ -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<permission_set> service::get_permissions(::shared_ptr<authenticated_user> u, data_resource r) const {
future<permission_set> service::get_permissions(::shared_ptr<authenticated_user> u, resource r) const {
return sharded_permissions_cache.local().get(std::move(u), std::move(r));
}

View File

@@ -105,7 +105,7 @@ public:
future<> delete_user(const sstring& name);
future<permission_set> get_permissions(::shared_ptr<authenticated_user>, data_resource) const;
future<permission_set> get_permissions(::shared_ptr<authenticated_user>, resource) const;
authenticator& underlying_authenticator() {
return *_authenticator;

View File

@@ -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<sasl_challenge> new_sasl_challenge() const override {
@@ -171,7 +171,7 @@ public:
const sstring& qualified_java_name() const override {
return transitional_authorizer_name();
}
future<permission_set> authorize(service& ser, ::shared_ptr<authenticated_user> user, data_resource resource) const override {
future<permission_set> authorize(service& ser, ::shared_ptr<authenticated_user> user, resource resource) const override {
return is_super_user(ser, *user).then([](bool s) {
static const permission_set transitional_permissions =
permission_set::of<permission::CREATE,
@@ -181,22 +181,22 @@ public:
return make_ready_future<permission_set>(s ? permissions::ALL : transitional_permissions);
});
}
future<> grant(::shared_ptr<authenticated_user> user, permission_set ps, data_resource r, sstring s) override {
future<> grant(::shared_ptr<authenticated_user> 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<authenticated_user> user, permission_set ps, data_resource r, sstring s) override {
future<> revoke(::shared_ptr<authenticated_user> 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<std::vector<permission_details>> list(service& ser, ::shared_ptr<authenticated_user> user, permission_set ps, optional<data_resource> r, optional<sstring> s) const override {
future<std::vector<permission_details>> list(service& ser, ::shared_ptr<authenticated_user> user, permission_set ps, optional<resource> r, optional<sstring> 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 {

View File

@@ -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([

View File

@@ -1022,7 +1022,7 @@ revokeRoleStatement returns [::shared_ptr<revoke_role_statement> stmt]
listPermissionsStatement returns [::shared_ptr<list_permissions_statement> stmt]
@init {
std::experimental::optional<auth::data_resource> r;
std::experimental::optional<auth::resource> r;
std::experimental::optional<sstring> 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<auth::resource> 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<auth::resource> 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()); }
;
/**

View File

@@ -82,9 +82,15 @@ future<::shared_ptr<cql_transport::messages::result_message>> 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);
}
}
}

View File

@@ -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<service::storage_proxy>& 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&);
};
}

View File

@@ -50,7 +50,7 @@
cql3::statements::list_permissions_statement::list_permissions_statement(
auth::permission_set permissions,
std::experimental::optional<auth::data_resource> resource,
std::experimental::optional<auth::resource> resource,
std::experimental::optional<sstring> 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<service::stora
make_column("username"), make_column("resource"), make_column("permission")
});
typedef std::experimental::optional<auth::data_resource> opt_resource;
typedef std::experimental::optional<auth::resource> opt_resource;
std::vector<opt_resource> 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(distributed<service::stora
rs->add_row(
std::vector<bytes_opt> { utf8_type->decompose(
v.user), utf8_type->decompose(
v.resource.to_string()),
sstring(sprint("%s", v.resource))),
utf8_type->decompose(p), });
}
}

View File

@@ -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<auth::data_resource> _resource;
std::experimental::optional<auth::resource> _resource;
std::experimental::optional<sstring> _username;
bool _recursive;
public:
list_permissions_statement(auth::permission_set, std::experimental::optional<auth::data_resource>, std::experimental::optional<sstring>, bool);
list_permissions_statement(auth::permission_set, std::experimental::optional<auth::resource>, std::experimental::optional<sstring>, bool);
void validate(distributed<service::storage_proxy>&, const service::client_state&) override;
future<> check_access(const service::client_state&) override;

View File

@@ -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));
}

View File

@@ -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<service::storage_proxy>&, const service::client_state&) override;
future<> check_access(const service::client_state&) override;

View File

@@ -235,12 +235,12 @@ future<::shared_ptr<cql_transport::messages::result_message>>
list_roles_statement::execute(distributed<service::storage_proxy>&, 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<const abstract_type>& ty) {
return ::make_shared<column_specification>(
virtual_table.keyspace(),
virtual_table.column_family(),
auth::meta::AUTH_KS,
virtual_table_name,
::make_shared<column_identifier>(name, true),
ty);
};

View File

@@ -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<auth::data_resource> readable_system_resources = [] {
std::set<auth::data_resource> tmp;
static thread_local std::set<auth::resource> readable_system_resources = [] {
std::set<auth::resource> 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<bool> service::client_state::check_has_permission(auth::permission p, auth::data_resource resource) const {
future<bool> service::client_state::check_has_permission(auth::permission p, auth::resource resource) const {
if (_is_internal) {
return make_ready_future<bool>(true);
}
std::experimental::optional<auth::data_resource> parent;
if (resource.has_parent()) {
parent = resource.get_parent();
}
std::experimental::optional<auth::resource> 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<bool> 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",

View File

@@ -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<bool> check_has_permission(auth::permission, auth::data_resource) const;
future<> has_access(const sstring&, auth::permission, auth::resource) const;
future<bool> 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

View File

@@ -95,6 +95,7 @@ boost_tests = [
'aggregate_fcts_test',
'role_manager_test',
'caching_options_test',
'auth_resource_test',
]
other_tests = [

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
#define BOOST_TEST_MODULE core
#include "auth/resource.hh"
#include <sstream>
#include <boost/test/unit_test.hpp>
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)), "<all keyspaces>");
BOOST_REQUIRE_EQUAL(sprint("%s", auth::resource::data("my_keyspace")), "<keyspace my_keyspace>");
BOOST_REQUIRE_EQUAL(sprint("%s", auth::resource::data("my_keyspace", "my_table")), "<table my_keyspace.my_table>");
}

View File

@@ -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();