gRPC authentication

This commit is contained in:
Gregory Schier
2024-02-17 23:47:28 -08:00
parent ab8503d87c
commit 8d29fad261
16 changed files with 246 additions and 123 deletions

View File

@@ -1,6 +1,6 @@
{
"db_name": "SQLite",
"query": "\n SELECT\n id, model, workspace_id, folder_id, created_at, updated_at, name, sort_priority,\n url, service, method, message,\n proto_files AS \"proto_files!: sqlx::types::Json<Vec<String>>\"\n FROM grpc_requests\n WHERE id = ?\n ",
"query": "\n SELECT\n id, model, workspace_id, folder_id, created_at, updated_at, name, sort_priority,\n url, service, method, message, authentication_type,\n authentication AS \"authentication!: Json<HashMap<String, JsonValue>>\",\n proto_files AS \"proto_files!: sqlx::types::Json<Vec<String>>\"\n FROM grpc_requests\n WHERE id = ?\n ",
"describe": {
"columns": [
{
@@ -64,9 +64,19 @@
"type_info": "Text"
},
{
"name": "proto_files!: sqlx::types::Json<Vec<String>>",
"name": "authentication_type",
"ordinal": 12,
"type_info": "Text"
},
{
"name": "authentication!: Json<HashMap<String, JsonValue>>",
"ordinal": 13,
"type_info": "Text"
},
{
"name": "proto_files!: sqlx::types::Json<Vec<String>>",
"ordinal": 14,
"type_info": "Text"
}
],
"parameters": {
@@ -85,8 +95,10 @@
true,
true,
false,
true,
false,
false
]
},
"hash": "7398403d3de2dc5c5b4b6392f083041d9a55194bb97819225a2612fdeb60ad42"
"hash": "0d9e685f878fc2a0e1803c6aaae3828deebd684fc9f78e9f8595a550f90749fe"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "SQLite",
"query": "\n SELECT\n id, model, workspace_id, folder_id, created_at, updated_at, name, sort_priority,\n url, service, method, message,\n proto_files AS \"proto_files!: sqlx::types::Json<Vec<String>>\"\n FROM grpc_requests\n WHERE workspace_id = ?\n ",
"query": "\n SELECT\n id, model, workspace_id, folder_id, created_at, updated_at, name, sort_priority,\n url, service, method, message, authentication_type,\n authentication AS \"authentication!: Json<HashMap<String, JsonValue>>\",\n proto_files AS \"proto_files!: sqlx::types::Json<Vec<String>>\"\n FROM grpc_requests\n WHERE workspace_id = ?\n ",
"describe": {
"columns": [
{
@@ -64,9 +64,19 @@
"type_info": "Text"
},
{
"name": "proto_files!: sqlx::types::Json<Vec<String>>",
"name": "authentication_type",
"ordinal": 12,
"type_info": "Text"
},
{
"name": "authentication!: Json<HashMap<String, JsonValue>>",
"ordinal": 13,
"type_info": "Text"
},
{
"name": "proto_files!: sqlx::types::Json<Vec<String>>",
"ordinal": 14,
"type_info": "Text"
}
],
"parameters": {
@@ -85,8 +95,10 @@
true,
true,
false,
true,
false,
false
]
},
"hash": "761d27c3ec425c37ad9abe9c732a9c1746c81ca50d2c413e540b74c8c8e908b7"
"hash": "545d21ff21bd02468be86746325dc9f1b50f3fe53d7735194d91927b5d14a436"
}

View File

@@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "\n INSERT INTO grpc_requests (\n id, name, workspace_id, folder_id, sort_priority, url, service, method, message,\n proto_files, authentication_type, authentication\n )\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT (id) DO UPDATE SET\n updated_at = CURRENT_TIMESTAMP,\n name = excluded.name,\n folder_id = excluded.folder_id,\n sort_priority = excluded.sort_priority,\n url = excluded.url,\n service = excluded.service,\n method = excluded.method,\n message = excluded.message,\n proto_files = excluded.proto_files,\n authentication_type = excluded.authentication_type,\n authentication = excluded.authentication\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 12
},
"nullable": []
},
"hash": "c554305252cb21e34aa1e3c1f204c6060c3d2a209689a879824dea4d26e5497e"
}

