From 29e18fe692ab3b1ba9b58fe0f6a35dc308bf9f95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20St=C3=BCrmer?= Date: Sat, 1 Jul 2023 07:36:56 +0200 Subject: [PATCH] adjust application configuration parsing --- Cargo.toml | 2 + src/config/client.rs | 89 +++++++++++++++++++++++ src/config/configuration.rs | 133 +++++++++++++++++++++++++++++++++++ src/config/mod.rs | 36 +--------- src/config/plugin.rs | 81 +++++++++++++++++++++ src/lib.rs | 12 +++- src/plugin/mod.rs | 17 +++++ src/plugin/plugin_manager.rs | 19 ++++- 8 files changed, 353 insertions(+), 36 deletions(-) create mode 100644 src/config/client.rs create mode 100644 src/config/configuration.rs create mode 100644 src/config/plugin.rs diff --git a/Cargo.toml b/Cargo.toml index 0189172..3524416 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,7 @@ members = [ # define dependencies to be inherited by members of the workspace [workspace.dependencies] core_common = { path = "./crates/core_common" } +# core_oauth = { path = "./crates/core_oauth" } core_photos = { path = "./crates/core_photos" } core_activity_pub = { path = "./crates/core_activity_pub" } activitypub_federation = "~0.4.0" @@ -54,6 +55,7 @@ path = "../plugin_interface" # core_api = { workspace = true } core_common = { workspace = true } core_photos = { workspace = true } +# core_oauth = { workspace = true } core_activity_pub = { workspace = true } # core_api_crud = { workspace = true } diff --git a/src/config/client.rs b/src/config/client.rs new file mode 100644 index 0000000..8e91a3c --- /dev/null +++ b/src/config/client.rs @@ -0,0 +1,89 @@ +/* Photos.network · A privacy first photo storage and sharing service for fediverse. + * Copyright (C) 2020 Photos network developers + * + * This program 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. + * + * This program 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +//! This represents an oauth client configuration +use std::fmt; + +use serde::{Deserialize, Serialize}; + +#[derive(PartialEq, Debug, Deserialize, Serialize, Clone)] +pub struct OAuthClientConfig { + pub name: String, + pub client_id: String, + pub client_secret: String, + pub redirect_uris: Vec +} + +impl fmt::Display for OAuthClientConfig { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, + "{}, redirect: {:?}", self.name, self.redirect_uris) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_full_deserialization() { + // given + let json = r#"{ + "name": "Client", + "client_id": "clientId", + "client_secret": "clientSecret", + "redirect_uris": [ + "https://demo.photos.network/callback", + "http://127.0.0.1:7777/callback", + "photosapp://authenticate" + ] + }"#; + + let data = OAuthClientConfig { + name: "Client".into(), + client_id: "clientId".into(), + client_secret: "clientSecret".into(), + redirect_uris: vec![ + "https://demo.photos.network/callback".into(), + "http://127.0.0.1:7777/callback".into(), + "photosapp://authenticate".into() + ] + }; + + assert_eq!(data, serde_json::from_str(json).unwrap()); + } + + #[test] + fn test_minimal_deserialization() { + // given + let json = r#"{ + "name": "Client", + "client_id": "clientId", + "client_secret": "clientSecret", + "redirect_uris": [] + }"#; + + let data = OAuthClientConfig { + name: "Client".into(), + client_id: "clientId".into(), + client_secret: "clientSecret".into(), + redirect_uris: vec![] + }; + + assert_eq!(data, serde_json::from_str(json).unwrap()); + } +} diff --git a/src/config/configuration.rs b/src/config/configuration.rs new file mode 100644 index 0000000..0f6547b --- /dev/null +++ b/src/config/configuration.rs @@ -0,0 +1,133 @@ +/* Photos.network · A privacy first photo storage and sharing service for fediverse. + * Copyright (C) 2020 Photos network developers + * + * This program 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. + * + * This program 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +//! This defines the app configuration +use std::{fs, fmt}; + +use serde::Deserialize; +use tracing::info; + +use super::{client::OAuthClientConfig, plugin::Plugin}; + + +#[derive(Debug, PartialEq, Deserialize, Clone)] +pub struct Configuration { + pub internal_url: String, + pub external_url: String, + pub clients: Vec, + pub plugins: Vec, +} + +impl Configuration { + pub fn new(path: &str) -> Option { + info!("Load configuration file {}", path); + let data = fs::read_to_string(path).expect("Unable to read configuration file!"); + let config: Configuration = serde_json::from_str(&data).expect("Configuration file could not be parsed as JSON!"); + + Some(config) + } +} + +impl fmt::Display for Configuration { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let clients = &self.clients; + let plugins = &self.plugins; + + write!(f, "{{")?; + write!(f, "\n\tinternal: {}", self.internal_url)?; + write!(f, "\n\texternal: {}", self.external_url)?; + + // clients + write!(f, "\n\tclients: [ ")?; + for (count, v) in clients.iter().enumerate() { + if count != 0 { write!(f, ", ")?; } + write!(f, "\n\t\t{}", v)?; + } + write!(f, "\n\t] ")?; + + // plugins + write!(f, "\n\tplugins: [ ")?; + for (count, v) in plugins.iter().enumerate() { + if count != 0 { write!(f, ", ")?; } + write!(f, "\n\t\t{}", v)?; + } + write!(f, "\n\t]")?; + write!(f, "\n}}") + } +} + + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::Map; + + #[test] + fn test_full_deserialization() { + // given + let json = r#"{ + "internal_url": "192.168.0.1", + "external_url": "demo.photos.network", + "clients": [ + { + "name": "Client", + "client_id": "clientId", + "client_secret": "clientSecret", + "redirect_uris": [] + } + ], + "plugins": [ + { + "name": "Plugin", + "config": { + "property1": null, + "property2": true, + "property3": "aBc", + "property4": 42 + } + } + ] + }"#; + + let mut config = Map::new(); + config.insert("property1".to_string(), serde_json::Value::Null); + config.insert("property2".to_string(), serde_json::Value::Bool(true)); + config.insert("property3".to_string(), serde_json::Value::String("aBc".into())); + config.insert("property4".to_string(), serde_json::Value::Number(42.into())); + + let data = Configuration { + internal_url: "192.168.0.1".into(), + external_url: "demo.photos.network".into(), + clients: vec![ + OAuthClientConfig { + name: "Client".into(), + client_id: "clientId".into(), + client_secret: "clientSecret".into(), + redirect_uris: vec![] + } + ], + plugins: vec![ + Plugin { + name: "Plugin".into(), + config: Some(config), + } + ] + }; + + assert_eq!(data, serde_json::from_str(json).unwrap()); + } +} diff --git a/src/config/mod.rs b/src/config/mod.rs index 783ced2..7cf549f 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,33 +1,3 @@ - -use std::fs; - -use serde::Deserialize; -use serde_json::Map; -use tracing::{debug, info}; - -#[derive(Debug, Deserialize, Clone)] -pub struct Configuration { - pub internal_url: String, - pub external_url: String, - pub plugins: Vec, -} - -#[derive(Debug, Deserialize, Clone)] -pub struct Plugin { - pub name: String, - pub config: Option> -} - -impl Configuration { - pub fn new(path: &str) -> Option { - info!("Load configuration file {}", path); - let data = fs::read_to_string(path).expect("Unable to read configuration file!"); - let config: Configuration = serde_json::from_str(&data).expect("Configuration file could not be parsed as JSON!"); - - debug!("internal: {}", config.internal_url); - debug!("external: {}", config.external_url); - debug!("plugins: {:?}", config.plugins); - - Some(config) - } -} +pub mod client; +pub mod plugin; +pub mod configuration; diff --git a/src/config/plugin.rs b/src/config/plugin.rs new file mode 100644 index 0000000..919286f --- /dev/null +++ b/src/config/plugin.rs @@ -0,0 +1,81 @@ +/* Photos.network · A privacy first photo storage and sharing service for fediverse. + * Copyright (C) 2020 Photos network developers + * + * This program 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. + * + * This program 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +//! This describes a plugin with a key-value pair configuration +use std::fmt; + +use serde::{Deserialize}; +use serde_json::Map; + +#[derive(Debug, PartialEq, Deserialize, Clone)] +pub struct Plugin { + pub name: String, + pub config: Option> +} + +impl fmt::Display for Plugin { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.name) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_full_deserialization() { + // given + let json = r#"{ + "name": "Plugin", + "config": { + "property1": null, + "property2": true, + "property3": "aBc", + "property4": 42 + } + }"#; + + let mut config = Map::new(); + config.insert("property1".to_string(), serde_json::Value::Null); + config.insert("property2".to_string(), serde_json::Value::Bool(true)); + config.insert("property3".to_string(), serde_json::Value::String("aBc".into())); + config.insert("property4".to_string(), serde_json::Value::Number(42.into())); + + let data = Plugin { + name: "Plugin".into(), + config: Some(config), + }; + + assert_eq!(data, serde_json::from_str(json).unwrap()); + } + + #[test] + fn test_minimal_deserialization() { + // given + let json = r#"{ + "name": "Plugin" + }"#; + + let data = Plugin { + name: "Plugin".into(), + config: None, + }; + + assert_eq!(data, serde_json::from_str(json).unwrap()); + } +} diff --git a/src/lib.rs b/src/lib.rs index c7122d1..80be216 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -33,7 +33,7 @@ use tower_http::trace::TraceLayer; use tracing::{error, info, debug}; use tracing_subscriber::{fmt, layer::SubscriberExt}; -use config::Configuration; +use config::configuration::Configuration; use plugin::plugin_manager::PluginManager; pub mod config; @@ -46,7 +46,7 @@ const CONFIG_PATH: &str = "./config/configuration.json"; const PLUGIN_PATH: &str = "./plugins"; const LOGGING_PATH: &str = "./logs"; -/// extract the server start into lib.rs for better testability +/// server start extracted from main for testability pub async fn start_server() -> Result<()> { // enable logging let file_appender = tracing_appender::rolling::daily(LOGGING_PATH, "core"); @@ -68,13 +68,17 @@ pub async fn start_server() -> Result<()> { info!("Photos.network core is starting..."); + // create mandatory application directories if necessary fs::create_dir_all("data")?; fs::create_dir_all("config")?; fs::create_dir_all("plugins")?; + // read config file let config = Configuration::new(CONFIG_PATH).expect("Could not parse configuration!"); + debug!("Configuration: {}", config); + // init application state let mut app_state = ApplicationState::new(config.clone()); @@ -94,6 +98,7 @@ pub async fn start_server() -> Result<()> { ; app_state.router = Some(router); + // initialize plugin manager let mut plugin_manager = PluginManager::new(config.clone(), PLUGIN_PATH.to_string(), &mut app_state)?; @@ -103,6 +108,7 @@ pub async fn start_server() -> Result<()> { } plugin_manager.trigger_on_init().await; + // trigger `on_core_init` on all loaded plugins for (plugin_id, factory) in app_state.plugins { info!("Plugin '{}' found in AppState.", plugin_id); @@ -127,12 +133,14 @@ pub async fn start_server() -> Result<()> { plugin.on_core_init(); } + // TODO: add routes lazy (e.g. from plugin) router = app_state .router .unwrap() .route("/test", get(|| async { "Test from within plugin" })); + // start server with all routes let addr: SocketAddr = SocketAddr::from(([0, 0, 0, 0], 7777)); tracing::debug!("listening on {}", addr); diff --git a/src/plugin/mod.rs b/src/plugin/mod.rs index 884c331..e7201b2 100644 --- a/src/plugin/mod.rs +++ b/src/plugin/mod.rs @@ -1,3 +1,20 @@ +/* Photos.network · A privacy first photo storage and sharing service for fediverse. + * Copyright (C) 2020 Photos network developers + * + * This program 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. + * + * This program 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + //! The PluginManager will setup & initialize configured and available plugins. //! //! diff --git a/src/plugin/plugin_manager.rs b/src/plugin/plugin_manager.rs index 93a4ec2..612cf3f 100644 --- a/src/plugin/plugin_manager.rs +++ b/src/plugin/plugin_manager.rs @@ -1,10 +1,27 @@ +/* Photos.network · A privacy first photo storage and sharing service for fediverse. + * Copyright (C) 2020 Photos network developers + * + * This program 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. + * + * This program 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + use std::path::PathBuf; use abi_stable::library::{lib_header_from_path, LibrarySuffix, RawLibrary}; use anyhow::Result; -use crate::{config::Configuration, ApplicationState}; +use crate::{config::configuration::Configuration, ApplicationState}; use core_extensions::SelfOps; use photos_network_plugin::{PluginFactory_Ref, PluginId}; use tracing::{debug, error, info};