mirror of
https://github.com/lldap/lldap.git
synced 2026-07-25 17:58:44 +00:00
server: extract graphql crate
This commit is contained in:
committed by
nitnelave
parent
db77a0f023
commit
d38a2cd08b
Generated
+27
@@ -2544,6 +2544,7 @@ dependencies = [
|
|||||||
"lldap_domain_handlers",
|
"lldap_domain_handlers",
|
||||||
"lldap_domain_model",
|
"lldap_domain_model",
|
||||||
"lldap_frontend_options",
|
"lldap_frontend_options",
|
||||||
|
"lldap_graphql_server",
|
||||||
"lldap_ldap",
|
"lldap_ldap",
|
||||||
"lldap_opaque_handler",
|
"lldap_opaque_handler",
|
||||||
"lldap_sql_backend_handler",
|
"lldap_sql_backend_handler",
|
||||||
@@ -2712,6 +2713,32 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lldap_graphql_server"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"chrono",
|
||||||
|
"juniper",
|
||||||
|
"lldap_access_control",
|
||||||
|
"lldap_auth",
|
||||||
|
"lldap_domain",
|
||||||
|
"lldap_domain_handlers",
|
||||||
|
"lldap_domain_model",
|
||||||
|
"lldap_ldap",
|
||||||
|
"lldap_sql_backend_handler",
|
||||||
|
"lldap_test_utils",
|
||||||
|
"lldap_validation",
|
||||||
|
"mockall",
|
||||||
|
"pretty_assertions",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"tokio",
|
||||||
|
"tracing",
|
||||||
|
"urlencoding",
|
||||||
|
"uuid 1.11.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lldap_ldap"
|
name = "lldap_ldap"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
[package]
|
||||||
|
name = "lldap_graphql_server"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "GraphQL server for LLDAP"
|
||||||
|
edition.workspace = true
|
||||||
|
authors.workspace = true
|
||||||
|
homepage.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
repository.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = "*"
|
||||||
|
juniper = "0.15"
|
||||||
|
serde_json = "1"
|
||||||
|
tracing = "*"
|
||||||
|
urlencoding = "2"
|
||||||
|
|
||||||
|
[dependencies.chrono]
|
||||||
|
features = ["serde"]
|
||||||
|
version = "*"
|
||||||
|
|
||||||
|
[dependencies.lldap_access_control]
|
||||||
|
path = "../access-control"
|
||||||
|
|
||||||
|
[dependencies.lldap_auth]
|
||||||
|
path = "../auth"
|
||||||
|
features = ["opaque_server", "opaque_client", "sea_orm"]
|
||||||
|
|
||||||
|
[dependencies.lldap_domain]
|
||||||
|
path = "../domain"
|
||||||
|
|
||||||
|
[dependencies.lldap_domain_model]
|
||||||
|
path = "../domain-model"
|
||||||
|
|
||||||
|
[dependencies.lldap_domain_handlers]
|
||||||
|
path = "../domain-handlers"
|
||||||
|
|
||||||
|
[dependencies.lldap_ldap]
|
||||||
|
path = "../ldap"
|
||||||
|
|
||||||
|
[dependencies.lldap_sql_backend_handler]
|
||||||
|
path = "../sql-backend-handler"
|
||||||
|
|
||||||
|
[dependencies.lldap_validation]
|
||||||
|
path = "../validation"
|
||||||
|
|
||||||
|
[dependencies.serde]
|
||||||
|
workspace = true
|
||||||
|
|
||||||
|
[dependencies.uuid]
|
||||||
|
features = ["v1", "v3"]
|
||||||
|
version = "1"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
mockall = "0.11.4"
|
||||||
|
pretty_assertions = "1"
|
||||||
|
|
||||||
|
#[dev-dependencies.lldap_auth]
|
||||||
|
#path = "../auth"
|
||||||
|
#features = ["test"]
|
||||||
|
#
|
||||||
|
#[dev-dependencies.lldap_opaque_handler]
|
||||||
|
#path = "../opaque-handler"
|
||||||
|
#features = ["test"]
|
||||||
|
|
||||||
|
[dev-dependencies.lldap_test_utils]
|
||||||
|
path = "../test-utils"
|
||||||
|
#
|
||||||
|
#[dev-dependencies.lldap_sql_backend_handler]
|
||||||
|
#path = "../sql-backend-handler"
|
||||||
|
#features = ["test"]
|
||||||
|
|
||||||
|
[dev-dependencies.tokio]
|
||||||
|
features = ["full"]
|
||||||
|
version = "1.25"
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
use crate::{mutation::Mutation, query::Query};
|
||||||
|
use juniper::{EmptySubscription, FieldError, RootNode};
|
||||||
|
use lldap_access_control::{
|
||||||
|
AccessControlledBackendHandler, AdminBackendHandler, ReadonlyBackendHandler,
|
||||||
|
UserReadableBackendHandler, UserWriteableBackendHandler,
|
||||||
|
};
|
||||||
|
use lldap_auth::{access_control::ValidationResults, types::UserId};
|
||||||
|
use lldap_domain_handlers::handler::BackendHandler;
|
||||||
|
use tracing::debug;
|
||||||
|
|
||||||
|
pub struct Context<Handler: BackendHandler> {
|
||||||
|
pub handler: AccessControlledBackendHandler<Handler>,
|
||||||
|
pub validation_result: ValidationResults,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn field_error_callback<'a>(
|
||||||
|
span: &'a tracing::Span,
|
||||||
|
error_message: &'a str,
|
||||||
|
) -> impl 'a + FnOnce() -> FieldError {
|
||||||
|
move || {
|
||||||
|
span.in_scope(|| debug!("Unauthorized"));
|
||||||
|
FieldError::from(error_message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Handler: BackendHandler> Context<Handler> {
|
||||||
|
#[cfg(test)]
|
||||||
|
pub fn new_for_tests(handler: Handler, validation_result: ValidationResults) -> Self {
|
||||||
|
Self {
|
||||||
|
handler: AccessControlledBackendHandler::new(handler),
|
||||||
|
validation_result,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_admin_handler(&self) -> Option<&(impl AdminBackendHandler + use<Handler>)> {
|
||||||
|
self.handler.get_admin_handler(&self.validation_result)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_readonly_handler(&self) -> Option<&(impl ReadonlyBackendHandler + use<Handler>)> {
|
||||||
|
self.handler.get_readonly_handler(&self.validation_result)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_writeable_handler(
|
||||||
|
&self,
|
||||||
|
user_id: &UserId,
|
||||||
|
) -> Option<&(impl UserWriteableBackendHandler + use<Handler>)> {
|
||||||
|
self.handler
|
||||||
|
.get_writeable_handler(&self.validation_result, user_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_readable_handler(
|
||||||
|
&self,
|
||||||
|
user_id: &UserId,
|
||||||
|
) -> Option<&(impl UserReadableBackendHandler + use<Handler>)> {
|
||||||
|
self.handler
|
||||||
|
.get_readable_handler(&self.validation_result, user_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Handler: BackendHandler> juniper::Context for Context<Handler> {}
|
||||||
|
|
||||||
|
type Schema<Handler> =
|
||||||
|
RootNode<'static, Query<Handler>, Mutation<Handler>, EmptySubscription<Context<Handler>>>;
|
||||||
|
|
||||||
|
pub fn schema<Handler: BackendHandler>() -> Schema<Handler> {
|
||||||
|
Schema::new(
|
||||||
|
Query::<Handler>::new(),
|
||||||
|
Mutation::<Handler>::new(),
|
||||||
|
EmptySubscription::<Context<Handler>>::new(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn export_schema(output_file: Option<String>) -> anyhow::Result<()> {
|
||||||
|
use anyhow::Context;
|
||||||
|
use lldap_sql_backend_handler::SqlBackendHandler;
|
||||||
|
let output = schema::<SqlBackendHandler>().as_schema_language();
|
||||||
|
match output_file {
|
||||||
|
None => println!("{}", output),
|
||||||
|
Some(path) => {
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::prelude::*;
|
||||||
|
use std::path::Path;
|
||||||
|
let path = Path::new(&path);
|
||||||
|
let mut file =
|
||||||
|
File::create(path).context(format!("unable to open '{}'", path.display()))?;
|
||||||
|
file.write_all(output.as_bytes())
|
||||||
|
.context(format!("unable to write in '{}'", path.display()))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::infra::graphql::api::{Context, field_error_callback};
|
use crate::api::{Context, field_error_callback};
|
||||||
use anyhow::{Context as AnyhowContext, anyhow};
|
use anyhow::{Context as AnyhowContext, anyhow};
|
||||||
use juniper::{FieldError, FieldResult, GraphQLInputObject, GraphQLObject, graphql_object};
|
use juniper::{FieldError, FieldResult, GraphQLInputObject, GraphQLObject, graphql_object};
|
||||||
use lldap_access_control::{
|
use lldap_access_control::{
|
||||||
@@ -29,6 +29,12 @@ pub struct Mutation<Handler: BackendHandler> {
|
|||||||
_phantom: std::marker::PhantomData<Box<Handler>>,
|
_phantom: std::marker::PhantomData<Box<Handler>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<Handler: BackendHandler> Default for Mutation<Handler> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<Handler: BackendHandler> Mutation<Handler> {
|
impl<Handler: BackendHandler> Mutation<Handler> {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
@@ -778,7 +784,7 @@ fn deserialize_attribute(
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::infra::graphql::query::Query;
|
use crate::query::Query;
|
||||||
use juniper::{
|
use juniper::{
|
||||||
DefaultScalarValue, EmptySubscription, GraphQLType, InputValue, RootNode, Variables,
|
DefaultScalarValue, EmptySubscription, GraphQLType, InputValue, RootNode, Variables,
|
||||||
execute, graphql_value,
|
execute, graphql_value,
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::infra::graphql::api::{Context, field_error_callback};
|
use crate::api::{Context, field_error_callback};
|
||||||
use anyhow::Context as AnyhowContext;
|
use anyhow::Context as AnyhowContext;
|
||||||
use chrono::TimeZone;
|
use chrono::TimeZone;
|
||||||
use juniper::{FieldResult, GraphQLInputObject, graphql_object};
|
use juniper::{FieldResult, GraphQLInputObject, graphql_object};
|
||||||
@@ -110,6 +110,12 @@ pub struct Query<Handler: BackendHandler> {
|
|||||||
_phantom: std::marker::PhantomData<Box<Handler>>,
|
_phantom: std::marker::PhantomData<Box<Handler>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<Handler: BackendHandler> Default for Query<Handler> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<Handler: BackendHandler> Query<Handler> {
|
impl<Handler: BackendHandler> Query<Handler> {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
@@ -98,6 +98,9 @@ path = "../crates/domain-handlers"
|
|||||||
[dependencies.lldap_frontend_options]
|
[dependencies.lldap_frontend_options]
|
||||||
path = "../crates/frontend-options"
|
path = "../crates/frontend-options"
|
||||||
|
|
||||||
|
[dependencies.lldap_graphql_server]
|
||||||
|
path = "../crates/graphql-server"
|
||||||
|
|
||||||
[dependencies.lldap_ldap]
|
[dependencies.lldap_ldap]
|
||||||
path = "../crates/ldap"
|
path = "../crates/ldap"
|
||||||
|
|
||||||
|
|||||||
@@ -1,109 +1,18 @@
|
|||||||
use crate::infra::{
|
use crate::infra::{auth_service::check_if_token_is_valid, tcp_server::AppState};
|
||||||
auth_service::check_if_token_is_valid,
|
|
||||||
cli::ExportGraphQLSchemaOpts,
|
|
||||||
graphql::{mutation::Mutation, query::Query},
|
|
||||||
tcp_server::AppState,
|
|
||||||
};
|
|
||||||
use actix_web::FromRequest;
|
use actix_web::FromRequest;
|
||||||
use actix_web::HttpMessage;
|
use actix_web::HttpMessage;
|
||||||
use actix_web::{Error, HttpRequest, HttpResponse, error::JsonPayloadError, web};
|
use actix_web::{Error, HttpRequest, HttpResponse, error::JsonPayloadError, web};
|
||||||
use actix_web_httpauth::extractors::bearer::BearerAuth;
|
use actix_web_httpauth::extractors::bearer::BearerAuth;
|
||||||
use juniper::{
|
use juniper::{
|
||||||
EmptySubscription, FieldError, RootNode, ScalarValue,
|
ScalarValue,
|
||||||
http::{
|
http::{
|
||||||
GraphQLBatchRequest, GraphQLRequest, graphiql::graphiql_source,
|
GraphQLBatchRequest, GraphQLRequest, graphiql::graphiql_source,
|
||||||
playground::playground_source,
|
playground::playground_source,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use lldap_access_control::{
|
|
||||||
AccessControlledBackendHandler, AdminBackendHandler, ReadonlyBackendHandler,
|
|
||||||
UserReadableBackendHandler, UserWriteableBackendHandler,
|
|
||||||
};
|
|
||||||
use lldap_auth::{access_control::ValidationResults, types::UserId};
|
|
||||||
use lldap_domain_handlers::handler::BackendHandler;
|
use lldap_domain_handlers::handler::BackendHandler;
|
||||||
use tracing::debug;
|
use lldap_graphql_server::api::Context;
|
||||||
|
use lldap_graphql_server::api::schema;
|
||||||
pub struct Context<Handler: BackendHandler> {
|
|
||||||
pub handler: AccessControlledBackendHandler<Handler>,
|
|
||||||
pub validation_result: ValidationResults,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn field_error_callback<'a>(
|
|
||||||
span: &'a tracing::Span,
|
|
||||||
error_message: &'a str,
|
|
||||||
) -> impl 'a + FnOnce() -> FieldError {
|
|
||||||
move || {
|
|
||||||
span.in_scope(|| debug!("Unauthorized"));
|
|
||||||
FieldError::from(error_message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<Handler: BackendHandler> Context<Handler> {
|
|
||||||
#[cfg(test)]
|
|
||||||
pub fn new_for_tests(handler: Handler, validation_result: ValidationResults) -> Self {
|
|
||||||
Self {
|
|
||||||
handler: AccessControlledBackendHandler::new(handler),
|
|
||||||
validation_result,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_admin_handler(&self) -> Option<&(impl AdminBackendHandler + use<Handler>)> {
|
|
||||||
self.handler.get_admin_handler(&self.validation_result)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_readonly_handler(&self) -> Option<&(impl ReadonlyBackendHandler + use<Handler>)> {
|
|
||||||
self.handler.get_readonly_handler(&self.validation_result)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_writeable_handler(
|
|
||||||
&self,
|
|
||||||
user_id: &UserId,
|
|
||||||
) -> Option<&(impl UserWriteableBackendHandler + use<Handler>)> {
|
|
||||||
self.handler
|
|
||||||
.get_writeable_handler(&self.validation_result, user_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_readable_handler(
|
|
||||||
&self,
|
|
||||||
user_id: &UserId,
|
|
||||||
) -> Option<&(impl UserReadableBackendHandler + use<Handler>)> {
|
|
||||||
self.handler
|
|
||||||
.get_readable_handler(&self.validation_result, user_id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<Handler: BackendHandler> juniper::Context for Context<Handler> {}
|
|
||||||
|
|
||||||
type Schema<Handler> =
|
|
||||||
RootNode<'static, Query<Handler>, Mutation<Handler>, EmptySubscription<Context<Handler>>>;
|
|
||||||
|
|
||||||
fn schema<Handler: BackendHandler>() -> Schema<Handler> {
|
|
||||||
Schema::new(
|
|
||||||
Query::<Handler>::new(),
|
|
||||||
Mutation::<Handler>::new(),
|
|
||||||
EmptySubscription::<Context<Handler>>::new(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn export_schema(opts: ExportGraphQLSchemaOpts) -> anyhow::Result<()> {
|
|
||||||
use anyhow::Context;
|
|
||||||
use lldap_sql_backend_handler::SqlBackendHandler;
|
|
||||||
let output = schema::<SqlBackendHandler>().as_schema_language();
|
|
||||||
match opts.output_file {
|
|
||||||
None => println!("{}", output),
|
|
||||||
Some(path) => {
|
|
||||||
use std::fs::File;
|
|
||||||
use std::io::prelude::*;
|
|
||||||
use std::path::Path;
|
|
||||||
let path = Path::new(&path);
|
|
||||||
let mut file =
|
|
||||||
File::create(path).context(format!("unable to open '{}'", path.display()))?;
|
|
||||||
file.write_all(output.as_bytes())
|
|
||||||
.context(format!("unable to write in '{}'", path.display()))?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn graphiql_route() -> Result<HttpResponse, Error> {
|
async fn graphiql_route() -> Result<HttpResponse, Error> {
|
||||||
let html = graphiql_source("/api/graphql", None);
|
let html = graphiql_source("/api/graphql", None);
|
||||||
@@ -3,7 +3,7 @@ pub mod cli;
|
|||||||
pub mod configuration;
|
pub mod configuration;
|
||||||
pub mod database_string;
|
pub mod database_string;
|
||||||
pub mod db_cleaner;
|
pub mod db_cleaner;
|
||||||
pub mod graphql;
|
pub mod graphql_server;
|
||||||
pub mod healthcheck;
|
pub mod healthcheck;
|
||||||
pub mod jwt_sql_tables;
|
pub mod jwt_sql_tables;
|
||||||
pub mod ldap_server;
|
pub mod ldap_server;
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ fn http_config<Backend>(
|
|||||||
.service(
|
.service(
|
||||||
web::scope("/api")
|
web::scope("/api")
|
||||||
.wrap(auth_service::CookieToHeaderTranslatorFactory)
|
.wrap(auth_service::CookieToHeaderTranslatorFactory)
|
||||||
.configure(super::graphql::api::configure_endpoint::<Backend>),
|
.configure(crate::infra::graphql_server::configure_endpoint::<Backend>),
|
||||||
)
|
)
|
||||||
.service(
|
.service(
|
||||||
web::resource("/pkg/lldap_app_bg.wasm.gz")
|
web::resource("/pkg/lldap_app_bg.wasm.gz")
|
||||||
|
|||||||
+3
-1
@@ -274,7 +274,9 @@ async fn create_schema_command(opts: RunOpts) -> Result<()> {
|
|||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
let cli_opts = infra::cli::init();
|
let cli_opts = infra::cli::init();
|
||||||
match cli_opts.command {
|
match cli_opts.command {
|
||||||
Command::ExportGraphQLSchema(opts) => infra::graphql::api::export_schema(opts),
|
Command::ExportGraphQLSchema(opts) => {
|
||||||
|
lldap_graphql_server::api::export_schema(opts.output_file)
|
||||||
|
}
|
||||||
Command::Run(opts) => run_server_command(opts).await,
|
Command::Run(opts) => run_server_command(opts).await,
|
||||||
Command::HealthCheck(opts) => run_healthcheck(opts).await,
|
Command::HealthCheck(opts) => run_healthcheck(opts).await,
|
||||||
Command::SendTestEmail(opts) => send_test_email_command(opts).await,
|
Command::SendTestEmail(opts) => send_test_email_command(opts).await,
|
||||||
|
|||||||
Reference in New Issue
Block a user