mirror of
https://github.com/ClickHouse/ClickHouse.git
synced 2024-11-21 15:12:02 +00:00
Merge pull request #62669 from slvrtrn/http-interface-role-query-param
Add `role` query parameter to the HTTP interface
This commit is contained in:
commit
d12608f366
@ -79,6 +79,11 @@ namespace Net
|
||||
/// Returns the value of the first name-value pair with the given name.
|
||||
/// If no value with the given name has been found, the defaultValue is returned.
|
||||
|
||||
const std::vector<std::reference_wrapper<const std::string>> getAll(const std::string & name) const;
|
||||
/// Returns all values of all name-value pairs with the given name.
|
||||
///
|
||||
/// Returns an empty vector if there are no name-value pairs with the given name.
|
||||
|
||||
bool has(const std::string & name) const;
|
||||
/// Returns true if there is at least one name-value pair
|
||||
/// with the given name.
|
||||
|
@ -15,6 +15,7 @@
|
||||
#include "Poco/Net/NameValueCollection.h"
|
||||
#include "Poco/Exception.h"
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
|
||||
|
||||
using Poco::NotFoundException;
|
||||
@ -55,7 +56,7 @@ void NameValueCollection::swap(NameValueCollection& nvc)
|
||||
std::swap(_map, nvc._map);
|
||||
}
|
||||
|
||||
|
||||
|
||||
const std::string& NameValueCollection::operator [] (const std::string& name) const
|
||||
{
|
||||
ConstIterator it = _map.find(name);
|
||||
@ -65,8 +66,8 @@ const std::string& NameValueCollection::operator [] (const std::string& name) co
|
||||
throw NotFoundException(name);
|
||||
}
|
||||
|
||||
|
||||
void NameValueCollection::set(const std::string& name, const std::string& value)
|
||||
|
||||
void NameValueCollection::set(const std::string& name, const std::string& value)
|
||||
{
|
||||
Iterator it = _map.find(name);
|
||||
if (it != _map.end())
|
||||
@ -75,13 +76,13 @@ void NameValueCollection::set(const std::string& name, const std::string& value)
|
||||
_map.insert(HeaderMap::ValueType(name, value));
|
||||
}
|
||||
|
||||
|
||||
|
||||
void NameValueCollection::add(const std::string& name, const std::string& value)
|
||||
{
|
||||
_map.insert(HeaderMap::ValueType(name, value));
|
||||
}
|
||||
|
||||
|
||||
|
||||
const std::string& NameValueCollection::get(const std::string& name) const
|
||||
{
|
||||
ConstIterator it = _map.find(name);
|
||||
@ -101,6 +102,15 @@ const std::string& NameValueCollection::get(const std::string& name, const std::
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
const std::vector<std::reference_wrapper<const std::string>> NameValueCollection::getAll(const std::string& name) const
|
||||
{
|
||||
std::vector<std::reference_wrapper<const std::string>> values;
|
||||
for (ConstIterator it = _map.find(name); it != _map.end(); it++)
|
||||
if (it->first == name)
|
||||
values.push_back(it->second);
|
||||
return values;
|
||||
}
|
||||
|
||||
|
||||
bool NameValueCollection::has(const std::string& name) const
|
||||
{
|
||||
@ -113,19 +123,19 @@ NameValueCollection::ConstIterator NameValueCollection::find(const std::string&
|
||||
return _map.find(name);
|
||||
}
|
||||
|
||||
|
||||
|
||||
NameValueCollection::ConstIterator NameValueCollection::begin() const
|
||||
{
|
||||
return _map.begin();
|
||||
}
|
||||
|
||||
|
||||
|
||||
NameValueCollection::ConstIterator NameValueCollection::end() const
|
||||
{
|
||||
return _map.end();
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool NameValueCollection::empty() const
|
||||
{
|
||||
return _map.empty();
|
||||
|
@ -325,6 +325,37 @@ $ curl -sS 'http://localhost:8123/?max_result_bytes=4000000&buffer_size=3000000&
|
||||
|
||||
Use buffering to avoid situations where a query processing error occurred after the response code and HTTP headers were sent to the client. In this situation, an error message is written at the end of the response body, and on the client-side, the error can only be detected at the parsing stage.
|
||||
|
||||
## Setting a role with query parameters {#setting-role-with-query-parameters}
|
||||
|
||||
In certain scenarios, it might be required to set the granted role first, before executing the statement itself.
|
||||
However, it is not possible to send `SET ROLE` and the statement together, as multi-statements are not allowed:
|
||||
|
||||
```
|
||||
curl -sS "http://localhost:8123" --data-binary "SET ROLE my_role;SELECT * FROM my_table;"
|
||||
```
|
||||
|
||||
Which will result in an error:
|
||||
|
||||
```
|
||||
Code: 62. DB::Exception: Syntax error (Multi-statements are not allowed)
|
||||
```
|
||||
|
||||
To overcome this limitation, you could use the `role` query parameter instead:
|
||||
|
||||
```
|
||||
curl -sS "http://localhost:8123?role=my_role" --data-binary "SELECT * FROM my_table;"
|
||||
```
|
||||
|
||||
This will be an equivalent of executing `SET ROLE my_role` before the statement.
|
||||
|
||||
Additionally, it is possible to specify multiple `role` query parameters:
|
||||
|
||||
```
|
||||
curl -sS "http://localhost:8123?role=my_role&role=my_other_role" --data-binary "SELECT * FROM my_table;"
|
||||
```
|
||||
|
||||
In this case, `?role=my_role&role=my_other_role` works similarly to executing `SET ROLE my_role, my_other_role` before the statement.
|
||||
|
||||
## HTTP response codes caveats {#http_response_codes_caveats}
|
||||
|
||||
Because of limitation of HTTP protocol, HTTP 200 response code does not guarantee that a query was successful.
|
||||
|
@ -2,7 +2,10 @@
|
||||
|
||||
#include <Access/Authentication.h>
|
||||
#include <Access/Credentials.h>
|
||||
#include <Access/AccessControl.h>
|
||||
#include <Access/ExternalAuthenticators.h>
|
||||
#include <Access/Role.h>
|
||||
#include <Access/User.h>
|
||||
#include <Compression/CompressedReadBuffer.h>
|
||||
#include <Compression/CompressedWriteBuffer.h>
|
||||
#include <Core/ExternalTable.h>
|
||||
@ -104,6 +107,7 @@ namespace ErrorCodes
|
||||
extern const int UNKNOWN_FORMAT;
|
||||
extern const int UNKNOWN_DATABASE_ENGINE;
|
||||
extern const int UNKNOWN_TYPE_OF_QUERY;
|
||||
extern const int UNKNOWN_ROLE;
|
||||
extern const int NO_ELEMENTS_IN_CONFIG;
|
||||
|
||||
extern const int QUERY_IS_TOO_LARGE;
|
||||
@ -115,6 +119,7 @@ namespace ErrorCodes
|
||||
extern const int WRONG_PASSWORD;
|
||||
extern const int REQUIRED_PASSWORD;
|
||||
extern const int AUTHENTICATION_FAILED;
|
||||
extern const int SET_NON_GRANTED_ROLE;
|
||||
|
||||
extern const int INVALID_SESSION_TIMEOUT;
|
||||
extern const int HTTP_LENGTH_REQUIRED;
|
||||
@ -140,7 +145,7 @@ bool tryAddHTTPOptionHeadersFromConfig(HTTPServerResponse & response, const Poco
|
||||
LOG_WARNING(getLogger("processOptionsRequest"), "Empty header was found in config. It will not be processed.");
|
||||
else
|
||||
response.add(config.getString("http_options_response." + config_key + ".name", ""),
|
||||
config.getString("http_options_response." + config_key + ".value", ""));
|
||||
config.getString("http_options_response." + config_key + ".value", ""));
|
||||
|
||||
}
|
||||
}
|
||||
@ -192,7 +197,8 @@ static Poco::Net::HTTPResponse::HTTPStatus exceptionCodeToHTTPStatus(int excepti
|
||||
}
|
||||
else if (exception_code == ErrorCodes::UNKNOWN_USER ||
|
||||
exception_code == ErrorCodes::WRONG_PASSWORD ||
|
||||
exception_code == ErrorCodes::AUTHENTICATION_FAILED)
|
||||
exception_code == ErrorCodes::AUTHENTICATION_FAILED ||
|
||||
exception_code == ErrorCodes::SET_NON_GRANTED_ROLE)
|
||||
{
|
||||
return HTTPResponse::HTTP_FORBIDDEN;
|
||||
}
|
||||
@ -235,7 +241,8 @@ static Poco::Net::HTTPResponse::HTTPStatus exceptionCodeToHTTPStatus(int excepti
|
||||
exception_code == ErrorCodes::UNKNOWN_AGGREGATE_FUNCTION ||
|
||||
exception_code == ErrorCodes::UNKNOWN_FORMAT ||
|
||||
exception_code == ErrorCodes::UNKNOWN_DATABASE_ENGINE ||
|
||||
exception_code == ErrorCodes::UNKNOWN_TYPE_OF_QUERY)
|
||||
exception_code == ErrorCodes::UNKNOWN_TYPE_OF_QUERY ||
|
||||
exception_code == ErrorCodes::UNKNOWN_ROLE)
|
||||
{
|
||||
return HTTPResponse::HTTP_NOT_FOUND;
|
||||
}
|
||||
@ -704,7 +711,7 @@ void HTTPHandler::processQuery(
|
||||
|
||||
std::unique_ptr<ReadBuffer> in;
|
||||
|
||||
static const NameSet reserved_param_names{"compress", "decompress", "user", "password", "quota_key", "query_id", "stacktrace",
|
||||
static const NameSet reserved_param_names{"compress", "decompress", "user", "password", "quota_key", "query_id", "stacktrace", "role",
|
||||
"buffer_size", "wait_end_of_query", "session_id", "session_timeout", "session_check", "client_protocol_version", "close_session"};
|
||||
|
||||
Names reserved_param_suffixes;
|
||||
@ -727,6 +734,23 @@ void HTTPHandler::processQuery(
|
||||
return false;
|
||||
};
|
||||
|
||||
auto roles = params.getAll("role");
|
||||
if (!roles.empty())
|
||||
{
|
||||
const auto & access_control = context->getAccessControl();
|
||||
const auto & user = context->getUser();
|
||||
std::vector<UUID> roles_ids(roles.size());
|
||||
for (size_t i = 0; i < roles.size(); i++)
|
||||
{
|
||||
auto role_id = access_control.getID<Role>(roles[i]);
|
||||
if (user->granted_roles.isGranted(role_id))
|
||||
roles_ids[i] = role_id;
|
||||
else
|
||||
throw Exception(ErrorCodes::SET_NON_GRANTED_ROLE, "Role {} should be granted to set as a current", roles[i].get());
|
||||
}
|
||||
context->setCurrentRoles(roles_ids);
|
||||
}
|
||||
|
||||
/// Settings can be overridden in the query.
|
||||
/// Some parameters (database, default_format, everything used in the code above) do not
|
||||
/// belong to the Settings class.
|
||||
|
@ -0,0 +1,39 @@
|
||||
### Shows the default role when there are no role parameters
|
||||
03096_role_query_param_role_enabled_by_default
|
||||
### Shows a single role from the query parameters
|
||||
03096_role_query_param_role1
|
||||
### Shows multiple roles from the query parameters
|
||||
03096_role_query_param_role1
|
||||
03096_role_query_param_role2
|
||||
### Sets the default role alongside with another granted one
|
||||
03096_role_query_param_role1
|
||||
03096_role_query_param_role_enabled_by_default
|
||||
### Sets a role with special characters in the name
|
||||
03096_role_query_param_@!\\$
|
||||
### Sets a role with special characters in the name with another granted role
|
||||
03096_role_query_param_@!\\$
|
||||
03096_role_query_param_role1
|
||||
### Sets a role once when it's present in the query parameters multiple times
|
||||
03096_role_query_param_role1
|
||||
### Sets a role when there are other parameters in the query (before the role parameter)
|
||||
03096_role_query_param_role1
|
||||
max_result_rows 42
|
||||
### Sets a role when there are other parameters in the query (after the role parameter)
|
||||
03096_role_query_param_role1
|
||||
max_result_rows 42
|
||||
### Sets multiple roles when there are other parameters in the query
|
||||
03096_role_query_param_role1
|
||||
03096_role_query_param_role2
|
||||
max_result_rows 42
|
||||
### Cannot set a role that does not exist (single parameter)
|
||||
Code: 511
|
||||
UNKNOWN_ROLE
|
||||
### Cannot set a role that does not exist (multiple parameters)
|
||||
Code: 511
|
||||
UNKNOWN_ROLE
|
||||
### Cannot set a role that is not granted to the user (single parameter)
|
||||
Code: 512
|
||||
SET_NON_GRANTED_ROLE
|
||||
### Cannot set a role that is not granted to the user (multiple parameters)
|
||||
Code: 512
|
||||
SET_NON_GRANTED_ROLE
|
104
tests/queries/0_stateless/03096_http_interface_role_query_param.sh
Executable file
104
tests/queries/0_stateless/03096_http_interface_role_query_param.sh
Executable file
@ -0,0 +1,104 @@
|
||||
#!/usr/bin/env bash
|
||||
# Tags: no-parallel
|
||||
|
||||
CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||
# shellcheck source=../shell_config.sh
|
||||
. "$CUR_DIR"/../shell_config.sh
|
||||
|
||||
TEST_USER="03096_role_query_param_user"
|
||||
TEST_USER_AUTH="$TEST_USER:"
|
||||
|
||||
TEST_ROLE1="03096_role_query_param_role1"
|
||||
TEST_ROLE2="03096_role_query_param_role2"
|
||||
TEST_ROLE_ENABLED_BY_DEFAULT="03096_role_query_param_role_enabled_by_default"
|
||||
TEST_ROLE_NOT_GRANTED="03096_role_query_param_role_not_granted"
|
||||
TEST_ROLE_SPECIAL_CHARS="\`03096_role_query_param_@!\\$\`" # = CREATE ROLE `03096_role_query_param_@!\$`
|
||||
TEST_ROLE_SPECIAL_CHARS_URLENCODED="03096_role_query_param_%40!%5C%24"
|
||||
|
||||
CHANGED_SETTING_NAME="max_result_rows"
|
||||
CHANGED_SETTING_VALUE="42"
|
||||
|
||||
SHOW_CURRENT_ROLES_QUERY="SELECT role_name FROM system.current_roles ORDER BY role_name ASC"
|
||||
SHOW_CHANGED_SETTINGS_QUERY="SELECT name, value FROM system.settings WHERE changed = 1 AND name = '$CHANGED_SETTING_NAME' ORDER BY name ASC"
|
||||
|
||||
$CLICKHOUSE_CLIENT -n --query "
|
||||
DROP USER IF EXISTS $TEST_USER;
|
||||
DROP ROLE IF EXISTS $TEST_ROLE1;
|
||||
DROP ROLE IF EXISTS $TEST_ROLE2;
|
||||
DROP ROLE IF EXISTS $TEST_ROLE_ENABLED_BY_DEFAULT;
|
||||
DROP ROLE IF EXISTS $TEST_ROLE_NOT_GRANTED;
|
||||
DROP ROLE IF EXISTS $TEST_ROLE_SPECIAL_CHARS;
|
||||
CREATE USER $TEST_USER NOT IDENTIFIED;
|
||||
CREATE ROLE $TEST_ROLE_ENABLED_BY_DEFAULT;
|
||||
GRANT $TEST_ROLE_ENABLED_BY_DEFAULT TO $TEST_USER;
|
||||
SET DEFAULT ROLE $TEST_ROLE_ENABLED_BY_DEFAULT TO $TEST_USER;
|
||||
CREATE ROLE $TEST_ROLE1;
|
||||
GRANT $TEST_ROLE1 TO $TEST_USER;
|
||||
CREATE ROLE $TEST_ROLE2;
|
||||
GRANT $TEST_ROLE2 TO $TEST_USER;
|
||||
CREATE ROLE $TEST_ROLE_SPECIAL_CHARS;
|
||||
GRANT $TEST_ROLE_SPECIAL_CHARS TO $TEST_USER;
|
||||
CREATE ROLE $TEST_ROLE_NOT_GRANTED;
|
||||
"
|
||||
|
||||
echo "### Shows the default role when there are no role parameters"
|
||||
$CLICKHOUSE_CURL -u $TEST_USER_AUTH -sS "$CLICKHOUSE_URL" --data-binary "$SHOW_CURRENT_ROLES_QUERY"
|
||||
|
||||
echo "### Shows a single role from the query parameters"
|
||||
$CLICKHOUSE_CURL -u $TEST_USER_AUTH -sS "$CLICKHOUSE_URL&role=$TEST_ROLE1" --data-binary "$SHOW_CURRENT_ROLES_QUERY"
|
||||
|
||||
echo "### Shows multiple roles from the query parameters"
|
||||
$CLICKHOUSE_CURL -u $TEST_USER_AUTH -sS "$CLICKHOUSE_URL&role=$TEST_ROLE1&role=$TEST_ROLE2" --data-binary "$SHOW_CURRENT_ROLES_QUERY"
|
||||
|
||||
echo "### Sets the default role alongside with another granted one"
|
||||
$CLICKHOUSE_CURL -u $TEST_USER_AUTH -sS "$CLICKHOUSE_URL&role=$TEST_ROLE_ENABLED_BY_DEFAULT&role=$TEST_ROLE1" --data-binary "$SHOW_CURRENT_ROLES_QUERY"
|
||||
|
||||
echo "### Sets a role with special characters in the name"
|
||||
$CLICKHOUSE_CURL -u $TEST_USER_AUTH -sS "$CLICKHOUSE_URL&role=$TEST_ROLE_SPECIAL_CHARS_URLENCODED" --data-binary "$SHOW_CURRENT_ROLES_QUERY"
|
||||
|
||||
echo "### Sets a role with special characters in the name with another granted role"
|
||||
$CLICKHOUSE_CURL -u $TEST_USER_AUTH -sS "$CLICKHOUSE_URL&role=$TEST_ROLE_SPECIAL_CHARS_URLENCODED&role=$TEST_ROLE1" --data-binary "$SHOW_CURRENT_ROLES_QUERY"
|
||||
|
||||
echo "### Sets a role once when it's present in the query parameters multiple times"
|
||||
$CLICKHOUSE_CURL -u $TEST_USER_AUTH -sS "$CLICKHOUSE_URL&role=$TEST_ROLE1&role=$TEST_ROLE1" --data-binary "$SHOW_CURRENT_ROLES_QUERY"
|
||||
|
||||
echo "### Sets a role when there are other parameters in the query (before the role parameter)"
|
||||
$CLICKHOUSE_CURL -u $TEST_USER_AUTH -sS "$CLICKHOUSE_URL&$CHANGED_SETTING_NAME=$CHANGED_SETTING_VALUE&role=$TEST_ROLE1" --data-binary "$SHOW_CURRENT_ROLES_QUERY"
|
||||
$CLICKHOUSE_CURL -u $TEST_USER_AUTH -sS "$CLICKHOUSE_URL&$CHANGED_SETTING_NAME=$CHANGED_SETTING_VALUE&role=$TEST_ROLE1" --data-binary "$SHOW_CHANGED_SETTINGS_QUERY"
|
||||
|
||||
echo "### Sets a role when there are other parameters in the query (after the role parameter)"
|
||||
$CLICKHOUSE_CURL -u $TEST_USER_AUTH -sS "$CLICKHOUSE_URL&role=$TEST_ROLE1&$CHANGED_SETTING_NAME=$CHANGED_SETTING_VALUE" --data-binary "$SHOW_CURRENT_ROLES_QUERY"
|
||||
$CLICKHOUSE_CURL -u $TEST_USER_AUTH -sS "$CLICKHOUSE_URL&role=$TEST_ROLE1&$CHANGED_SETTING_NAME=$CHANGED_SETTING_VALUE" --data-binary "$SHOW_CHANGED_SETTINGS_QUERY"
|
||||
|
||||
echo "### Sets multiple roles when there are other parameters in the query"
|
||||
$CLICKHOUSE_CURL -u $TEST_USER_AUTH -sS "$CLICKHOUSE_URL&role=$TEST_ROLE1&$CHANGED_SETTING_NAME=$CHANGED_SETTING_VALUE&role=$TEST_ROLE2" --data-binary "$SHOW_CURRENT_ROLES_QUERY"
|
||||
$CLICKHOUSE_CURL -u $TEST_USER_AUTH -sS "$CLICKHOUSE_URL&role=$TEST_ROLE1&$CHANGED_SETTING_NAME=$CHANGED_SETTING_VALUE&role=$TEST_ROLE2" --data-binary "$SHOW_CHANGED_SETTINGS_QUERY"
|
||||
|
||||
echo "### Cannot set a role that does not exist (single parameter)"
|
||||
OUT=$($CLICKHOUSE_CURL -u $TEST_USER_AUTH -sS "$CLICKHOUSE_URL&role=aaaaaaaaaaa" --data-binary "$SHOW_CURRENT_ROLES_QUERY")
|
||||
echo -ne $OUT | grep -o "Code: 511" || echo "expected code 511, got: $OUT"
|
||||
echo -ne $OUT | grep -o "UNKNOWN_ROLE" || echo "expected UNKNOWN_ROLE error, got: $OUT"
|
||||
|
||||
echo "### Cannot set a role that does not exist (multiple parameters)"
|
||||
OUT=$($CLICKHOUSE_CURL -u $TEST_USER_AUTH -sS "$CLICKHOUSE_URL&role=$TEST_ROLE1&role=aaaaaaaaaaa" --data-binary "$SHOW_CURRENT_ROLES_QUERY")
|
||||
echo -ne $OUT | grep -o "Code: 511" || echo "expected code 511, got: $OUT"
|
||||
echo -ne $OUT | grep -o "UNKNOWN_ROLE" || echo "expected UNKNOWN_ROLE error, got: $OUT"
|
||||
|
||||
echo "### Cannot set a role that is not granted to the user (single parameter)"
|
||||
OUT=$($CLICKHOUSE_CURL -u $TEST_USER_AUTH -sS "$CLICKHOUSE_URL&role=$TEST_ROLE_NOT_GRANTED" --data-binary "$SHOW_CURRENT_ROLES_QUERY")
|
||||
echo -ne $OUT | grep -o "Code: 512" || echo "expected code 512, got: $OUT"
|
||||
echo -ne $OUT | grep -o "SET_NON_GRANTED_ROLE" || echo "expected SET_NON_GRANTED_ROLE error, got: $OUT"
|
||||
|
||||
echo "### Cannot set a role that is not granted to the user (multiple parameters)"
|
||||
OUT=$($CLICKHOUSE_CURL -u $TEST_USER_AUTH -sS "$CLICKHOUSE_URL&role=$TEST_ROLE1&role=$TEST_ROLE_NOT_GRANTED" --data-binary "$SHOW_CURRENT_ROLES_QUERY")
|
||||
echo -ne $OUT | grep -o "Code: 512" || echo "expected code 512, got: $OUT"
|
||||
echo -ne $OUT | grep -o "SET_NON_GRANTED_ROLE" || echo "expected SET_NON_GRANTED_ROLE error, got: $OUT"
|
||||
|
||||
$CLICKHOUSE_CLIENT -n --query "
|
||||
DROP USER $TEST_USER;
|
||||
DROP ROLE $TEST_ROLE1;
|
||||
DROP ROLE $TEST_ROLE2;
|
||||
DROP ROLE $TEST_ROLE_ENABLED_BY_DEFAULT;
|
||||
DROP ROLE $TEST_ROLE_NOT_GRANTED;
|
||||
DROP ROLE $TEST_ROLE_SPECIAL_CHARS;
|
||||
"
|
Loading…
Reference in New Issue
Block a user