From b5997c6797e1ee0e7d5a589df7e1d71a77c83b7f Mon Sep 17 00:00:00 2001
From: Han <42765252+Detachm@users.noreply.github.com>
Date: Fri, 8 May 2026 16:56:22 +0800
Subject: [PATCH] test: cover standalone user provider config (#8067)
* test: cover standalone user provider config
Signed-off-by: Detachm <42765252+Detachm@users.noreply.github.com>
* test: cover config-driven http auth
Signed-off-by: Detachm <42765252+Detachm@users.noreply.github.com>
---------
Signed-off-by: Detachm <42765252+Detachm@users.noreply.github.com>
---
Cargo.lock | 2 +
config/config.md | 3 +
config/flownode.example.toml | 5 ++
config/frontend.example.toml | 5 ++
config/standalone.example.toml | 5 ++
src/cmd/tests/load_config_test.rs | 26 +++++++
src/servers/src/http/authorize.rs | 37 +++++++---
src/servers/tests/http/authorize.rs | 25 ++++---
src/servers/tests/http/influxdb_test.rs | 10 ++-
tests-integration/Cargo.toml | 2 +
tests-integration/tests/grpc.rs | 14 +++-
tests-integration/tests/http.rs | 91 ++++++++++++++++++++-----
tests-integration/tests/main.rs | 5 ++
13 files changed, 188 insertions(+), 42 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index 5ee58dca31..ef976ee23c 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -14081,6 +14081,7 @@ dependencies = [
"async-trait",
"auth",
"axum 0.8.4",
+ "base64 0.22.1",
"cache",
"catalog",
"chrono",
@@ -14135,6 +14136,7 @@ dependencies = [
"partition",
"paste",
"pipeline",
+ "plugins",
"prost 0.14.1",
"query",
"rand 0.9.1",
diff --git a/config/config.md b/config/config.md
index 01cf9e75fa..0e395dc445 100644
--- a/config/config.md
+++ b/config/config.md
@@ -14,6 +14,7 @@
| --- | -----| ------- | ----------- |
| `default_timezone` | String | Unset | The default timezone of the server. |
| `default_column_prefix` | String | Unset | The default column prefix for auto-created time index and value columns. |
+| `user_provider` | String | Unset | The user provider for authentication.
Examples: "static_user_provider:file:/path/to/users", "static_user_provider:cmd:greptime_user=greptime_pwd" |
| `max_in_flight_write_bytes` | String | Unset | Maximum total memory for all concurrent write request bodies and messages (HTTP, gRPC, Flight).
Set to 0 to disable the limit. Default: "0" (unlimited) |
| `write_bytes_exhausted_policy` | String | Unset | Policy when write bytes quota is exhausted.
Options: "wait" (default, 10s timeout), "wait()" (e.g., "wait(30s)"), "fail" |
| `init_regions_in_background` | Bool | `false` | Initialize all regions in the background during the startup.
By default, it provides services after all regions have been initialized. |
@@ -231,6 +232,7 @@
| --- | -----| ------- | ----------- |
| `default_timezone` | String | Unset | The default timezone of the server. |
| `default_column_prefix` | String | Unset | The default column prefix for auto-created time index and value columns. |
+| `user_provider` | String | Unset | The user provider for authentication.
Examples: "static_user_provider:file:/path/to/users", "static_user_provider:cmd:greptime_user=greptime_pwd" |
| `max_in_flight_write_bytes` | String | Unset | Maximum total memory for all concurrent write request bodies and messages (HTTP, gRPC, Flight).
Set to 0 to disable the limit. Default: "0" (unlimited) |
| `write_bytes_exhausted_policy` | String | Unset | Policy when write bytes quota is exhausted.
Options: "wait" (default, 10s timeout), "wait()" (e.g., "wait(30s)"), "fail" |
| `runtime` | -- | -- | The runtime options. |
@@ -624,6 +626,7 @@
| Key | Type | Default | Descriptions |
| --- | -----| ------- | ----------- |
| `node_id` | Integer | Unset | The flownode identifier and should be unique in the cluster. |
+| `user_provider` | String | Unset | The user provider for authentication.
Examples: "static_user_provider:file:/path/to/users", "static_user_provider:cmd:greptime_user=greptime_pwd" |
| `flow` | -- | -- | flow engine options. |
| `flow.num_workers` | Integer | `0` | The number of flow worker in flownode.
Not setting(or set to 0) this value will use the number of CPU cores divided by 2. |
| `flow.batching_mode` | -- | -- | -- |
diff --git a/config/flownode.example.toml b/config/flownode.example.toml
index 5519de1b89..8a819d562e 100644
--- a/config/flownode.example.toml
+++ b/config/flownode.example.toml
@@ -2,6 +2,11 @@
## @toml2docs:none-default
node_id = 14
+## The user provider for authentication.
+## Examples: "static_user_provider:file:/path/to/users", "static_user_provider:cmd:greptime_user=greptime_pwd"
+## @toml2docs:none-default
+#+ user_provider = "static_user_provider:file:/path/to/users"
+
## flow engine options.
[flow]
## The number of flow worker in flownode.
diff --git a/config/frontend.example.toml b/config/frontend.example.toml
index 7a8ff8294e..dfa0990cca 100644
--- a/config/frontend.example.toml
+++ b/config/frontend.example.toml
@@ -6,6 +6,11 @@ default_timezone = "UTC"
## @toml2docs:none-default
default_column_prefix = "greptime"
+## The user provider for authentication.
+## Examples: "static_user_provider:file:/path/to/users", "static_user_provider:cmd:greptime_user=greptime_pwd"
+## @toml2docs:none-default
+#+ user_provider = "static_user_provider:file:/path/to/users"
+
## Maximum total memory for all concurrent write request bodies and messages (HTTP, gRPC, Flight).
## Set to 0 to disable the limit. Default: "0" (unlimited)
## @toml2docs:none-default
diff --git a/config/standalone.example.toml b/config/standalone.example.toml
index d14bbe63d5..aa745bb6ba 100644
--- a/config/standalone.example.toml
+++ b/config/standalone.example.toml
@@ -6,6 +6,11 @@ default_timezone = "UTC"
## @toml2docs:none-default
default_column_prefix = "greptime"
+## The user provider for authentication.
+## Examples: "static_user_provider:file:/path/to/users", "static_user_provider:cmd:greptime_user=greptime_pwd"
+## @toml2docs:none-default
+#+ user_provider = "static_user_provider:file:/path/to/users"
+
## Maximum total memory for all concurrent write request bodies and messages (HTTP, gRPC, Flight).
## Set to 0 to disable the limit. Default: "0" (unlimited)
## @toml2docs:none-default
diff --git a/src/cmd/tests/load_config_test.rs b/src/cmd/tests/load_config_test.rs
index 8478c08b53..6cffcd67c2 100644
--- a/src/cmd/tests/load_config_test.rs
+++ b/src/cmd/tests/load_config_test.rs
@@ -312,6 +312,32 @@ fn test_load_standalone_example_config() {
similar_asserts::assert_eq!(options, expected);
}
+#[test]
+fn test_load_standalone_user_provider_from_config() {
+ let config = tempfile::NamedTempFile::new().unwrap();
+ let user_provider = "static_user_provider:file:/tmp/greptimedb-users";
+ std::fs::write(
+ config.path(),
+ format!("user_provider = \"{user_provider}\"\n"),
+ )
+ .unwrap();
+
+ let options =
+ GreptimeOptions::::load_layered_options(config.path().to_str(), "")
+ .unwrap();
+
+ assert_eq!(
+ options.component.user_provider.as_deref(),
+ Some(user_provider)
+ );
+
+ let frontend_options = options.component.frontend_options();
+ assert_eq!(
+ frontend_options.user_provider.as_deref(),
+ Some(user_provider)
+ );
+}
+
#[test]
fn test_load_heartbeat_env_vars_from_env() {
let env_prefix = "HEARTBEAT_ENV_VARS_UT";
diff --git a/src/servers/src/http/authorize.rs b/src/servers/src/http/authorize.rs
index ef3a74b38e..226f824acb 100644
--- a/src/servers/src/http/authorize.rs
+++ b/src/servers/src/http/authorize.rs
@@ -362,14 +362,13 @@ mod tests {
#[test]
fn test_decode_basic() {
- // base64encode("username:password") == "dXNlcm5hbWU6cGFzc3dvcmQ="
- let credential = "dXNlcm5hbWU6cGFzc3dvcmQ=";
- let (username, pwd) = decode_basic(credential).unwrap();
+ let credential = basic_auth_credentials("username", "password");
+ let (username, pwd) = decode_basic(&credential).unwrap();
assert_eq!("username", username);
assert_eq!("password", pwd.expose_secret());
- let wrong_credential = "dXNlcm5hbWU6cG Fzc3dvcmQ=";
- let result = decode_basic(wrong_credential);
+ let wrong_credential = credential.replacen('c', "c ", 1);
+ let result = decode_basic(&wrong_credential);
assert_matches!(result.err(), Some(error::Error::InvalidBase64Value { .. }));
}
@@ -379,8 +378,8 @@ mod tests {
let re: Result = auth_scheme_str.try_into();
assert!(re.is_err());
- let auth_scheme_str = "basic dGVzdDp0ZXN0";
- let scheme: AuthScheme = auth_scheme_str.try_into().unwrap();
+ let auth_scheme_str = basic_auth("test", "test");
+ let scheme: AuthScheme = auth_scheme_str.as_str().try_into().unwrap();
assert_matches!(scheme, AuthScheme::Basic(username, pwd) if username == "test" && pwd.expose_secret() == "test");
let unsupported = "digest";
@@ -390,21 +389,37 @@ mod tests {
#[test]
fn test_auth_header() {
- // base64encode("username:password") == "dXNlcm5hbWU6cGFzc3dvcmQ="
- let req = mock_http_request(Some("Basic dXNlcm5hbWU6cGFzc3dvcmQ="), None).unwrap();
+ let header_value = basic_auth("username", "password");
+ let req = mock_http_request(Some(&header_value), None).unwrap();
let auth_scheme = auth_header(&req).unwrap();
assert_matches!(auth_scheme, AuthScheme::Basic(username, pwd) if username == "username" && pwd.expose_secret() == "password");
- let wrong_req = mock_http_request(Some("Basic dXNlcm5hbWU6 cGFzc3dvcmQ="), None).unwrap();
+ let wrong_auth_header = header_value.replacen('c', "c ", 1);
+ let wrong_req = mock_http_request(Some(&wrong_auth_header), None).unwrap();
let res = auth_header(&wrong_req);
assert_matches!(res.err(), Some(error::Error::InvalidAuthHeader { .. }));
- let wrong_req = mock_http_request(Some("Digest dXNlcm5hbWU6cGFzc3dvcmQ="), None).unwrap();
+ let wrong_req = mock_http_request(
+ Some(&format!(
+ "Digest {}",
+ basic_auth_credentials("username", "password")
+ )),
+ None,
+ )
+ .unwrap();
let res = auth_header(&wrong_req);
assert_matches!(res.err(), Some(error::Error::UnsupportedAuthScheme { .. }));
}
+ fn basic_auth(username: &str, password: &str) -> String {
+ format!("Basic {}", basic_auth_credentials(username, password))
+ }
+
+ fn basic_auth_credentials(username: &str, password: &str) -> String {
+ BASE64_STANDARD.encode(format!("{username}:{password}"))
+ }
+
fn mock_http_request(auth_header: Option<&str>, uri: Option<&str>) -> Result> {
let http_api_version = crate::http::HTTP_API_VERSION;
let mut req = Request::builder()
diff --git a/src/servers/tests/http/authorize.rs b/src/servers/tests/http/authorize.rs
index 214ab7d9f9..be54d47e78 100644
--- a/src/servers/tests/http/authorize.rs
+++ b/src/servers/tests/http/authorize.rs
@@ -17,14 +17,15 @@ use std::sync::Arc;
use auth::UserProvider;
use auth::tests::MockUserProvider;
use axum::http;
+use base64::prelude::{BASE64_STANDARD, Engine as _};
use hyper::{Request, StatusCode};
use servers::http::AUTHORIZATION_HEADER;
use servers::http::authorize::inner_auth;
use session::context::QueryContext;
async fn check_http_auth(header_key: &str) {
- // base64encode("username:password") == "dXNlcm5hbWU6cGFzc3dvcmQ="
- let req = mock_http_request(header_key, Some("Basic dXNlcm5hbWU6cGFzc3dvcmQ="), None).unwrap();
+ let req =
+ mock_http_request(header_key, Some(&basic_auth("username", "password")), None).unwrap();
let req = inner_auth(None, req).await.unwrap();
let ctx: &QueryContext = req.extensions().get().unwrap();
let user_info = ctx.current_user();
@@ -34,8 +35,8 @@ async fn check_http_auth(header_key: &str) {
// In mock user provider, right username:password == "greptime:greptime"
let mock_user_provider = Some(Arc::new(MockUserProvider::default()) as Arc);
- // base64encode("greptime:greptime") == "Z3JlcHRpbWU6Z3JlcHRpbWU="
- let req = mock_http_request(header_key, Some("Basic Z3JlcHRpbWU6Z3JlcHRpbWU="), None).unwrap();
+ let req =
+ mock_http_request(header_key, Some(&basic_auth("greptime", "greptime")), None).unwrap();
let req = inner_auth(mock_user_provider.clone(), req).await.unwrap();
let ctx: &QueryContext = req.extensions().get().unwrap();
let user_info = ctx.current_user();
@@ -52,9 +53,8 @@ async fn check_http_auth(header_key: &str) {
axum::body::to_bytes(resp.into_body(), usize::MAX).await.unwrap().as_ref()
);
- // base64encode("username:password") == "dXNlcm5hbWU6cGFzc3dvcmQ="
let wrong_req =
- mock_http_request(header_key, Some("Basic dXNlcm5hbWU6cGFzc3dvcmQ="), None).unwrap();
+ mock_http_request(header_key, Some(&basic_auth("username", "password")), None).unwrap();
let auth_res = inner_auth(mock_user_provider, wrong_req).await;
assert!(auth_res.is_err());
let resp = auth_res.unwrap_err();
@@ -78,12 +78,11 @@ async fn check_schema_validating(header: &str) {
// In mock user provider, right username:password == "greptime:greptime"
let mock_user_provider = Some(Arc::new(MockUserProvider::default()) as Arc);
- // base64encode("greptime:greptime") == "Z3JlcHRpbWU6Z3JlcHRpbWU="
// http://localhost/{http_api_version}/sql?db=greptime
let version = servers::http::HTTP_API_VERSION;
let req = mock_http_request(
header,
- Some("Basic Z3JlcHRpbWU6Z3JlcHRpbWU="),
+ Some(&basic_auth("greptime", "greptime")),
Some(format!("http://localhost/{version}/sql?db=public").as_str()),
)
.unwrap();
@@ -96,7 +95,7 @@ async fn check_schema_validating(header: &str) {
// wrong database
let req = mock_http_request(
header,
- Some("Basic Z3JlcHRpbWU6Z3JlcHRpbWU="),
+ Some(&basic_auth("greptime", "greptime")),
Some(format!("http://localhost/{version}/sql?db=wrong").as_str()),
)
.unwrap();
@@ -120,7 +119,6 @@ async fn check_auth_header(header_key: &str) {
// In mock user provider, right username:password == "greptime:greptime"
let mock_user_provider = Some(Arc::new(MockUserProvider::default()) as Arc);
- // base64encode("greptime:greptime") == "Z3JlcHRpbWU6Z3JlcHRpbWU="
// try auth path first
let req = mock_http_request(header_key, None, None).unwrap();
let auth_res = inner_auth(mock_user_provider.clone(), req).await;
@@ -144,6 +142,13 @@ async fn test_whitelist_no_auth() {
check_auth_header(AUTHORIZATION_HEADER).await;
}
+fn basic_auth(username: &str, password: &str) -> String {
+ format!(
+ "Basic {}",
+ BASE64_STANDARD.encode(format!("{username}:{password}"))
+ )
+}
+
// copy from http::authorize
fn mock_http_request(
auth_header_key: &str,
diff --git a/src/servers/tests/http/influxdb_test.rs b/src/servers/tests/http/influxdb_test.rs
index cbc1ec291c..59ec9c138c 100644
--- a/src/servers/tests/http/influxdb_test.rs
+++ b/src/servers/tests/http/influxdb_test.rs
@@ -18,6 +18,7 @@ use api::v1::RowInsertRequests;
use async_trait::async_trait;
use auth::tests::{DatabaseAuthInfo, MockUserProvider};
use axum::{Router, http};
+use base64::prelude::{BASE64_STANDARD, Engine as _};
use common_query::Output;
use common_test_util::ports;
use datafusion_expr::LogicalPlan;
@@ -34,6 +35,13 @@ use session::context::QueryContextRef;
use sql::statements::statement::Statement;
use tokio::sync::mpsc;
+fn basic_auth(username: &str, password: &str) -> String {
+ format!(
+ "basic {}",
+ BASE64_STANDARD.encode(format!("{username}:{password}"))
+ )
+}
+
struct DummyInstance {
tx: Arc>,
}
@@ -146,7 +154,7 @@ async fn test_influxdb_write() {
.body("monitor,host=host1 cpu=1.2 1664370459457010101")
.header(
http::header::AUTHORIZATION,
- "basic Z3JlcHRpbWU6Z3JlcHRpbWU=",
+ basic_auth("greptime", "greptime"),
)
.send()
.await;
diff --git a/tests-integration/Cargo.toml b/tests-integration/Cargo.toml
index b9bda50c0b..050a2e5b1a 100644
--- a/tests-integration/Cargo.toml
+++ b/tests-integration/Cargo.toml
@@ -23,6 +23,7 @@ async-stream.workspace = true
async-trait.workspace = true
auth.workspace = true
axum.workspace = true
+base64.workspace = true
cache.workspace = true
catalog.workspace = true
chrono.workspace = true
@@ -64,6 +65,7 @@ meta-srv = { workspace = true, features = ["mock"] }
mito2.workspace = true
object-store.workspace = true
operator = { workspace = true, features = ["testing"] }
+plugins.workspace = true
prost.workspace = true
query.workspace = true
rand.workspace = true
diff --git a/tests-integration/tests/grpc.rs b/tests-integration/tests/grpc.rs
index e31444b45c..0f1112ff4a 100644
--- a/tests-integration/tests/grpc.rs
+++ b/tests-integration/tests/grpc.rs
@@ -22,6 +22,7 @@ use api::v1::{
column,
};
use auth::user_provider_from_option;
+use base64::prelude::{BASE64_STANDARD, Engine as _};
use client::{Client, DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME, Database, OutputData};
use common_catalog::consts::MITO_ENGINE;
use common_grpc::channel_manager::ClientTlsOption;
@@ -336,7 +337,7 @@ pub async fn test_otel_arrow_auth(store_type: StorageType) {
let mut request = Request::new(stream);
request.metadata_mut().insert(
"authorization",
- MetadataValue::from_static("Basic Z3JlcHRpbWVfdXNlcjpncmVwdGltZV9wd2Q="), // greptime_user:greptime_pwd base64 encoded
+ MetadataValue::try_from(basic_auth("greptime_user", "greptime_pwd")).unwrap(),
);
let response = client.arrow_metrics(request).await;
assert!(response.is_ok());
@@ -356,7 +357,8 @@ pub async fn test_otel_arrow_auth(store_type: StorageType) {
let mut request = Request::new(stream);
request.metadata_mut().insert(
"authorization",
- MetadataValue::from_static("Z3JlcHRpbWVfdXNlcjpncmVwdGltZV9wd2Q="), // greptime_user:greptime_pwd base64 encoded
+ MetadataValue::try_from(basic_auth_credentials("greptime_user", "greptime_pwd"))
+ .unwrap(),
);
let response = client.arrow_metrics(request).await;
assert!(response.is_ok());
@@ -374,6 +376,14 @@ pub async fn test_otel_arrow_auth(store_type: StorageType) {
let _ = fe_grpc_server.shutdown().await;
}
+fn basic_auth(username: &str, password: &str) -> String {
+ format!("Basic {}", basic_auth_credentials(username, password))
+}
+
+fn basic_auth_credentials(username: &str, password: &str) -> String {
+ BASE64_STANDARD.encode(format!("{username}:{password}"))
+}
+
pub async fn test_auto_create_table(store_type: StorageType) {
let (_db, fe_grpc_server) = setup_grpc_server(store_type, "test_auto_create_table").await;
let addr = fe_grpc_server.bind_addr().unwrap().to_string();
diff --git a/tests-integration/tests/http.rs b/tests-integration/tests/http.rs
index f270540035..dcf943db51 100644
--- a/tests-integration/tests/http.rs
+++ b/tests-integration/tests/http.rs
@@ -21,13 +21,17 @@ use api::prom_store::remote::label_matcher::Type as MatcherType;
use api::prom_store::remote::{
Label, LabelMatcher, Query, ReadRequest, ReadResponse, Sample, TimeSeries, WriteRequest,
};
-use auth::user_provider_from_option;
+use auth::{UserProviderRef, user_provider_from_option};
use axum::http::{HeaderName, HeaderValue, StatusCode};
+use base64::prelude::{BASE64_STANDARD, Engine as _};
use chrono::Utc;
+use cmd::options::GreptimeOptions;
+use common_base::Plugins;
use common_catalog::consts::{
DEFAULT_PRIVATE_SCHEMA_NAME, TRACE_TABLE_NAME, trace_operations_table_name,
trace_services_table_name,
};
+use common_config::Configurable;
use common_error::status_code::StatusCode as ErrorCode;
use common_frontend::slow_query_event::{
SLOW_QUERY_TABLE_NAME, SLOW_QUERY_TABLE_QUERY_COLUMN_NAME,
@@ -60,6 +64,7 @@ use servers::http::result::influxdb_result_v1::{InfluxdbOutput, InfluxdbV1Respon
use servers::http::test_helpers::{TestClient, TestResponse};
use servers::prom_store::{self, mock_timeseries_new_label};
use servers::request_memory_limiter::ServerMemoryLimiter;
+use standalone::options::StandaloneOptions;
use table::table_name::TableName;
use tests_integration::test_util::{
StorageType, setup_test_http_app, setup_test_http_app_with_frontend,
@@ -186,7 +191,7 @@ pub async fn test_http_auth(store_type: StorageType) {
// 2. wrong auth
let res = client
.get("/v1/sql?db=public&sql=show tables;")
- .header("Authorization", "basic Z3JlcHRpbWVfdXNlcjp3cm9uZ19wd2Q=")
+ .header("Authorization", basic_auth("greptime_user", "wrong_pwd"))
.send()
.await;
assert_eq!(res.status(), StatusCode::UNAUTHORIZED);
@@ -194,10 +199,7 @@ pub async fn test_http_auth(store_type: StorageType) {
// 3. right auth
let res = client
.get("/v1/sql?db=public&sql=show tables;")
- .header(
- "Authorization",
- "basic Z3JlcHRpbWVfdXNlcjpncmVwdGltZV9wd2Q=",
- )
+ .header("Authorization", basic_auth("greptime_user", "greptime_pwd"))
.send()
.await;
assert_eq!(res.status(), StatusCode::OK);
@@ -205,19 +207,13 @@ pub async fn test_http_auth(store_type: StorageType) {
// 4. readonly user cannot write
let res = client
.get("/v1/sql?db=public&sql=show tables;")
- .header(
- "Authorization",
- "basic cmVhZG9ubHlfdXNlcjpyZWFkb25seV9wd2Q=",
- )
+ .header("Authorization", basic_auth("readonly_user", "readonly_pwd"))
.send()
.await;
assert_eq!(res.status(), StatusCode::OK);
let res = client
.get("/v1/sql?db=public&sql=create table auth_test(ts timestamp time index);")
- .header(
- "Authorization",
- "basic cmVhZG9ubHlfdXNlcjpyZWFkb25seV9wd2Q=",
- )
+ .header("Authorization", basic_auth("readonly_user", "readonly_pwd"))
.send()
.await;
assert_eq!(res.status(), StatusCode::FORBIDDEN);
@@ -227,7 +223,7 @@ pub async fn test_http_auth(store_type: StorageType) {
.get("/v1/sql?db=public&sql=show tables;")
.header(
"Authorization",
- "basic d3JpdGVvbmx5X3VzZXI6d3JpdGVvbmx5X3B3ZA==",
+ basic_auth("writeonly_user", "writeonly_pwd"),
)
.send()
.await;
@@ -236,7 +232,7 @@ pub async fn test_http_auth(store_type: StorageType) {
.get("/v1/sql?db=public&sql=create table auth_test(ts timestamp time index);")
.header(
"Authorization",
- "basic d3JpdGVvbmx5X3VzZXI6d3JpdGVvbmx5X3B3ZA==",
+ basic_auth("writeonly_user", "writeonly_pwd"),
)
.send()
.await;
@@ -245,7 +241,7 @@ pub async fn test_http_auth(store_type: StorageType) {
.get("/v1/sql?db=public&sql=insert into auth_test values(1);")
.header(
"Authorization",
- "basic d3JpdGVvbmx5X3VzZXI6d3JpdGVvbmx5X3B3ZA==",
+ basic_auth("writeonly_user", "writeonly_pwd"),
)
.send()
.await;
@@ -254,7 +250,7 @@ pub async fn test_http_auth(store_type: StorageType) {
.get("/v1/sql?db=public&sql=select * from auth_test;")
.header(
"Authorization",
- "basic d3JpdGVvbmx5X3VzZXI6d3JpdGVvbmx5X3B3ZA==",
+ basic_auth("writeonly_user", "writeonly_pwd"),
)
.send()
.await;
@@ -263,6 +259,65 @@ pub async fn test_http_auth(store_type: StorageType) {
guard.remove_all().await;
}
+fn basic_auth(username: &str, password: &str) -> String {
+ format!(
+ "basic {}",
+ BASE64_STANDARD.encode(format!("{username}:{password}"))
+ )
+}
+
+pub async fn test_http_auth_from_standalone_user_provider_config() {
+ common_telemetry::init_default_ut_logging();
+
+ let config = tempfile::NamedTempFile::new().unwrap();
+ let user_provider = "static_user_provider:cmd:greptime_user=greptime_pwd";
+ std::fs::write(
+ config.path(),
+ format!("user_provider = \"{user_provider}\"\n"),
+ )
+ .unwrap();
+
+ let options =
+ GreptimeOptions::::load_layered_options(config.path().to_str(), "")
+ .unwrap();
+ let fe_opts = options.component.frontend_options();
+
+ let mut plugins = Plugins::new();
+ plugins::setup_frontend_plugins(&mut plugins, &[], &fe_opts)
+ .await
+ .unwrap();
+ let user_provider = plugins.get::();
+
+ let (app, mut guard) = setup_test_http_app_with_frontend_and_user_provider(
+ StorageType::File,
+ "sql_api_user_provider_config",
+ user_provider,
+ )
+ .await;
+ let client = TestClient::new(app).await;
+
+ let res = client
+ .get("/v1/sql?db=public&sql=show tables;")
+ .send()
+ .await;
+ let status = res.status();
+ let body = res.text().await;
+ assert_eq!(
+ status,
+ StatusCode::UNAUTHORIZED,
+ "unexpected response body: {body}"
+ );
+
+ let res = client
+ .get("/v1/sql?db=public&sql=show tables;")
+ .header("Authorization", basic_auth("greptime_user", "greptime_pwd"))
+ .send()
+ .await;
+ assert_eq!(res.status(), StatusCode::OK);
+
+ guard.remove_all().await;
+}
+
#[tokio::test]
pub async fn test_cors() {
let (app, mut guard) = setup_test_http_app_with_frontend(StorageType::File, "test_cors").await;
diff --git a/tests-integration/tests/main.rs b/tests-integration/tests/main.rs
index 95f605ff11..e8b8232ad8 100644
--- a/tests-integration/tests/main.rs
+++ b/tests-integration/tests/main.rs
@@ -33,6 +33,11 @@ grpc_tests!(File, S3, S3WithCache, Oss, Azblob, Gcs);
http_tests!(File, S3, S3WithCache, Oss, Azblob, Gcs);
+#[tokio::test(flavor = "multi_thread")]
+async fn test_http_auth_from_standalone_user_provider_config() {
+ http::test_http_auth_from_standalone_user_provider_config().await;
+}
+
sql_tests!(File);
region_migration_tests!(File);