diff --git a/src-tauri/src/extension/core/manifest.rs b/src-tauri/src/extension/core/manifest.rs index 88c88b5..5cb7bf6 100644 --- a/src-tauri/src/extension/core/manifest.rs +++ b/src-tauri/src/extension/core/manifest.rs @@ -1,6 +1,6 @@ use crate::extension::error::ExtensionError; use crate::extension::permissions::types::{ - Action, DbAction, ExtensionPermission, FsAction, HttpAction, PermissionConstraints, + Action, DbAction, ExtensionPermission, FsAction, WebAction, PermissionConstraints, PermissionStatus, ResourceType, ShellAction, }; use serde::{Deserialize, Serialize}; @@ -117,7 +117,7 @@ impl ExtensionPermissions { } if let Some(entries) = &self.http { for p in entries { - if let Some(perm) = Self::create_internal(extension_id, ResourceType::Http, p) { + if let Some(perm) = Self::create_internal(extension_id, ResourceType::Web, p) { permissions.push(perm); } } @@ -146,7 +146,7 @@ impl ExtensionPermissions { ResourceType::Fs => FsAction::from_str(operation_str) .ok() .map(Action::Filesystem), - ResourceType::Http => HttpAction::from_str(operation_str).ok().map(Action::Http), + ResourceType::Web => WebAction::from_str(operation_str).ok().map(Action::Web), ResourceType::Shell => ShellAction::from_str(operation_str).ok().map(Action::Shell), }; diff --git a/src-tauri/src/extension/permissions/manager.rs b/src-tauri/src/extension/permissions/manager.rs index 1d7a712..a184213 100644 --- a/src-tauri/src/extension/permissions/manager.rs +++ b/src-tauri/src/extension/permissions/manager.rs @@ -4,7 +4,7 @@ use crate::database::core::with_connection; use crate::database::error::DatabaseError; use crate::extension::database::executor::SqlExecutor; use crate::extension::error::ExtensionError; -use crate::extension::permissions::types::{Action, ExtensionPermission, PermissionStatus, ResourceType}; +use crate::extension::permissions::types::{Action, ExtensionPermission, PermissionConstraints, PermissionStatus, ResourceType}; use tauri::State; use crate::database::generated::HaexExtensionPermissions; use rusqlite::params; @@ -245,6 +245,74 @@ impl PermissionManager { Ok(()) } + /// Prüft Web-Berechtigungen für Requests + pub async fn check_web_permission( + app_state: &State<'_, AppState>, + extension_id: &str, + method: &str, + url: &str, + ) -> Result<(), ExtensionError> { + // Optimiert: Lade nur Web-Permissions aus der Datenbank + let permissions = with_connection(&app_state.db, |conn| { + let sql = format!( + "SELECT * FROM {TABLE_EXTENSION_PERMISSIONS} WHERE extension_id = ? AND resource_type = 'web'" + ); + let mut stmt = conn.prepare(&sql).map_err(DatabaseError::from)?; + + let perms_iter = stmt.query_map(params![extension_id], |row| { + crate::database::generated::HaexExtensionPermissions::from_row(row) + })?; + + let permissions: Vec = perms_iter + .filter_map(Result::ok) + .map(Into::into) + .collect(); + + Ok(permissions) + })?; + + let url_parsed = url::Url::parse(url).map_err(|e| ExtensionError::ValidationError { + reason: format!("Invalid URL: {}", e), + })?; + + let domain = url_parsed.host_str().ok_or_else(|| ExtensionError::ValidationError { + reason: "URL does not contain a valid host".to_string(), + })?; + + let has_permission = permissions + .iter() + .filter(|perm| perm.status == PermissionStatus::Granted) + .any(|perm| { + let domain_matches = perm.target == "*" + || perm.target == domain + || domain.ends_with(&format!(".{}", perm.target)); + + if !domain_matches { + return false; + } + + if let Some(PermissionConstraints::Web(constraints)) = &perm.constraints { + if let Some(methods) = &constraints.methods { + if !methods.iter().any(|m| m.eq_ignore_ascii_case(method)) { + return false; + } + } + } + + true + }); + + if !has_permission { + return Err(ExtensionError::permission_denied( + extension_id, + method, + &format!("web request to '{}'", url), + )); + } + + Ok(()) + } + /* /// Prüft Dateisystem-Berechtigungen pub async fn check_filesystem_permission( app_state: &State<'_, AppState>, @@ -293,56 +361,6 @@ impl PermissionManager { Ok(()) } - /// Prüft HTTP-Berechtigungen - pub async fn check_http_permission( - app_state: &State<'_, AppState>, - extension_id: &str, - method: &str, - url: &str, - ) -> Result<(), ExtensionError> { - let permissions = Self::get_permissions(app_state, extension_id).await?; - - let url_parsed = Url::parse(url).map_err(|e| ExtensionError::ValidationError { - reason: format!("Invalid URL: {}", e), - })?; - - let domain = url_parsed.host_str().unwrap_or(""); - - let has_permission = permissions - .iter() - .filter(|perm| perm.status == PermissionStatus::Granted) - .filter(|perm| perm.resource_type == ResourceType::Http) - .any(|perm| { - let domain_matches = perm.target == "*" - || perm.target == domain - || domain.ends_with(&format!(".{}", perm.target)); - - if !domain_matches { - return false; - } - - if let Some(PermissionConstraints::Http(constraints)) = &perm.constraints { - if let Some(methods) = &constraints.methods { - if !methods.iter().any(|m| m.eq_ignore_ascii_case(method)) { - return false; - } - } - } - - true - }); - - if !has_permission { - return Err(ExtensionError::permission_denied( - extension_id, - method, - &format!("HTTP request to '{}'", url), - )); - } - - Ok(()) - } - /// Prüft Shell-Berechtigungen pub async fn check_shell_permission( app_state: &State<'_, AppState>, @@ -410,7 +428,7 @@ impl PermissionManager { pub fn parse_resource_type(s: &str) -> Result { match s { "fs" => Ok(ResourceType::Fs), - "http" => Ok(ResourceType::Http), + "web" => Ok(ResourceType::Web), "db" => Ok(ResourceType::Db), "shell" => Ok(ResourceType::Shell), _ => Err(DatabaseError::SerializationError { diff --git a/src-tauri/src/extension/permissions/types.rs b/src-tauri/src/extension/permissions/types.rs index 27a1bbc..c68af80 100644 --- a/src-tauri/src/extension/permissions/types.rs +++ b/src-tauri/src/extension/permissions/types.rs @@ -86,11 +86,11 @@ impl FromStr for FsAction { } } -/// Definiert Aktionen (HTTP-Methoden), die auf HTTP-Anfragen angewendet werden können. +/// Definiert Aktionen (HTTP-Methoden), die auf Web-Anfragen angewendet werden können. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)] #[serde(rename_all = "UPPERCASE")] #[ts(export)] -pub enum HttpAction { +pub enum WebAction { Get, Post, Put, @@ -100,20 +100,20 @@ pub enum HttpAction { All, } -impl FromStr for HttpAction { +impl FromStr for WebAction { type Err = ExtensionError; fn from_str(s: &str) -> Result { match s.to_uppercase().as_str() { - "GET" => Ok(HttpAction::Get), - "POST" => Ok(HttpAction::Post), - "PUT" => Ok(HttpAction::Put), - "PATCH" => Ok(HttpAction::Patch), - "DELETE" => Ok(HttpAction::Delete), - "*" => Ok(HttpAction::All), + "GET" => Ok(WebAction::Get), + "POST" => Ok(WebAction::Post), + "PUT" => Ok(WebAction::Put), + "PATCH" => Ok(WebAction::Patch), + "DELETE" => Ok(WebAction::Delete), + "*" => Ok(WebAction::All), _ => Err(ExtensionError::InvalidActionString { input: s.to_string(), - resource_type: "http".to_string(), + resource_type: "web".to_string(), }), } } @@ -149,7 +149,7 @@ impl FromStr for ShellAction { pub enum Action { Database(DbAction), Filesystem(FsAction), - Http(HttpAction), + Web(WebAction), Shell(ShellAction), } @@ -173,7 +173,7 @@ pub struct ExtensionPermission { #[ts(export)] pub enum ResourceType { Fs, - Http, + Web, Db, Shell, } @@ -195,7 +195,7 @@ pub enum PermissionStatus { pub enum PermissionConstraints { Database(DbConstraints), Filesystem(FsConstraints), - Http(HttpConstraints), + Web(WebConstraints), Shell(ShellConstraints), } @@ -223,7 +223,7 @@ pub struct FsConstraints { #[derive(Serialize, Deserialize, Clone, Debug, Default, TS)] #[ts(export)] -pub struct HttpConstraints { +pub struct WebConstraints { #[serde(skip_serializing_if = "Option::is_none")] pub methods: Option>, #[serde(skip_serializing_if = "Option::is_none")] @@ -254,7 +254,7 @@ impl ResourceType { pub fn as_str(&self) -> &str { match self { ResourceType::Fs => "fs", - ResourceType::Http => "http", + ResourceType::Web => "web", ResourceType::Db => "db", ResourceType::Shell => "shell", } @@ -263,7 +263,7 @@ impl ResourceType { pub fn from_str(s: &str) -> Result { match s { "fs" => Ok(ResourceType::Fs), - "http" => Ok(ResourceType::Http), + "web" => Ok(ResourceType::Web), "db" => Ok(ResourceType::Db), "shell" => Ok(ResourceType::Shell), _ => Err(ExtensionError::ValidationError { @@ -284,7 +284,7 @@ impl Action { .unwrap_or_default() .trim_matches('"') .to_string(), - Action::Http(action) => serde_json::to_string(action) + Action::Web(action) => serde_json::to_string(action) .unwrap_or_default() .trim_matches('"') .to_string(), @@ -299,15 +299,15 @@ impl Action { match resource_type { ResourceType::Db => Ok(Action::Database(DbAction::from_str(s)?)), ResourceType::Fs => Ok(Action::Filesystem(FsAction::from_str(s)?)), - ResourceType::Http => { - let action: HttpAction = + ResourceType::Web => { + let action: WebAction = serde_json::from_str(&format!("\"{s}\"")).map_err(|_| { ExtensionError::InvalidActionString { input: s.to_string(), - resource_type: "http".to_string(), + resource_type: "web".to_string(), } })?; - Ok(Action::Http(action)) + Ok(Action::Web(action)) } ResourceType::Shell => Ok(Action::Shell(ShellAction::from_str(s)?)), } diff --git a/src-tauri/src/extension/web/mod.rs b/src-tauri/src/extension/web/mod.rs index a978519..644f995 100644 --- a/src-tauri/src/extension/web/mod.rs +++ b/src-tauri/src/extension/web/mod.rs @@ -41,7 +41,7 @@ pub async fn extension_web_open( state: State<'_, AppState>, ) -> Result<(), ExtensionError> { // Get extension to validate it exists - let _extension = state + let extension = state .extension_manager .get_extension_by_public_key_and_name(&public_key, &name)? .ok_or_else(|| ExtensionError::NotFound { @@ -62,6 +62,15 @@ pub async fn extension_web_open( }); } + // Check web permissions (open uses GET method for permission check) + crate::extension::permissions::manager::PermissionManager::check_web_permission( + &state, + &extension.id, + "GET", + &url, + ) + .await?; + // Open URL in default browser using tauri-plugin-opener tauri_plugin_opener::open_url(&url, None::<&str>).map_err(|e| ExtensionError::WebError { reason: format!("Failed to open URL in browser: {}", e), @@ -82,7 +91,7 @@ pub async fn extension_web_fetch( state: State<'_, AppState>, ) -> Result { // Get extension to validate it exists - let _extension = state + let extension = state .extension_manager .get_extension_by_public_key_and_name(&public_key, &name)? .ok_or_else(|| ExtensionError::NotFound { @@ -90,13 +99,20 @@ pub async fn extension_web_fetch( name: name.clone(), })?; - // TODO: Add permission check for web requests once permission system is complete - // For now, extensions are allowed to make web requests - // Use _extension for permission validation when implemented + let method_str = method.as_deref().unwrap_or("GET"); + + // Check web permissions before making request + crate::extension::permissions::manager::PermissionManager::check_web_permission( + &state, + &extension.id, + method_str, + &url, + ) + .await?; let request = WebFetchRequest { url, - method, + method: Some(method_str.to_string()), headers, body, timeout,