Added local api server + geoffrey_cli
ci/woodpecker/push/woodpecker Pipeline failed Details

+ Local api server is designed to allow local access to Geoffrey without a token
+ Uses a unix domain socket, allowing permissions to be handled by the OS
+ Started work on a tool to exploit this, geoffrey-cli
main
Joey Hines 2022-02-06 15:32:33 -07:00
parent c3c3814d77
commit 333329c631
No known key found for this signature in database
GPG Key ID: 80F567B5C968F91B
29 changed files with 612 additions and 237 deletions

91
Cargo.lock generated
View File

@ -158,7 +158,7 @@ dependencies = [
"libc",
"num-integer",
"num-traits 0.2.14",
"serde 1.0.133",
"serde 1.0.136",
"time",
"winapi",
]
@ -198,7 +198,7 @@ dependencies = [
"lazy_static",
"nom",
"rust-ini",
"serde 1.0.133",
"serde 1.0.136",
"serde-hjson",
"serde_json",
"toml",
@ -369,6 +369,17 @@ version = "0.3.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1f9d34af5a1aac6fb380f735fe510746c38067c5bf16c7fd250280503c971b2"
[[package]]
name = "futures-macro"
version = "0.3.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6dbd947adfffb0efc70599b3ddcf7b5597bb5fa9e245eb99f62b3a5f7bb8bd3c"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "futures-sink"
version = "0.3.19"
@ -390,6 +401,7 @@ dependencies = [
"futures-channel",
"futures-core",
"futures-io",
"futures-macro",
"futures-sink",
"futures-task",
"memchr",
@ -428,12 +440,13 @@ dependencies = [
"log",
"rand 0.8.4",
"regex",
"serde 1.0.133",
"serde 1.0.136",
"serde_json",
"simple_logger",
"strsim 0.10.0",
"structopt",
"tokio",
"tokio-stream",
"warp",
]
@ -447,7 +460,7 @@ dependencies = [
"geoffrey_models",
"log",
"reqwest",
"serde 1.0.133",
"serde 1.0.136",
"serde_json",
"serde_plain",
"serenity",
@ -456,6 +469,18 @@ dependencies = [
"tokio",
]
[[package]]
name = "geoffrey_cli"
version = "0.1.0"
dependencies = [
"geoffrey_models",
"hyper",
"hyperlocal",
"serde 1.0.136",
"serde_json",
"tokio",
]
[[package]]
name = "geoffrey_db"
version = "0.1.0"
@ -466,7 +491,7 @@ dependencies = [
"lazy_static",
"log",
"regex",
"serde 1.0.133",
"serde 1.0.136",
"serde_json",
"sled",
]
@ -477,7 +502,7 @@ version = "0.1.0"
dependencies = [
"chrono",
"log",
"serde 1.0.133",
"serde 1.0.136",
"serde_json",
]
@ -571,6 +596,12 @@ dependencies = [
"libc",
]
[[package]]
name = "hex"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "http"
version = "0.2.6"
@ -655,6 +686,19 @@ dependencies = [
"tokio-native-tls",
]
[[package]]
name = "hyperlocal"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fafdf7b2b2de7c9784f76e02c0935e65a8117ec3b768644379983ab333ac98c"
dependencies = [
"futures-util",
"hex",
"hyper",
"pin-project",
"tokio",
]
[[package]]
name = "idna"
version = "0.2.3"
@ -1225,15 +1269,16 @@ dependencies = [
[[package]]
name = "reqwest"
version = "0.11.8"
version = "0.11.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c4e0a76dc12a116108933f6301b95e83634e0c47b0afbed6abbaa0601e99258"
checksum = "87f242f1488a539a79bac6dbe7c8609ae43b7914b7736210f239a37cccb32525"
dependencies = [
"base64 0.13.0",
"bytes 1.1.0",
"encoding_rs",
"futures-core",
"futures-util",
"h2",
"http",
"http-body",
"hyper",
@ -1250,7 +1295,7 @@ dependencies = [
"pin-project-lite",
"rustls 0.20.2",
"rustls-pemfile",
"serde 1.0.133",
"serde 1.0.136",
"serde_json",
"serde_urlencoded",
"tokio",
@ -1405,9 +1450,9 @@ checksum = "9dad3f759919b92c3068c696c15c3d17238234498bbdcc80f2c469606f948ac8"
[[package]]
name = "serde"
version = "1.0.133"
version = "1.0.136"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97565067517b60e2d1ea8b268e59ce036de907ac523ad83a0475da04e818989a"
checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789"
dependencies = [
"serde_derive",
]
@ -1426,9 +1471,9 @@ dependencies = [
[[package]]
name = "serde_derive"
version = "1.0.133"
version = "1.0.136"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed201699328568d8d08208fdd080e3ff594e6c422e438b6705905da01005d537"
checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9"
dependencies = [
"proc-macro2",
"quote",
@ -1437,13 +1482,13 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.74"
version = "1.0.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee2bb9cd061c5865d345bb02ca49fcef1391741b672b54a0bf7b679badec3142"
checksum = "d23c1ba4cf0efd44be32017709280b32d1cea5c3f1275c3b6d9e8bc54f758085"
dependencies = [
"itoa 1.0.1",
"ryu",
"serde 1.0.133",
"serde 1.0.136",
]
[[package]]
@ -1452,7 +1497,7 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95455e7e29fada2052e72170af226fbe368a4ca33dee847875325d9fdb133858"
dependencies = [
"serde 1.0.133",
"serde 1.0.136",
]
[[package]]
@ -1464,7 +1509,7 @@ dependencies = [
"form_urlencoded",
"itoa 0.4.8",
"ryu",
"serde 1.0.133",
"serde 1.0.136",
]
[[package]]
@ -1482,7 +1527,7 @@ dependencies = [
"futures",
"percent-encoding",
"reqwest",
"serde 1.0.133",
"serde 1.0.136",
"serde_json",
"tokio",
"tracing",
@ -1692,9 +1737,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]]
name = "tokio"
version = "1.15.0"
version = "1.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbbf1c778ec206785635ce8ad57fe52b3009ae9e0c9f574a728f3049d3e55838"
checksum = "0c27a64b625de6d309e8c57716ba93021dccf1b3b5c97edd6d3dd2d2135afc0a"
dependencies = [
"bytes 1.1.0",
"libc",
@ -1796,7 +1841,7 @@ version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa"
dependencies = [
"serde 1.0.133",
"serde 1.0.136",
]
[[package]]
@ -2016,7 +2061,7 @@ dependencies = [
"percent-encoding",
"pin-project",
"scoped-tls",
"serde 1.0.133",
"serde 1.0.136",
"serde_json",
"serde_urlencoded",
"tokio",

View File

@ -3,5 +3,6 @@ members = [
"geoffrey_models",
"geoffrey_db",
"geoffrey_api",
"geoffrey_bot"
"geoffrey_bot",
"geoffrey_cli"
]

View File

@ -16,6 +16,7 @@ own library for reuse.
* [`geoffrey_api`](./geoffrey_api): API wrapper around the database to provide data to the website, bot, plugin, etc.
It will implement the command API and the model API. A lot of Geoffrey's logic is implemented here.
* [`geoffrey_bot`](./geoffrey_bot): Discord bot for Geoffrey. Uses [serenity](https://github.com/serenity-rs/serenity)
* ['geoffrey_cli`](./geoffrey_cli): CLI tool for interacting with Geoffrey locally or on a remote server.
## License
[License](LICENSE)

View File

@ -7,7 +7,7 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
tokio = { version = "1", features = ["full"] }
tokio = { version = "1.16.1", features = ["full"] }
warp = "0.3"
serde = "1.0.124"
serde_json = "1.0.64"
@ -20,6 +20,7 @@ rand = "0.8.4"
regex = "1.5.4"
chrono = { version = "0.4.19", features = ["serde"] }
strsim = "0.10.0"
tokio-stream = { version = "0.1.8", features = ["net"] }
# Doing this for now, as there seems to be an issue with using timestamps
[dependencies.simple_logger]

View File

@ -0,0 +1,11 @@
#[derive(Debug, Clone, PartialEq)]
#[allow(clippy::upper_case_acronyms)]
pub enum RequestType {
POST,
GET,
}
pub trait ApiEndpoint {
fn endpoint_name() -> String;
fn request_type() -> RequestType;
}

View File

@ -1,7 +1,5 @@
use crate::commands::{Command, RequestType};
use crate::context::Context;
use crate::helper::validate_string_parameter;
use crate::Result;
use std::sync::Arc;
use geoffrey_db::helper::load_location;
use geoffrey_db::query::QueryBuilder;
use geoffrey_models::models::item::ItemListing;
@ -11,21 +9,28 @@ use geoffrey_models::models::player::Player;
use geoffrey_models::models::response::api_error::GeoffreyAPIError;
use geoffrey_models::models::settings::GeoffreySettings;
use geoffrey_models::models::CommandLevel;
use std::sync::Arc;
use crate::api_endpoint::{ApiEndpoint, RequestType};
use crate::commands::Command;
use crate::context::Context;
use crate::helper::validate_string_parameter;
use crate::Result;
pub struct AddItem {}
impl Command for AddItem {
type Req = AddItemParams;
type Resp = Location;
fn command_name() -> String {
impl ApiEndpoint for AddItem {
fn endpoint_name() -> String {
"add_item".to_string()
}
fn request_type() -> RequestType {
RequestType::POST
}
}
impl Command for AddItem {
type Req = AddItemParams;
type Resp = Location;
fn command_level() -> CommandLevel {
CommandLevel::REGISTERED

View File

@ -1,28 +1,33 @@
use crate::commands::{Command, RequestType};
use crate::context::Context;
use crate::helper::validate_string_parameter;
use crate::Result;
use std::sync::Arc;
use geoffrey_db::helper::load_location;
use geoffrey_models::models::locations::{Location, LocationDb};
use geoffrey_models::models::parameters::add_location_params::AddLocationParams;
use geoffrey_models::models::player::Player;
use geoffrey_models::models::settings::GeoffreySettings;
use geoffrey_models::models::CommandLevel;
use std::sync::Arc;
use crate::api_endpoint::{ApiEndpoint, RequestType};
use crate::commands::Command;
use crate::context::Context;
use crate::helper::validate_string_parameter;
use crate::Result;
pub struct AddLocation {}
impl Command for AddLocation {
type Req = AddLocationParams;
type Resp = Location;
fn command_name() -> String {
impl ApiEndpoint for AddLocation {
fn endpoint_name() -> String {
"add_location".to_string()
}
fn request_type() -> RequestType {
RequestType::POST
}
}
impl Command for AddLocation {
type Req = AddLocationParams;
type Resp = Location;
fn command_level() -> CommandLevel {
CommandLevel::REGISTERED

View File

@ -1,28 +1,34 @@
use crate::commands::{Command, RequestType};
use crate::context::Context;
use crate::Result;
use std::sync::Arc;
use rand::distributions::Alphanumeric;
use rand::Rng;
use geoffrey_models::models::parameters::add_token_params::AddTokenParams;
use geoffrey_models::models::player::Player;
use geoffrey_models::models::response::api_error::GeoffreyAPIError;
use geoffrey_models::models::token::Token;
use geoffrey_models::models::CommandLevel;
use rand::distributions::Alphanumeric;
use rand::Rng;
use std::sync::Arc;
use crate::api_endpoint::{ApiEndpoint, RequestType};
use crate::commands::Command;
use crate::context::Context;
use crate::Result;
pub struct AddToken {}
impl Command for AddToken {
type Req = AddTokenParams;
type Resp = Token;
fn command_name() -> String {
impl ApiEndpoint for AddToken {
fn endpoint_name() -> String {
"add_token".to_string()
}
fn request_type() -> RequestType {
RequestType::POST
}
}
impl Command for AddToken {
type Req = AddTokenParams;
type Resp = Token;
fn command_level() -> CommandLevel {
CommandLevel::ADMIN

View File

@ -1,6 +1,5 @@
use crate::commands::{Command, RequestType};
use crate::context::Context;
use crate::Result;
use std::sync::Arc;
use geoffrey_db::helper::load_location;
use geoffrey_db::query::QueryBuilder;
use geoffrey_models::models::locations::{Location, LocationDb};
@ -8,21 +7,27 @@ use geoffrey_models::models::parameters::delete_params::DeleteParams;
use geoffrey_models::models::player::Player;
use geoffrey_models::models::response::api_error::GeoffreyAPIError;
use geoffrey_models::models::CommandLevel;
use std::sync::Arc;
use crate::api_endpoint::{ApiEndpoint, RequestType};
use crate::commands::Command;
use crate::context::Context;
use crate::Result;
pub struct Delete {}
impl Command for Delete {
type Req = DeleteParams;
type Resp = Location;
fn command_name() -> String {
impl ApiEndpoint for Delete {
fn endpoint_name() -> String {
"delete".to_string()
}
fn request_type() -> RequestType {
RequestType::POST
}
}
impl Command for Delete {
type Req = DeleteParams;
type Resp = Location;
fn command_level() -> CommandLevel {
CommandLevel::REGISTERED

View File

@ -1,6 +1,5 @@
use crate::commands::{Command, RequestType};
use crate::context::Context;
use crate::Result;
use std::sync::Arc;
use geoffrey_db::helper::load_location;
use geoffrey_db::query::QueryBuilder;
use geoffrey_models::models::locations::{Location, LocationDb};
@ -8,21 +7,27 @@ use geoffrey_models::models::parameters::edit_params::EditParams;
use geoffrey_models::models::player::Player;
use geoffrey_models::models::response::api_error::GeoffreyAPIError;
use geoffrey_models::models::CommandLevel;
use std::sync::Arc;
use crate::api_endpoint::{ApiEndpoint, RequestType};
use crate::commands::Command;
use crate::context::Context;
use crate::Result;
pub struct Edit {}
impl Command for Edit {
type Req = EditParams;
type Resp = Location;
fn command_name() -> String {
impl ApiEndpoint for Edit {
fn endpoint_name() -> String {
"edit".to_string()
}
fn request_type() -> RequestType {
RequestType::POST
}
}
impl Command for Edit {
type Req = EditParams;
type Resp = Location;
fn command_level() -> CommandLevel {
CommandLevel::REGISTERED

View File

@ -1,27 +1,32 @@
use crate::commands::{Command, RequestType};
use crate::context::Context;
use crate::Result;
use std::sync::Arc;
use geoffrey_db::helper::load_location;
use geoffrey_models::models::locations::{Location, LocationDb};
use geoffrey_models::models::parameters::find_params::FindParams;
use geoffrey_models::models::player::Player;
use geoffrey_models::models::response::api_error::GeoffreyAPIError;
use geoffrey_models::models::CommandLevel;
use std::sync::Arc;
use crate::api_endpoint::{ApiEndpoint, RequestType};
use crate::commands::Command;
use crate::context::Context;
use crate::Result;
pub struct FindCommand {}
impl Command for FindCommand {
type Req = FindParams;
type Resp = Vec<Location>;
fn command_name() -> String {
impl ApiEndpoint for FindCommand {
fn endpoint_name() -> String {
"find".to_string()
}
fn request_type() -> RequestType {
RequestType::GET
}
}
impl Command for FindCommand {
type Req = FindParams;
type Resp = Vec<Location>;
fn command_level() -> CommandLevel {
CommandLevel::ALL

View File

@ -1,6 +1,7 @@
use crate::commands::{Command, RequestType};
use crate::context::Context;
use crate::Result;
use std::sync::Arc;
use strsim::normalized_damerau_levenshtein;
use geoffrey_db::helper::load_location;
use geoffrey_db::query::QueryBuilder;
use geoffrey_models::models::locations::{Location, LocationDb};
@ -8,22 +9,27 @@ use geoffrey_models::models::parameters::info_params::InfoParams;
use geoffrey_models::models::player::Player;
use geoffrey_models::models::response::api_error::GeoffreyAPIError;
use geoffrey_models::models::CommandLevel;
use std::sync::Arc;
use strsim::normalized_damerau_levenshtein;
use crate::api_endpoint::{ApiEndpoint, RequestType};
use crate::commands::Command;
use crate::context::Context;
use crate::Result;
pub struct InfoCommand {}
impl Command for InfoCommand {
type Req = InfoParams;
type Resp = Location;
fn command_name() -> String {
impl ApiEndpoint for InfoCommand {
fn endpoint_name() -> String {
"info".to_string()
}
fn request_type() -> RequestType {
RequestType::GET
}
}
impl Command for InfoCommand {
type Req = InfoParams;
type Resp = Location;
fn command_level() -> CommandLevel {
CommandLevel::ALL

View File

@ -1,29 +1,35 @@
use crate::commands::{Command, RequestType};
use crate::context::Context;
use crate::Result;
use std::sync::Arc;
use chrono::{Duration, Utc};
use rand::distributions::Alphanumeric;
use rand::Rng;
use geoffrey_models::models::link::Link;
use geoffrey_models::models::parameters::link_params::LinkParameters;
use geoffrey_models::models::player::Player;
use geoffrey_models::models::CommandLevel;
use geoffrey_models::GeoffreyDatabaseModel;
use rand::distributions::Alphanumeric;
use rand::Rng;
use std::sync::Arc;
use crate::api_endpoint::{ApiEndpoint, RequestType};
use crate::commands::Command;
use crate::context::Context;
use crate::Result;
pub struct LinkCommand {}
impl Command for LinkCommand {
type Req = LinkParameters;
type Resp = Link;
fn command_name() -> String {
impl ApiEndpoint for LinkCommand {
fn endpoint_name() -> String {
"link".to_string()
}
fn request_type() -> RequestType {
RequestType::POST
}
}
impl Command for LinkCommand {
type Req = LinkParameters;
type Resp = Link;
fn command_level() -> CommandLevel {
CommandLevel::REGISTERED

View File

@ -1,3 +1,20 @@
use std::fmt::Debug;
use std::sync::Arc;
use serde::de::DeserializeOwned;
use serde::Serialize;
use warp::filters::BoxedFilter;
use warp::Filter;
use geoffrey_models::models::parameters::{CommandRequest, GeoffreyParam};
use geoffrey_models::models::player::Player;
use geoffrey_models::models::response::api_error::GeoffreyAPIError;
use geoffrey_models::models::response::APIResponse;
use geoffrey_models::models::settings::GeoffreySettings;
use geoffrey_models::models::token::{Permissions, Token};
use geoffrey_models::models::CommandLevel;
use crate::api_endpoint::{ApiEndpoint, RequestType};
use crate::commands::add_item::AddItem;
use crate::commands::add_location::AddLocation;
use crate::commands::delete::Delete;
@ -11,23 +28,9 @@ use crate::commands::report_out_of_stock::ReportOutOfStock;
use crate::commands::restock::Restock;
use crate::commands::selling::Selling;
use crate::commands::set_portal::SetPortal;
use crate::commands::settings::Settings;
use crate::context::Context;
use crate::helper::{get_player_from_req, get_token_from_req};
use crate::Result;
use geoffrey_models::models::parameters::{CommandRequest, GeoffreyParam};
use geoffrey_models::models::player::Player;
use geoffrey_models::models::response::api_error::GeoffreyAPIError;
use geoffrey_models::models::response::APIResponse;
use geoffrey_models::models::settings::GeoffreySettings;
use geoffrey_models::models::token::{Permissions, Token};
use geoffrey_models::models::CommandLevel;
use serde::de::DeserializeOwned;
use serde::Serialize;
use std::fmt::Debug;
use std::sync::Arc;
use warp::filters::BoxedFilter;
use warp::Filter;
pub mod add_item;
pub mod add_location;
@ -45,19 +48,10 @@ pub mod selling;
pub mod set_portal;
pub mod settings;
#[derive(Debug, Clone, PartialEq)]
#[allow(clippy::upper_case_acronyms)]
pub enum RequestType {
POST,
GET,
}
pub trait Command {
pub trait Command: ApiEndpoint {
type Req: GeoffreyParam + 'static;
type Resp: Serialize + DeserializeOwned + Send + Debug;
fn command_name() -> String;
fn request_type() -> RequestType;
fn command_level() -> CommandLevel;
fn run_command(ctx: Arc<Context>, req: &Self::Req, user: Option<Player>) -> Result<Self::Resp>;
@ -96,11 +90,11 @@ pub fn handle_command<T: Command>(
ctx: Arc<Context>,
req: CommandRequest<T::Req>,
) -> Result<T::Resp> {
log::info!("Running command {}", T::command_name());
log::info!("Running command {}", T::endpoint_name());
log::debug!("User: {:?} Request params: {:?}", req.user_id, req.params);
let user = get_player_from_req::<T::Req>(&ctx.db, &req)?;
let token = get_token_from_req::<T::Req>(&ctx.db, &req)?;
let token = get_token_from_req(&ctx.db, &req)?;
match T::user_is_authorized(&token, &user) {
Ok(_) => {
@ -113,7 +107,7 @@ pub fn handle_command<T: Command>(
#[allow(clippy::needless_return)]
pub fn create_command_filter<T: Command>(ctx: Arc<Context>) -> BoxedFilter<(impl warp::Reply,)> {
let filter = warp::path(T::command_name())
let filter = warp::path(T::endpoint_name())
.and(warp::any().map(move || ctx.clone()))
.and(warp::body::json())
.map(|ctx: Arc<Context>, req: CommandRequest<T::Req>| {
@ -155,9 +149,3 @@ pub fn command_filter(
.or(create_command_filter::<SetPortal>(ctx)),
)
}
pub fn model_filter(
ctx: Arc<Context>,
) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
warp::path("model").and(create_command_filter::<Settings>(ctx))
}

View File

@ -1,27 +1,32 @@
use crate::commands::{Command, RequestType};
use crate::context::Context;
use crate::Result;
use std::sync::Arc;
use geoffrey_models::models::link::Link;
use geoffrey_models::models::parameters::register_params::RegisterParameters;
use geoffrey_models::models::player::{Player, UserID};
use geoffrey_models::models::response::api_error::GeoffreyAPIError;
use geoffrey_models::models::CommandLevel;
use geoffrey_models::GeoffreyDatabaseModel;
use std::sync::Arc;
use crate::api_endpoint::{ApiEndpoint, RequestType};
use crate::commands::Command;
use crate::context::Context;
use crate::Result;
pub struct Register {}
impl Command for Register {
type Req = RegisterParameters;
type Resp = Player;
fn command_name() -> String {
impl ApiEndpoint for Register {
fn endpoint_name() -> String {
"register".to_string()
}
fn request_type() -> RequestType {
RequestType::POST
}
}
impl Command for Register {
type Req = RegisterParameters;
type Resp = Player;
fn command_level() -> CommandLevel {
CommandLevel::ALL

View File

@ -1,6 +1,5 @@
use crate::commands::{Command, RequestType};
use crate::context::Context;
use crate::Result;
use std::sync::Arc;
use geoffrey_db::helper::load_location;
use geoffrey_db::query::QueryBuilder;
use geoffrey_models::models::locations::{Location, LocationDataDb, LocationDb, LocationType};
@ -8,21 +7,27 @@ use geoffrey_models::models::parameters::item_command_params::ItemCommandParams;
use geoffrey_models::models::player::Player;
use geoffrey_models::models::response::api_error::GeoffreyAPIError;
use geoffrey_models::models::CommandLevel;
use std::sync::Arc;
use crate::api_endpoint::{ApiEndpoint, RequestType};
use crate::commands::Command;
use crate::context::Context;
use crate::Result;
pub struct RemoveItem {}
impl Command for RemoveItem {
type Req = ItemCommandParams;
type Resp = Location;
fn command_name() -> String {
impl ApiEndpoint for RemoveItem {
fn endpoint_name() -> String {
"remove_item".to_string()
}
fn request_type() -> RequestType {
RequestType::POST
}
}
impl Command for RemoveItem {
type Req = ItemCommandParams;
type Resp = Location;
fn command_level() -> CommandLevel {
CommandLevel::REGISTERED

View File

@ -1,6 +1,6 @@
use crate::commands::{Command, RequestType};
use crate::context::Context;
use crate::Result;
use std::collections::HashSet;
use std::sync::Arc;
use geoffrey_db::helper::load_location;
use geoffrey_db::query::QueryBuilder;
use geoffrey_models::models::item::ItemListing;
@ -9,22 +9,27 @@ use geoffrey_models::models::parameters::item_command_params::ItemCommandParams;
use geoffrey_models::models::player::Player;
use geoffrey_models::models::response::api_error::GeoffreyAPIError;
use geoffrey_models::models::CommandLevel;
use std::collections::HashSet;
use std::sync::Arc;
use crate::api_endpoint::{ApiEndpoint, RequestType};
use crate::commands::Command;
use crate::context::Context;
use crate::Result;
pub struct ReportOutOfStock {}
impl Command for ReportOutOfStock {
type Req = ItemCommandParams;
type Resp = Location;
fn command_name() -> String {
impl ApiEndpoint for ReportOutOfStock {
fn endpoint_name() -> String {
"report_out_of_stock".to_string()
}
fn request_type() -> RequestType {
RequestType::POST
}
}
impl Command for ReportOutOfStock {
type Req = ItemCommandParams;
type Resp = Location;
fn command_level() -> CommandLevel {
CommandLevel::REGISTERED

View File

@ -1,6 +1,6 @@
use crate::commands::{Command, RequestType};
use crate::context::Context;
use crate::Result;
use std::collections::HashSet;
use std::sync::Arc;
use geoffrey_db::helper::load_location;
use geoffrey_db::query::QueryBuilder;
use geoffrey_models::models::item::ItemListing;
@ -9,22 +9,27 @@ use geoffrey_models::models::parameters::item_command_params::ItemCommandParams;
use geoffrey_models::models::player::Player;
use geoffrey_models::models::response::api_error::GeoffreyAPIError;
use geoffrey_models::models::CommandLevel;
use std::collections::HashSet;
use std::sync::Arc;
use crate::api_endpoint::{ApiEndpoint, RequestType};
use crate::commands::Command;
use crate::context::Context;
use crate::Result;
pub struct Restock {}
impl Command for Restock {
type Req = ItemCommandParams;
type Resp = Location;
fn command_name() -> String {
impl ApiEndpoint for Restock {
fn endpoint_name() -> String {
"restock".to_string()
}
fn request_type() -> RequestType {
RequestType::POST
}
}
impl Command for Restock {
type Req = ItemCommandParams;
type Resp = Location;
fn command_level() -> CommandLevel {
CommandLevel::REGISTERED

View File

@ -1,27 +1,32 @@
use crate::commands::{Command, RequestType};
use crate::context::Context;
use crate::Result;
use std::sync::Arc;
use geoffrey_models::models::item::ItemListing;
use geoffrey_models::models::locations::{LocationDataDb, LocationDb};
use geoffrey_models::models::parameters::selling_params::{ItemSort, Order, SellingParams};
use geoffrey_models::models::player::Player;
use geoffrey_models::models::response::selling_listing::SellingListing;
use geoffrey_models::models::CommandLevel;
use std::sync::Arc;
use crate::api_endpoint::{ApiEndpoint, RequestType};
use crate::commands::Command;
use crate::context::Context;
use crate::Result;
pub struct Selling {}
impl Command for Selling {
type Req = SellingParams;
type Resp = Vec<SellingListing>;
fn command_name() -> String {
impl ApiEndpoint for Selling {
fn endpoint_name() -> String {
"selling".to_string()
}
fn request_type() -> RequestType {
RequestType::GET
}
}
impl Command for Selling {
type Req = SellingParams;
type Resp = Vec<SellingListing>;
fn command_level() -> CommandLevel {
CommandLevel::ALL
@ -99,19 +104,21 @@ impl Command for Selling {
#[cfg(test)]
mod test {
use crate::commands::selling::Selling;
use crate::commands::Command;
use crate::config::{GeoffreyAPIConfig, ServerConfig};
use crate::context::Context;
use crate::Args;
use std::path::PathBuf;
use std::time::Instant;
use geoffrey_models::models::item::ItemListing;
use geoffrey_models::models::locations::shop::Shop;
use geoffrey_models::models::locations::{LocationDataDb, LocationDb};
use geoffrey_models::models::parameters::selling_params::SellingParams;
use geoffrey_models::models::settings::GeoffreySettings;
use geoffrey_models::models::Position;
use std::path::PathBuf;
use std::time::Instant;
use crate::commands::selling::Selling;
use crate::commands::Command;
use crate::config::{GeoffreyAPIConfig, ServerConfig};
use crate::context::Context;
use crate::Args;
fn test_selling_lookup_speed(iter: i32, shop_count: i32, items_for_sale: i32) -> f32 {
let config = GeoffreyAPIConfig {

View File

@ -1,6 +1,5 @@
use crate::commands::{Command, RequestType};
use crate::context::Context;
use crate::Result;
use std::sync::Arc;
use geoffrey_db::helper::load_location;
use geoffrey_db::query::QueryBuilder;
use geoffrey_models::models::locations::{Location, LocationDb};
@ -8,21 +7,27 @@ use geoffrey_models::models::parameters::set_portal_params::SetPortalParams;
use geoffrey_models::models::player::Player;
use geoffrey_models::models::response::api_error::GeoffreyAPIError;
use geoffrey_models::models::CommandLevel;
use std::sync::Arc;
use crate::api_endpoint::{ApiEndpoint, RequestType};
use crate::commands::Command;
use crate::context::Context;
use crate::Result;
pub struct SetPortal {}
impl Command for SetPortal {
type Req = SetPortalParams;
type Resp = Location;
fn command_name() -> String {
impl ApiEndpoint for SetPortal {
fn endpoint_name() -> String {
"set_portal".to_string()
}
fn request_type() -> RequestType {
RequestType::POST
}
}
impl Command for SetPortal {
type Req = SetPortalParams;
type Resp = Location;
fn command_level() -> CommandLevel {
CommandLevel::REGISTERED

View File

@ -1,31 +1 @@
use crate::commands::{Command, RequestType};
use crate::context::Context;
use crate::Result;
use geoffrey_models::models::parameters::EmptyRequest;
use geoffrey_models::models::player::Player;
use geoffrey_models::models::settings::GeoffreySettings;
use geoffrey_models::models::CommandLevel;
use std::sync::Arc;
pub struct Settings {}
impl Command for Settings {
type Req = EmptyRequest;
type Resp = GeoffreySettings;
fn command_name() -> String {
"settings".to_string()
}
fn request_type() -> RequestType {
RequestType::GET
}
fn command_level() -> CommandLevel {
CommandLevel::ALL
}
fn run_command(ctx: Arc<Context>, _: &Self::Req, _: Option<Player>) -> Result<Self::Resp> {
Ok(ctx.cfg.geoffrey_settings.clone())
}
}

View File

@ -13,6 +13,7 @@ pub struct GeoffreyAPIConfig {
pub struct ServerConfig {
pub db_path: PathBuf,
pub host: String,
pub local_socket: Option<PathBuf>,
}
impl GeoffreyAPIConfig {

View File

@ -1,6 +1,6 @@
use crate::Result;
use geoffrey_db::database::Database;
use geoffrey_models::models::parameters::{CommandRequest, GeoffreyParam};
use geoffrey_models::models::parameters::{CommandRequest, GeoffreyParam, GeoffreyRequest};
use geoffrey_models::models::player::Player;
use geoffrey_models::models::response::api_error::GeoffreyAPIError;
use geoffrey_models::models::token::Token;
@ -18,12 +18,12 @@ pub fn get_player_from_req<T: GeoffreyParam>(
}
}
pub fn get_token_from_req<T: GeoffreyParam>(
pub fn get_token_from_req<T: GeoffreyParam, U: GeoffreyRequest<T>>(
db: &Database,
req: &CommandRequest<T>,
req: &U,
) -> Result<Option<Token>> {
Ok(db
.filter(|_, token: &Token| token.secret == req.token)?
.filter(|_, token: &Token| token.secret == req.get_token())?
.next())
}

View File

@ -8,22 +8,28 @@ use warp::Filter;
use structopt::StructOpt;
use tokio::net::UnixListener;
use geoffrey_models::logging::LogLevel;
use geoffrey_models::models::parameters::add_token_params::AddTokenParams;
use geoffrey_models::models::response::api_error::GeoffreyAPIError;
use geoffrey_models::models::token::Permissions;
use crate::commands::add_token::AddToken;
use crate::commands::{command_filter, model_filter, Command};
use crate::commands::{command_filter, Command};
use crate::config::GeoffreyAPIConfig;
use crate::context::Context;
use crate::logging::init_logging;
use crate::model::{local_model_filter, remote_model_filter};
use tokio_stream::wrappers::UnixListenerStream;
mod api_endpoint;
mod commands;
mod config;
mod context;
mod helper;
mod logging;
mod model;
pub type Result<T> = std::result::Result<T, GeoffreyAPIError>;
@ -66,7 +72,39 @@ pub struct CreateTokenCommand {
pub permissions: Vec<Permissions>,
}
async fn run_server(ctx: Arc<Context>) {
async fn run_local_server(ctx: &Arc<Context>) {
let socket_path = ctx
.cfg
.server_config
.local_socket
.as_ref()
.expect("Expected local_socket to be defined");
if socket_path.exists() {
if let Err(e) = std::fs::remove_file(&socket_path) {
log::error!("Unable to cleanup local socket: {}", e);
return;
}
}
let listener = match UnixListener::bind(socket_path) {
Ok(l) => l,
Err(e) => {
log::error!("Unable to open local socket: {}", e);
return;
}
};
let unix_listener_stream = UnixListenerStream::new(listener);
let local_api = local_model_filter(ctx.clone());
warp::serve(local_api.clone())
.run_incoming(unix_listener_stream)
.await;
}
async fn run_remote_server(ctx: &Arc<Context>) {
let socket_addr = match SocketAddr::from_str(ctx.cfg.server_config.host.as_str()) {
Ok(socket_addr) => socket_addr,
Err(e) => {
@ -79,11 +117,24 @@ async fn run_server(ctx: Arc<Context>) {
}
};
let api = command_filter(ctx.clone()).or(model_filter(ctx.clone()));
let api = command_filter(ctx.clone()).or(remote_model_filter(ctx.clone()));
warp::serve(api).run(socket_addr).await;
}
async fn run_server(ctx: Arc<Context>) {
tokio::select! {
// Start local api
_ = run_local_server(&ctx), if ctx.cfg.server_config.local_socket.is_some() => {
log::warn!("Socket API exited")
}
// Start HTTP api
_ = run_remote_server(&ctx) => {
log::warn!("HTTP API exited")
}
}
}
fn create_token(ctx: Arc<Context>, perms: Vec<Permissions>) {
match AddToken::run_command(ctx, &AddTokenParams { permissions: perms }, None) {
Ok(token) => {

View File

@ -0,0 +1,123 @@
mod settings;
use crate::api_endpoint::{ApiEndpoint, RequestType};
use crate::context::Context;
use crate::helper::get_token_from_req;
use crate::model::settings::Settings;
use crate::Result;
use geoffrey_models::models::parameters::{GeoffreyParam, ModelRequest};
use geoffrey_models::models::response::api_error::GeoffreyAPIError;
use geoffrey_models::models::response::APIResponse;
use geoffrey_models::models::token::{Permissions, Token};
use serde::de::DeserializeOwned;
use serde::Serialize;
use std::fmt::Debug;
use std::sync::Arc;
use warp::filters::BoxedFilter;
use warp::Filter;
pub trait ModelEndpoint: ApiEndpoint {
type Req: GeoffreyParam + 'static;
type Resp: Serialize + DeserializeOwned + Send + Debug;
fn token_permission() -> Vec<Permissions>;
fn run_endpoint(ctx: Arc<Context>, req: &Self::Req) -> Result<Self::Resp>;
fn check_token_permission(token: &Token) -> bool {
for perm in Self::token_permission() {
if !token.check_permission(perm) {
return false;
}
}
true
}
}
pub fn handle_remote_model_request<T: ModelEndpoint>(
ctx: Arc<Context>,
req: ModelRequest<T::Req>,
) -> Result<T::Resp> {
let token = get_token_from_req(&ctx.db, &req)?;
if let Some(token) = token {
if T::check_token_permission(&token) {
return T::run_endpoint(ctx, &req.params);
}
}
Err(GeoffreyAPIError::TokenNotAuthorized)
}
#[allow(clippy::needless_return)]
pub fn create_remote_model_filter<T: ModelEndpoint>(
ctx: Arc<Context>,
) -> BoxedFilter<(impl warp::Reply,)> {
let filter = warp::path(T::endpoint_name())
.and(warp::any().map(move || ctx.clone()))
.and(warp::body::json())
.map(|ctx: Arc<Context>, req: ModelRequest<T::Req>| {
log::info!("Running model query {}", T::endpoint_name());
log::debug!("Request params: {:?}", req.params);
let reply = handle_remote_model_request::<T>(ctx, req);
if let Ok(reply) = reply {
log::debug!("Successfully processed model request");
warp::reply::json(&APIResponse::Response::<T::Resp>(reply))
} else {
let e = reply.err().unwrap();
let msg = e.to_string();
log::warn!("Got error when processing model request '{:?}': {}", e, msg);
warp::reply::json(&APIResponse::<T::Resp>::Error { error: e, msg })
}
});
if T::request_type() == RequestType::POST {
return filter.and(warp::post()).boxed();
} else {
return filter.and(warp::get()).boxed();
}
}
#[allow(clippy::needless_return)]
pub fn create_local_model_filter<T: ModelEndpoint>(
ctx: Arc<Context>,
) -> BoxedFilter<(impl warp::Reply,)> {
let filter = warp::path(T::endpoint_name())
.and(warp::any().map(move || ctx.clone()))
.and(warp::body::json())
.map(|ctx: Arc<Context>, req: T::Req| {
log::info!("Running local model query {}", T::endpoint_name());
log::debug!("Request params: {:?}", req);
let reply = T::run_endpoint(ctx, &req);
if let Ok(reply) = reply {
log::debug!("Successfully processed model request");
warp::reply::json(&APIResponse::Response::<T::Resp>(reply))
} else {
let e = reply.err().unwrap();
let msg = e.to_string();
log::warn!("Got error when processing model request '{:?}': {}", e, msg);
warp::reply::json(&APIResponse::<T::Resp>::Error { error: e, msg })
}
});
if T::request_type() == RequestType::POST {
return filter.and(warp::post()).boxed();
} else {
return filter.and(warp::get()).boxed();
}
}
pub fn remote_model_filter(
ctx: Arc<Context>,
) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
warp::path("model").and(create_remote_model_filter::<Settings>(ctx))
}
pub fn local_model_filter(
ctx: Arc<Context>,
) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
warp::path("model").and(create_local_model_filter::<Settings>(ctx))
}

View File

@ -0,0 +1,35 @@
use std::sync::Arc;
use geoffrey_models::models::settings::GeoffreySettings;
use crate::api_endpoint::{ApiEndpoint, RequestType};
use crate::context::Context;
use crate::model::ModelEndpoint;
use crate::Result;
use geoffrey_models::models::parameters::EmptyRequest;
use geoffrey_models::models::token::Permissions;
pub struct Settings {}
impl ApiEndpoint for Settings {
fn endpoint_name() -> String {
"settings".to_string()
}
fn request_type() -> RequestType {
RequestType::GET
}
}
impl ModelEndpoint for Settings {
type Req = EmptyRequest;
type Resp = GeoffreySettings;
fn token_permission() -> Vec<Permissions> {
vec![Permissions::ModelGet]
}
fn run_endpoint(ctx: Arc<Context>, _: &Self::Req) -> Result<Self::Resp> {
Ok(ctx.cfg.geoffrey_settings.clone())
}
}

View File

@ -0,0 +1,14 @@
[package]
name = "geoffrey_cli"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
hyper = { version = "0.14", features = ["full"] }
hyperlocal = "0.8.0"
tokio = { version = "1", features = ["full"] }
geoffrey_models = { path = "../geoffrey_models" }
serde = "1.0.136"
serde_json = "1.0.78"

View File

@ -0,0 +1,28 @@
use geoffrey_models::models::parameters::EmptyRequest;
use hyper::body::{Body, HttpBody};
use hyper::Client;
use hyper::{Method, Request};
use hyperlocal::{UnixClientExt, Uri};
use tokio::io::{stdout, AsyncWriteExt as _};
#[tokio::main]
async fn main() {
let client = Client::unix();
let uri: hyper::Uri = Uri::new("/tmp/geoffrey.socket", "/model/settings/").into();
let params = EmptyRequest {};
let req = Request::builder()
.method(Method::GET)
.uri(uri)
.header("content-type", "application/json")
.body(Body::from(serde_json::to_string(&params).unwrap()))
.unwrap();
let mut resp = client.request(req).await.unwrap();
while let Some(chunk) = resp.body_mut().data().await {
stdout().write_all(&chunk.unwrap()).await.unwrap();
}
}

View File

@ -18,6 +18,11 @@ use std::fmt::Debug;
pub trait GeoffreyParam: Serialize + DeserializeOwned + Debug + Clone + Send + Sync {}
pub trait GeoffreyRequest<T> {
fn get_token(&self) -> String;
fn get_params(&self) -> &T;
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct CommandRequest<T> {
pub token: String,
@ -25,6 +30,32 @@ pub struct CommandRequest<T> {
pub params: T,
}
impl<T> GeoffreyRequest<T> for CommandRequest<T> {
fn get_token(&self) -> String {
self.token.clone()
}
fn get_params(&self) -> &T {
&self.params
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ModelRequest<T> {
pub token: String,
pub params: T,
}
impl<T> GeoffreyRequest<T> for ModelRequest<T> {
fn get_token(&self) -> String {
self.token.clone()
}
fn get_params(&self) -> &T {
&self.params
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct EmptyRequest {}