View File

@@ -1,12 +0,0 @@
{
"db_name": "SQLite",
"query": "\n INSERT INTO grpc_requests (\n id, name, workspace_id, folder_id, sort_priority, url, service, method, message,\n proto_files\n )\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT (id) DO UPDATE SET\n updated_at = CURRENT_TIMESTAMP,\n name = excluded.name,\n folder_id = excluded.folder_id,\n sort_priority = excluded.sort_priority,\n url = excluded.url,\n service = excluded.service,\n method = excluded.method,\n message = excluded.message,\n proto_files = excluded.proto_files\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 10
},
"nullable": []
},
"hash": "ee562f85ec28c554c607adde670fc30eaeffeed6883e712bda4b4d6ca49cf740"
}

View File

@@ -1,5 +1,6 @@
use std::collections::HashMap;
use std::path::PathBuf;
use std::str::FromStr;
use hyper::client::HttpConnector;
use hyper::Client;
@@ -10,8 +11,9 @@ use serde_json::Deserializer;
use tokio_stream::wrappers::ReceiverStream;
use tokio_stream::StreamExt;
use tonic::body::BoxBody;
use tonic::metadata::{MetadataKey, MetadataValue};
use tonic::transport::Uri;
use tonic::{IntoRequest, IntoStreamingRequest, Response, Status, Streaming};
use tonic::{IntoRequest, IntoStreamingRequest, Request, Response, Status, Streaming};
use crate::codec::DynamicCodec;
use crate::proto::{fill_pool, fill_pool_from_files, get_transport, method_desc_to_path};
@@ -47,6 +49,7 @@ impl GrpcConnection {
service: &str,
method: &str,
message: &str,
metadata: HashMap<String, String>,
) -> Result<DynamicMessage, String> {
let method = &self.method(&service, &method)?;
let input_message = method.input();
@@ -58,7 +61,9 @@ impl GrpcConnection {
let mut client = tonic::client::Grpc::with_origin(self.conn.clone(), self.uri.clone());
let req = req_message.into_request();
let mut req = req_message.into_request();
decorate_req(metadata, &mut req).map_err(|e| e.to_string())?;
let path = method_desc_to_path(method);
let codec = DynamicCodec::new(method.clone());
client.ready().await.unwrap();
@@ -75,12 +80,13 @@ impl GrpcConnection {
service: &str,
method: &str,
stream: ReceiverStream<String>,
metadata: HashMap<String, String>,
) -> Result<Result<Response<Streaming<DynamicMessage>>, Status>, String> {
let method = &self.method(&service, &method)?;
let mut client = tonic::client::Grpc::with_origin(self.conn.clone(), self.uri.clone());
let method2 = method.clone();
let req = stream
let mut req = stream
.map(move |s| {
let mut deserializer = Deserializer::from_str(&s);
let req_message = DynamicMessage::deserialize(method2.input(), &mut deserializer)
@@ -90,6 +96,9 @@ impl GrpcConnection {
req_message
})
.into_streaming_request();
decorate_req(metadata, &mut req).map_err(|e| e.to_string())?;
let path = method_desc_to_path(method);
let codec = DynamicCodec::new(method.clone());
client.ready().await.map_err(|e| e.to_string())?;
@@ -101,11 +110,12 @@ impl GrpcConnection {
service: &str,
method: &str,
stream: ReceiverStream<String>,
metadata: HashMap<String, String>,
) -> Result<DynamicMessage, String> {
let method = &self.method(&service, &method)?;
let mut client = tonic::client::Grpc::with_origin(self.conn.clone(), self.uri.clone());
let req = {
let mut req = {
let method = method.clone();
stream
.map(move |s| {
@@ -119,6 +129,9 @@ impl GrpcConnection {
})
.into_streaming_request()
};
decorate_req(metadata, &mut req).map_err(|e| e.to_string())?;
let path = method_desc_to_path(method);
let codec = DynamicCodec::new(method.clone());
client.ready().await.unwrap();
@@ -134,6 +147,7 @@ impl GrpcConnection {
service: &str,
method: &str,
message: &str,
metadata: HashMap<String, String>,
) -> Result<Result<Response<Streaming<DynamicMessage>>, Status>, String> {
let method = &self.method(&service, &method)?;
let input_message = method.input();
@@ -145,7 +159,9 @@ impl GrpcConnection {
let mut client = tonic::client::Grpc::with_origin(self.conn.clone(), self.uri.clone());
let req = req_message.into_request();
let mut req = req_message.into_request();
decorate_req(metadata, &mut req).map_err(|e| e.to_string())?;
let path = method_desc_to_path(method);
let codec = DynamicCodec::new(method.clone());
client.ready().await.map_err(|e| e.to_string())?;
@@ -222,10 +238,11 @@ impl GrpcHandle {
service: &str,
method: &str,
message: &str,
metadata: HashMap<String, String>,
) -> Result<Result<Response<Streaming<DynamicMessage>>, Status>, String> {
self.connect(id, uri, proto_files)
.await?
.server_streaming(service, method, message)
.server_streaming(service, method, message, metadata)
.await
}
@@ -237,10 +254,11 @@ impl GrpcHandle {
service: &str,
method: &str,
stream: ReceiverStream<String>,
metadata: HashMap<String, String>,
) -> Result<DynamicMessage, String> {
self.connect(id, uri, proto_files)
.await?
.client_streaming(service, method, stream)
.client_streaming(service, method, stream, metadata)
.await
}
@@ -252,10 +270,11 @@ impl GrpcHandle {
service: &str,
method: &str,
stream: ReceiverStream<String>,
metadata: HashMap<String, String>,
) -> Result<Result<Response<Streaming<DynamicMessage>>, Status>, String> {
self.connect(id, uri, proto_files)
.await?
.streaming(service, method, stream)
.streaming(service, method, stream, metadata)
.await
}
@@ -282,3 +301,13 @@ impl GrpcHandle {
Ok(connection)
}
}
fn decorate_req<T>(metadata: HashMap<String, String>, req: &mut Request<T>) -> Result<(), String> {
for (k, v) in metadata {
req.metadata_mut().insert(
MetadataKey::from_str(k.as_str()).map_err(|e| e.to_string())?,
MetadataValue::from_str(v.as_str()).map_err(|e| e.to_string())?,
);
}
Ok(())
}

View File

@@ -0,0 +1,2 @@
ALTER TABLE grpc_requests ADD COLUMN authentication TEXT NOT NULL DEFAULT '{}';
ALTER TABLE grpc_requests ADD COLUMN authentication_type TEXT;

View File

@@ -17,6 +17,7 @@ use std::str::FromStr;
use ::http::uri::InvalidUri;
use ::http::Uri;
use base64::Engine;
use fern::colors::ColoredLevelConfig;
use log::{debug, error, info, warn};
use rand::random;
@@ -128,12 +129,20 @@ async fn cmd_grpc_reflect(
#[tauri::command]
async fn cmd_grpc_go(
request_id: &str,
environment_id: Option<&str>,
w: Window,
grpc_handle: State<'_, Mutex<GrpcHandle>>,
) -> Result<String, String> {
let req = get_grpc_request(&w, request_id)
.await
.map_err(|e| e.to_string())?;
let environment = match environment_id {
Some(id) => Some(get_environment(&w, id).await.map_err(|e| e.to_string())?),
None => None,
};
let workspace = get_workspace(&w, &req.workspace_id)
.await
.map_err(|e| e.to_string())?;
let conn = {
let req = req.clone();
upsert_grpc_connection(
@@ -261,6 +270,37 @@ async fn cmd_grpc_go(
}
};
let event_handler = w.listen_global(format!("grpc_client_msg_{}", conn.id).as_str(), cb);
let mut metadata = HashMap::new();
if let Some(b) = &req.authentication_type {
let req = req.clone();
let environment_ref = environment.as_ref();
let empty_value = &serde_json::to_value("").unwrap();
let a = req.authentication.0;
if b == "basic" {
let raw_username = a
.get("username")
.unwrap_or(empty_value)
.as_str()
.unwrap_or("");
let raw_password = a
.get("password")
.unwrap_or(empty_value)
.as_str()
.unwrap_or("");
let username = render::render(raw_username, &workspace, environment_ref);
let password = render::render(raw_password, &workspace, environment_ref);
let auth = format!("{username}:{password}");
let encoded = base64::engine::general_purpose::STANDARD_NO_PAD.encode(auth);
metadata.insert("Authorization".to_string(), format!("Basic {}", encoded));
} else if b == "bearer" {
let raw_token = a.get("token").unwrap_or(empty_value).as_str().unwrap_or("");
let token = render::render(raw_token, &workspace, environment_ref);
metadata.insert("Authorization".to_string(), format!("Bearer {token}"));
}
}
println!("METADATA: {:?}", metadata);
let grpc_listen = {
let w = w.clone();
@@ -272,28 +312,36 @@ async fn cmd_grpc_go(
method_desc.is_server_streaming(),
) {
(true, true) => (
Some(connection.streaming(&service, &method, in_msg_stream).await),
Some(
connection
.streaming(&service, &method, in_msg_stream, metadata)
.await,
),
None,
),
(true, false) => (
None,
Some(
connection
.client_streaming(&service, &method, in_msg_stream)
.client_streaming(&service, &method, in_msg_stream, metadata)
.await,
),
),
(false, true) => (
Some(
connection
.server_streaming(&service, &method, &req.message)
.server_streaming(&service, &method, &req.message, metadata)
.await,
),
None,
),
(false, false) => (
None,
Some(connection.unary(&service, &method, &req.message).await),
Some(
connection
.unary(&service, &method, &req.message, metadata)
.await,
),
),
};

View File

@@ -205,6 +205,8 @@ pub struct GrpcRequest {
pub method: Option<String>,
pub message: String,
pub proto_files: Json<Vec<String>>,
pub authentication_type: Option<String>,
pub authentication: Json<HashMap<String, JsonValue>>,
}
#[derive(sqlx::FromRow, Debug, Clone, Serialize, Deserialize, Default)]
@@ -502,9 +504,9 @@ pub async fn upsert_grpc_request(
r#"
INSERT INTO grpc_requests (
id, name, workspace_id, folder_id, sort_priority, url, service, method, message,
proto_files
proto_files, authentication_type, authentication
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT (id) DO UPDATE SET
updated_at = CURRENT_TIMESTAMP,
name = excluded.name,
@@ -514,7 +516,9 @@ pub async fn upsert_grpc_request(
service = excluded.service,
method = excluded.method,
message = excluded.message,
proto_files = excluded.proto_files
proto_files = excluded.proto_files,
authentication_type = excluded.authentication_type,
authentication = excluded.authentication
"#,
id,
trimmed_name,
@@ -526,6 +530,8 @@ pub async fn upsert_grpc_request(
request.method,
request.message,
request.proto_files,
request.authentication_type,
request.authentication,
)
.execute(&db)
.await?;
@@ -546,7 +552,8 @@ pub async fn get_grpc_request(
r#"
SELECT
id, model, workspace_id, folder_id, created_at, updated_at, name, sort_priority,
url, service, method, message,
url, service, method, message, authentication_type,
authentication AS "authentication!: Json<HashMap<String, JsonValue>>",
proto_files AS "proto_files!: sqlx::types::Json<Vec<String>>"
FROM grpc_requests
WHERE id = ?
@@ -567,7 +574,8 @@ pub async fn list_grpc_requests(
r#"
SELECT
id, model, workspace_id, folder_id, created_at, updated_at, name, sort_priority,
url, service, method, message,
url, service, method, message, authentication_type,
authentication AS "authentication!: Json<HashMap<String, JsonValue>>",
proto_files AS "proto_files!: sqlx::types::Json<Vec<String>>"
FROM grpc_requests
WHERE workspace_id = ?