diff --git a/Cargo.lock b/Cargo.lock index d7ff474..cccaf13 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -118,7 +118,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "465a6172cf69b960917811022d8f29bc0b7fa1398bc4f78b3c466673db1213b6" dependencies = [ "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -252,7 +252,7 @@ dependencies = [ "actix-router", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -278,7 +278,7 @@ checksum = "6d44b8fee1ced9671ba043476deddef739dd0959bf77030b26b738cc591737a7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -400,7 +400,7 @@ checksum = "726535892e8eae7e70657b4c8ea93d26b8553afb1ce617caee529ef96d7dee6c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", "synstructure", ] @@ -412,7 +412,21 @@ checksum = "2777730b2039ac0f95f093556e61b6d26cebed5393ca6f152717777cec3a42ed" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", +] + +[[package]] +name = "assert_cmd" +version = "2.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9834fcc22e0874394a010230586367d4a3e9f11b560f469262678547e1d2575e" +dependencies = [ + "bstr", + "doc-comment", + "predicates", + "predicates-core", + "predicates-tree", + "wait-timeout", ] [[package]] @@ -434,7 +448,7 @@ checksum = "e4655ae1a7b0cdf149156f780c5bf3f1352bc53cbd9e0a361a7ef7b22947e965" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -445,7 +459,7 @@ checksum = "1cd7fce9ba8c3c042128ce72d8b2ddbf3a05747efb67ea0313c635e10bda47a2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -497,7 +511,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -606,6 +620,18 @@ dependencies = [ "uuid 0.8.2", ] +[[package]] +name = "bstr" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d4260bcc2e8fc9df1eac4919a720effeb63a3f0952f5bf4944adfa18897f09" +dependencies = [ + "memchr", + "once_cell", + "regex-automata", + "serde", +] + [[package]] name = "bumpalo" version = "3.12.0" @@ -695,7 +721,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -958,7 +984,7 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn", + "syn 1.0.109", ] [[package]] @@ -975,7 +1001,7 @@ checksum = "086c685979a698443656e5cf7856c95c642295a38599f12fb1ff76fb28d19892" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -999,7 +1025,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn", + "syn 1.0.109", ] [[package]] @@ -1010,7 +1036,20 @@ checksum = "b36230598a2d5de7ec1c6f51f72d8a99a9208daff41de2084d06e3fd3ea56685" dependencies = [ "darling_core", "quote", - "syn", + "syn 1.0.109", +] + +[[package]] +name = "dashmap" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "907076dfda823b0b36d2a1bb5f90c96660a5bbcd7729e10727f07858f22c4edc" +dependencies = [ + "cfg-if", + "hashbrown 0.12.3", + "lock_api", + "once_cell", + "parking_lot_core 0.9.7", ] [[package]] @@ -1062,7 +1101,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1072,7 +1111,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebcda35c7a396850a55ffeac740804b40ffec779b98fffbb1738f4033f0ee79e" dependencies = [ "derive_builder_core", - "syn", + "syn 1.0.109", ] [[package]] @@ -1085,7 +1124,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn", + "syn 1.0.109", ] [[package]] @@ -1096,7 +1135,7 @@ checksum = "532b4c15dccee12c7044f1fcad956e98410860b22231e44a3b827464797ca7bf" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1153,7 +1192,7 @@ checksum = "adc2ab4d5a16117f9029e9a6b5e4e79f4c67f6519bc134210d4d4a04ba31f41b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1164,9 +1203,15 @@ checksum = "3bf95dc3f046b9da4f2d51833c0d3547d8564ef6910f5c1ed130306a75b92886" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + [[package]] name = "dotenvy" version = "0.15.6" @@ -1255,7 +1300,7 @@ checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", "synstructure", ] @@ -1349,6 +1394,16 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa" +[[package]] +name = "fslock" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04412b8935272e3a9bae6f48c7bfff74c2911f60525404edfdd28e49884c3bfb" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "futures" version = "0.3.26" @@ -1388,7 +1443,7 @@ checksum = "3422d14de7903a52e9dbc10ae05a7e14445ec61890100e098754e120b2bd7b1e" dependencies = [ "derive_utils", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1427,7 +1482,7 @@ checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1718,7 +1773,7 @@ dependencies = [ "quote", "serde", "serde_json", - "syn", + "syn 1.0.109", ] [[package]] @@ -1735,7 +1790,7 @@ dependencies = [ "quote", "serde", "serde_json", - "syn", + "syn 1.0.109", ] [[package]] @@ -1746,7 +1801,7 @@ checksum = "e56b093bfda71de1da99758b036f4cc811fd2511c8a76f75680e9ffbd2bb4251" dependencies = [ "graphql_client_codegen 0.10.0", "proc-macro2", - "syn", + "syn 1.0.109", ] [[package]] @@ -1757,7 +1812,7 @@ checksum = "a755cc59cda2641ea3037b4f9f7ef40471c329f55c1fa2db6fa0bb7ae6c1f7ce" dependencies = [ "graphql_client_codegen 0.11.0", "proc-macro2", - "syn", + "syn 1.0.109", ] [[package]] @@ -2162,7 +2217,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -2268,7 +2323,7 @@ dependencies = [ "peg", "tokio-util", "tracing", - "uuid 1.3.0", + "uuid 1.3.1", ] [[package]] @@ -2345,7 +2400,7 @@ checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" [[package]] name = "lldap" -version = "0.4.3-alpha" +version = "0.4.3" dependencies = [ "actix", "actix-files", @@ -2357,6 +2412,7 @@ dependencies = [ "actix-web", "actix-web-httpauth", "anyhow", + "assert_cmd", "async-trait", "base64 0.21.0", "bincode", @@ -2368,6 +2424,7 @@ dependencies = [ "figment_file_provider_adapter", "futures", "futures-util", + "graphql_client 0.11.0", "hmac 0.12.1", "http", "image", @@ -2375,6 +2432,7 @@ dependencies = [ "juniper", "jwt 0.16.0", "lber 0.4.1", + "ldap3", "ldap3_proto", "lettre", "lldap_auth", @@ -2391,6 +2449,7 @@ dependencies = [ "serde", "serde_bytes", "serde_json", + "serial_test", "sha2 0.10.6", "thiserror", "time 0.3.19", @@ -2405,13 +2464,13 @@ dependencies = [ "tracing-log", "tracing-subscriber", "urlencoding", - "uuid 1.3.0", + "uuid 0.8.2", "webpki-roots", ] [[package]] name = "lldap_app" -version = "0.4.3-alpha" +version = "0.4.3" dependencies = [ "anyhow", "base64 0.13.1", @@ -2531,6 +2590,12 @@ dependencies = [ "digest 0.10.6", ] +[[package]] +name = "md5" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" + [[package]] name = "memchr" version = "2.5.0" @@ -2621,7 +2686,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -2831,7 +2896,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -2914,7 +2979,7 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -2976,7 +3041,7 @@ checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -3064,7 +3129,7 @@ dependencies = [ "proc-macro-error-attr", "proc-macro2", "quote", - "syn", + "syn 1.0.109", "version_check", ] @@ -3081,9 +3146,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.51" +version = "1.0.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6" +checksum = "e472a104799c74b514a57226160104aa483546de37e839ec50e3c2e41dd87534" dependencies = [ "unicode-ident", ] @@ -3096,16 +3161,16 @@ checksum = "4bf29726d67464d49fa6224a1d07936a8c08bb3fba727c7493f6cf1616fdaada" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", "version_check", "yansi", ] [[package]] name = "quote" -version = "1.0.23" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" dependencies = [ "proc-macro2", ] @@ -3499,7 +3564,7 @@ dependencies = [ "thiserror", "tracing", "url", - "uuid 1.3.0", + "uuid 1.3.1", ] [[package]] @@ -3512,7 +3577,7 @@ dependencies = [ "heck 0.3.3", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -3523,7 +3588,7 @@ checksum = "d2fbe015dbdaa7d8829d71c1e14fb6289e928ac256b93dfda543c85cd89d6f03" dependencies = [ "chrono", "sea-query-derive", - "uuid 1.3.0", + "uuid 1.3.1", ] [[package]] @@ -3535,7 +3600,7 @@ dependencies = [ "chrono", "sea-query", "sqlx", - "uuid 1.3.0", + "uuid 1.3.1", ] [[package]] @@ -3547,7 +3612,7 @@ dependencies = [ "heck 0.4.1", "proc-macro2", "quote", - "syn", + "syn 1.0.109", "thiserror", ] @@ -3570,7 +3635,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn", + "syn 1.0.109", ] [[package]] @@ -3650,7 +3715,7 @@ checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -3677,6 +3742,30 @@ dependencies = [ "serde", ] +[[package]] +name = "serial_test" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e56dd856803e253c8f298af3f4d7eb0ae5e23a737252cd90bb4f3b435033b2d" +dependencies = [ + "dashmap", + "fslock", + "lazy_static", + "parking_lot 0.12.1", + "serial_test_derive", +] + +[[package]] +name = "serial_test_derive" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91d129178576168c589c9ec973feedf7d3126c01ac2bf08795109aa35b69fb8f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.12", +] + [[package]] name = "sha1" version = "0.10.5" @@ -3895,7 +3984,7 @@ dependencies = [ "thiserror", "tokio-stream", "url", - "uuid 1.3.0", + "uuid 1.3.1", "webpki-roots", "whoami", ] @@ -3914,7 +4003,7 @@ dependencies = [ "quote", "sqlx-core", "sqlx-rt", - "syn", + "syn 1.0.109", "url", ] @@ -3968,6 +4057,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79d9531f94112cfc3e4c8f5f02cb2b58f72c97b7efd85f70203cc6d8efda5927" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "synstructure" version = "0.12.6" @@ -3976,7 +4076,7 @@ checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", "unicode-xid", ] @@ -4037,7 +4137,7 @@ checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -4131,7 +4231,7 @@ checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -4207,7 +4307,7 @@ dependencies = [ "actix-web", "pin-project", "tracing", - "uuid 1.3.0", + "uuid 1.3.1", ] [[package]] @@ -4218,7 +4318,7 @@ checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -4405,15 +4505,18 @@ name = "uuid" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" +dependencies = [ + "getrandom 0.2.8", + "md5", +] [[package]] name = "uuid" -version = "1.3.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1674845326ee10d37ca60470760d4288a6f80f304007d92e5c53bab78c9cfd79" +checksum = "5b55a3fef2a1e3b3a00ce878640918820d3c51081576ac657d23af9fc7928fdb" dependencies = [ "getrandom 0.2.8", - "md-5", ] [[package]] @@ -4444,7 +4547,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn", + "syn 1.0.109", "validator_types", ] @@ -4455,7 +4558,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ded9d97e1d42327632f5f3bae6403c04886e2de3036261ef42deebd931a6a291" dependencies = [ "proc-macro2", - "syn", + "syn 1.0.109", ] [[package]] @@ -4482,6 +4585,15 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] + [[package]] name = "want" version = "0.3.0" @@ -4531,7 +4643,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 1.0.109", "wasm-bindgen-shared", ] @@ -4565,7 +4677,7 @@ checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -4790,7 +4902,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -4821,7 +4933,7 @@ checksum = "39049d193b52eaad4ffc80916bf08806d142c90b5edcebd527644de438a7e19a" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -4844,7 +4956,7 @@ source = "git+https://github.com/jfbilodeau/yew_form?rev=4b9fabffb63393ec7626a44 dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", "yew_form", ] @@ -4865,7 +4977,7 @@ checksum = "44bf07cb3e50ea2003396695d58bf46bc9887a1f362260446fad6bc4e79bd36c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", "synstructure", ] diff --git a/server/Cargo.toml b/server/Cargo.toml index 57ee963..8f16bf2 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -120,3 +120,30 @@ features = ["dangerous_configuration"] [dev-dependencies] mockall = "0.11" +assert_cmd = "2.0" + + +[dev-dependencies.serial_test] +version = "2.0.0" +default-features = false +features = ["file_locks"] + + +[dev-dependencies.ldap3] +version = "*" +default-features = false +features = ["sync", "tls-rustls"] + +[dev-dependencies.graphql_client] +features = ["graphql_query_derive", "reqwest-rustls"] +default-features = false +version = "0.11" + +[dev-dependencies.reqwest] +version = "*" +default-features = false +features = ["json", "blocking", "rustls-tls"] + +[dev-dependencies.uuid] +version = "*" +features = ["v4"] \ No newline at end of file diff --git a/server/tests/common/auth.rs b/server/tests/common/auth.rs new file mode 100644 index 0000000..0bcaf3e --- /dev/null +++ b/server/tests/common/auth.rs @@ -0,0 +1,27 @@ +use crate::common::env; +use reqwest::blocking::Client; + +pub fn get_token(client: &Client) -> String { + let username = env::admin_dn(); + let password = env::admin_password(); + let base_url = env::http_url(); + let response = client + .post(format!("{base_url}/auth/simple/login")) + .header(reqwest::header::CONTENT_TYPE, "application/json") + .body( + serde_json::to_string(&lldap_auth::login::ClientSimpleLoginRequest { + username, + password, + }) + .expect("Failed to encode the username/password as json to log in"), + ) + .send() + .expect("Failed to send auth request") + .error_for_status() + .expect("Auth attempt failed"); + serde_json::from_str::( + &response.text().expect("Failed to get response as text"), + ) + .expect("Failed to parse json") + .token +} diff --git a/server/tests/common/env.rs b/server/tests/common/env.rs new file mode 100644 index 0000000..449a87f --- /dev/null +++ b/server/tests/common/env.rs @@ -0,0 +1,39 @@ +use std::env::var; + +pub const DB_KEY: &str = "LLDAP_DATABASE_URL"; + +pub fn database_url() -> String { + let url = var(DB_KEY).ok(); + url.unwrap_or("sqlite://e2e_test.db?mode=rwc".to_string()) +} + +pub fn ldap_url() -> String { + let port = var("LLDAP_LDAP_PORT").ok(); + let port = port.unwrap_or("3890".to_string()); + let mut url = String::from("ldap://localhost:"); + url += &port; + url +} + +pub fn http_url() -> String { + let port = var("LLDAP_HTTP_PORT").ok(); + let port = port.unwrap_or("17170".to_string()); + let mut url = String::from("http://localhost:"); + url += &port; + url +} + +pub fn admin_dn() -> String { + let user = var("LLDAP_LDAP_USER_DN").ok(); + user.unwrap_or("admin".to_string()) +} + +pub fn admin_password() -> String { + let pass = var("LLDAP_LDAP_USER_PASS").ok(); + pass.unwrap_or("password".to_string()) +} + +pub fn base_dn() -> String { + let dn = var("LLDAP_LDAP_BASE_DN").ok(); + dn.unwrap_or("dc=example,dc=com".to_string()) +} diff --git a/server/tests/common/fixture.rs b/server/tests/common/fixture.rs new file mode 100644 index 0000000..eb95b2a --- /dev/null +++ b/server/tests/common/fixture.rs @@ -0,0 +1,188 @@ +use crate::common::auth::get_token; +use crate::common::env; +use crate::common::graphql::*; +use assert_cmd::prelude::*; +use reqwest::blocking::{Client, ClientBuilder}; +use std::collections::{HashMap, HashSet}; +use std::process::{Child, Command}; +use std::{fs::canonicalize, thread, time::Duration}; +use uuid::Uuid; + +#[derive(Clone)] +pub struct User { + pub username: String, + pub groups: Vec, +} + +impl User { + pub fn new(username: &str, groups: Vec<&str>) -> Self { + let username = username.to_string(); + let groups = groups.iter().map(|username| username.to_string()).collect(); + Self { username, groups } + } +} + +pub struct LLDAPFixture { + token: String, + client: Client, + child: Child, + users: HashSet, + groups: HashMap, +} + +impl LLDAPFixture { + pub fn new() -> Self { + let mut cmd = Command::cargo_bin(env!("CARGO_PKG_NAME")).expect("cargo bin not found"); + + let path = canonicalize("..").expect("canonical path"); + let db_url = env::database_url(); + println!("Running from directory: {:?}", path); + println!("Using database: {db_url}"); + cmd.current_dir(path.clone()); + cmd.env(env::DB_KEY, db_url); + cmd.arg("run"); + cmd.arg("--verbose"); + let child = cmd.spawn().expect("Unable to start server"); + loop { + let status = Command::cargo_bin(env!("CARGO_PKG_NAME")) + .expect("cargo bin not found") + .current_dir(path.clone()) + .arg("healthcheck") + .status() + .expect("healthcheck fail"); + if status.success() { + break; + } + thread::sleep(Duration::from_millis(1000)); + } + let client = ClientBuilder::new() + .connect_timeout(std::time::Duration::from_secs(2)) + .timeout(std::time::Duration::from_secs(5)) + .redirect(reqwest::redirect::Policy::none()) + .build() + .expect("failed to make http client"); + let token = get_token(&client); + Self { + client, + token, + child, + users: HashSet::new(), + groups: HashMap::new(), + } + } + + pub fn load_state(&mut self, state: &Vec) { + let mut users: HashSet = HashSet::new(); + let mut groups: HashSet = HashSet::new(); + for user in state { + users.insert(user.username.clone()); + groups.extend(user.groups.clone()); + } + for user in &users { + self.add_user(user); + } + for group in &groups { + self.add_group(group); + } + for User { username, groups } in state { + for group in groups { + self.add_user_to_group(username, group); + } + } + } + + fn add_user(&mut self, user: &String) { + post::( + &self.client, + &self.token, + create_user::Variables { + user: create_user::CreateUserInput { + id: user.clone(), + email: format!("{}@lldap.test", user), + avatar: None, + display_name: None, + first_name: None, + last_name: None, + }, + }, + ) + .expect("failed to add user"); + self.users.insert(user.clone()); + } + + fn add_group(&mut self, group: &str) { + let id = post::( + &self.client, + &self.token, + create_group::Variables { + name: group.to_owned(), + }, + ) + .expect("failed to add group") + .create_group + .id; + self.groups.insert(group.to_owned(), id); + } + + fn delete_user(&mut self, user: &String) { + post::( + &self.client, + &self.token, + delete_user_query::Variables { user: user.clone() }, + ) + .expect("failed to delete user"); + self.users.remove(user); + } + + fn delete_group(&mut self, group: &String) { + let group_id = self.groups.get(group).unwrap(); + post::( + &self.client, + &self.token, + delete_group_query::Variables { + group_id: *group_id, + }, + ) + .expect("failed to delete group"); + self.groups.remove(group); + } + + fn add_user_to_group(&mut self, user: &str, group: &String) { + let group_id = self.groups.get(group).unwrap(); + post::( + &self.client, + &self.token, + add_user_to_group::Variables { + user: user.to_owned(), + group: *group_id, + }, + ) + .expect("failed to add user to group"); + } +} + +impl Drop for LLDAPFixture { + fn drop(&mut self) { + let users = self.users.clone(); + for user in users { + self.delete_user(&user); + } + let groups = self.groups.clone(); + for group in groups.keys() { + self.delete_group(group); + } + self.child + .kill() + .map_err(|err| println!("Failed to kill LLDAP: {:?}", err)) + .ok(); + } +} + +pub fn new_id(prefix: Option<&str>) -> String { + let id = Uuid::new_v4(); + let id = format!("{}-lldap-test", id.to_simple()); + match prefix { + Some(prefix) => format!("{}{}", prefix, id), + None => id, + } +} diff --git a/server/tests/common/graphql.rs b/server/tests/common/graphql.rs new file mode 100644 index 0000000..cf31c92 --- /dev/null +++ b/server/tests/common/graphql.rs @@ -0,0 +1,109 @@ +use crate::common::env; +use anyhow::{anyhow, Context, Result}; +use graphql_client::GraphQLQuery; +use reqwest::blocking::Client; + +#[derive(GraphQLQuery)] +#[graphql( + schema_path = "../schema.graphql", + query_path = "tests/queries/add_user_to_group.graphql", + response_derives = "Debug", + variables_derives = "Debug,Clone", + custom_scalars_module = "crate::infra::graphql" +)] +pub struct AddUserToGroup; + +#[derive(GraphQLQuery)] +#[graphql( + schema_path = "../schema.graphql", + query_path = "tests/queries/create_user.graphql", + response_derives = "Debug", + variables_derives = "Debug,Clone", + custom_scalars_module = "crate::infra::graphql" +)] +pub struct CreateUser; + +#[derive(GraphQLQuery)] +#[graphql( + schema_path = "../schema.graphql", + query_path = "tests/queries/create_group.graphql", + response_derives = "Debug", + variables_derives = "Debug,Clone", + custom_scalars_module = "crate::infra::graphql" +)] +pub struct CreateGroup; + +#[derive(GraphQLQuery)] +#[graphql( + schema_path = "../schema.graphql", + query_path = "tests/queries/list_users.graphql", + response_derives = "Debug", + custom_scalars_module = "crate::infra::graphql" +)] +pub struct ListUsers; + +#[derive(GraphQLQuery)] +#[graphql( + schema_path = "../schema.graphql", + query_path = "tests/queries/list_groups.graphql", + response_derives = "Debug", + custom_scalars_module = "crate::infra::graphql" +)] +pub struct ListGroups; + +#[derive(GraphQLQuery)] +#[graphql( + schema_path = "../schema.graphql", + query_path = "tests/queries/delete_group.graphql", + response_derives = "Debug", + custom_scalars_module = "crate::infra::graphql" +)] +pub struct DeleteGroupQuery; + +#[derive(GraphQLQuery)] +#[graphql( + schema_path = "../schema.graphql", + query_path = "tests/queries/delete_user.graphql", + response_derives = "Debug", + custom_scalars_module = "crate::infra::graphql" +)] +pub struct DeleteUserQuery; + +pub fn post( + client: &Client, + token: &String, + variables: QueryType::Variables, +) -> Result +where + QueryType: GraphQLQuery + 'static, +{ + let unwrap_graphql_response = |graphql_client::Response { data, errors, .. }| { + data.ok_or_else(|| { + anyhow!( + "Errors: [{}]", + errors + .unwrap_or_default() + .iter() + .map(ToString::to_string) + .collect::>() + .join(", ") + ) + }) + }; + let url = env::http_url() + "/api/graphql"; + let auth_header = format!("Bearer {}", token); + client + .post(url) + .header(reqwest::header::AUTHORIZATION, auth_header) + // Request body. + .json(&QueryType::build_query(variables)) + .send() + .context("while sending a request to the LLDAP server")? + .error_for_status() + .context("error from an LLDAP response")? + // Parse response as Json. + .json::>() + .context("while parsing backend response") + .and_then(unwrap_graphql_response) + .context("GraphQL error from an LLDAP response") +} diff --git a/server/tests/common/mod.rs b/server/tests/common/mod.rs new file mode 100644 index 0000000..7a54c77 --- /dev/null +++ b/server/tests/common/mod.rs @@ -0,0 +1,4 @@ +pub mod auth; +pub mod env; +pub mod fixture; +pub mod graphql; diff --git a/server/tests/graphql.rs b/server/tests/graphql.rs new file mode 100644 index 0000000..b9a181d --- /dev/null +++ b/server/tests/graphql.rs @@ -0,0 +1,41 @@ +use crate::common::{ + auth::get_token, + fixture::{new_id, LLDAPFixture, User}, + graphql::{post, ListUsers}, +}; +use reqwest::blocking::ClientBuilder; +use serial_test::file_serial; +use std::collections::HashSet; +mod common; + +#[test] +#[file_serial] +fn list_users() { + let mut fixture = LLDAPFixture::new(); + let prefix = "graphql-list_users-"; + let user1_name = new_id(Some(prefix)); + let user2_name = new_id(Some(prefix)); + let user3_name = new_id(Some(prefix)); + let group1_name = new_id(Some(prefix)); + let group2_name = new_id(Some(prefix)); + let initial_state = vec![ + User::new(&user1_name, vec![&group1_name]), + User::new(&user2_name, vec![&group1_name, &group2_name]), + User::new(&user3_name, vec![]), + ]; + fixture.load_state(&initial_state); + + let client = ClientBuilder::new() + .connect_timeout(std::time::Duration::from_secs(2)) + .timeout(std::time::Duration::from_secs(5)) + .redirect(reqwest::redirect::Policy::none()) + .build() + .expect("failed to make http client"); + let token = get_token(&client); + let result = post::(&client, &token, common::graphql::list_users::Variables {}) + .expect("failed to list users"); + let users: HashSet = result.users.iter().map(|user| user.id.clone()).collect(); + assert!(users.contains(&user1_name)); + assert!(users.contains(&user2_name)); + assert!(users.contains(&user3_name)); +} diff --git a/server/tests/integrations.rs b/server/tests/integrations.rs new file mode 100644 index 0000000..88fc605 --- /dev/null +++ b/server/tests/integrations.rs @@ -0,0 +1,57 @@ +use std::collections::HashSet; + +use crate::common::{ + env, + fixture::{new_id, LLDAPFixture, User}, +}; +use ldap3::{LdapConn, Scope, SearchEntry}; +use serial_test::file_serial; +mod common; + +#[test] +#[file_serial] +fn gitea() { + let mut fixture = LLDAPFixture::new(); + let gitea_user_group = new_id(Some("gitea_user-")); + let gitea_admin_group = new_id(Some("gitea_admin-")); + let gitea_user1 = new_id(Some("gitea1-")); + let gitea_user2 = new_id(Some("gitea2-")); + let gitea_user3 = new_id(Some("gitea3-")); + let initial_state = vec![ + User::new(&gitea_user1, vec![&gitea_user_group, &gitea_admin_group]), + User::new(&gitea_user2, vec![&gitea_user_group]), + User::new(&gitea_user3, vec![]), + ]; + fixture.load_state(&initial_state); + + let mut ldap = + LdapConn::new(env::ldap_url().as_str()).expect("failed to create ldap connection"); + let base_dn = env::base_dn(); + let bind_dn = format!("uid={},ou=people,{}", env::admin_dn(), base_dn); + ldap.simple_bind(bind_dn.as_str(), env::admin_password().as_str()) + .expect("failed to bind to ldap"); + + let user_base = format!("ou=people,{}", base_dn); + let attrs = vec!["uid", "givenName", "sn", "mail", "jpegPhoto"]; + let results = ldap + .search( + user_base.as_str(), + Scope::Subtree, + format!("(memberof=cn={},ou=groups,{})", gitea_user_group, base_dn).as_str(), + attrs, + ) + .expect("failed to find gitea users") + .success() + .expect("failed to get gitea user results") + .0; + let mut found_users: HashSet = HashSet::new(); + for result in results { + let attrs = SearchEntry::construct(result).attrs; + let user = attrs.get("uid").unwrap().get(0).unwrap(); + found_users.insert(user.clone()); + } + assert!(found_users.contains(&gitea_user1)); + assert!(found_users.contains(&gitea_user2)); + assert!(!found_users.contains(&gitea_user3)); + ldap.unbind().expect("failed to unbind ldap connection"); +} diff --git a/server/tests/ldap.rs b/server/tests/ldap.rs new file mode 100644 index 0000000..6ea52cf --- /dev/null +++ b/server/tests/ldap.rs @@ -0,0 +1,73 @@ +use std::collections::{HashMap, HashSet}; + +use crate::common::{ + env, + fixture::{new_id, LLDAPFixture, User}, +}; +use ldap3::{LdapConn, Scope, SearchEntry}; +use serial_test::file_serial; +mod common; + +#[test] +#[file_serial] +fn basic_users_search() { + let mut fixture = LLDAPFixture::new(); + let prefix = "ldap-basic_users_search-"; + let user1_name = new_id(Some(prefix)); + let user2_name = new_id(Some(prefix)); + let user3_name = new_id(Some(prefix)); + let group1_name = new_id(Some(prefix)); + let group2_name = new_id(Some(prefix)); + let initial_state = vec![ + User::new(&user1_name, vec![&group1_name]), + User::new(&user2_name, vec![&group1_name, &group2_name]), + User::new(&user3_name, vec![]), + ]; + fixture.load_state(&initial_state); + + let mut ldap = + LdapConn::new(env::ldap_url().as_str()).expect("failed to create ldap connection"); + let base_dn = env::base_dn(); + let bind_dn = format!("uid={},ou=people,{}", env::admin_dn(), base_dn); + ldap.simple_bind(bind_dn.as_str(), env::admin_password().as_str()) + .expect("failed to bind to ldap"); + + let attrs = vec!["uid", "memberof"]; + let results = ldap + .search( + env::base_dn().as_str(), + Scope::Subtree, + "(objectclass=person)", + attrs, + ) + .expect("failed to find users") + .success() + .expect("failed to get user results") + .0; + let mut found_users: HashMap> = HashMap::new(); + for result in results { + let attrs = SearchEntry::construct(result).attrs; + let user = attrs.get("uid").unwrap().get(0).unwrap(); + let user_groups = attrs.get("memberof").unwrap().clone(); + let mut groups: HashSet = HashSet::new(); + groups.extend(user_groups.clone()); + found_users.insert(user.clone(), groups); + } + assert!(found_users.contains_key(&user1_name)); + assert!(found_users + .get(&user1_name) + .unwrap() + .contains(format!("cn={},ou=groups,{}", &group1_name, base_dn).as_str())); + assert!(found_users.contains_key(&user2_name)); + assert!(found_users + .get(&user2_name) + .unwrap() + .contains(format!("cn={},ou=groups,{}", &group1_name, base_dn).as_str())); + assert!(found_users + .get(&user2_name) + .unwrap() + .contains(format!("cn={},ou=groups,{}", &group2_name, base_dn).as_str())); + assert!(found_users.contains_key(&user3_name)); + assert!(found_users.get(&user3_name).unwrap().is_empty()); + ldap.unbind().expect("failed to unbind ldap connection"); +} diff --git a/server/tests/queries/add_user_to_group.graphql b/server/tests/queries/add_user_to_group.graphql new file mode 100644 index 0000000..8921d2e --- /dev/null +++ b/server/tests/queries/add_user_to_group.graphql @@ -0,0 +1,5 @@ +mutation AddUserToGroup($user: String!, $group: Int!) { + addUserToGroup(userId: $user, groupId: $group) { + ok + } +} diff --git a/server/tests/queries/create_group.graphql b/server/tests/queries/create_group.graphql new file mode 100644 index 0000000..96ea2fa --- /dev/null +++ b/server/tests/queries/create_group.graphql @@ -0,0 +1,6 @@ +mutation CreateGroup($name: String!) { + createGroup(name: $name) { + id + displayName + } +} diff --git a/server/tests/queries/create_user.graphql b/server/tests/queries/create_user.graphql new file mode 100644 index 0000000..7b72108 --- /dev/null +++ b/server/tests/queries/create_user.graphql @@ -0,0 +1,5 @@ +mutation CreateUser($user: CreateUserInput!) { + createUser(user: $user) { + id + } +} diff --git a/server/tests/queries/delete_group.graphql b/server/tests/queries/delete_group.graphql new file mode 100644 index 0000000..3ed0182 --- /dev/null +++ b/server/tests/queries/delete_group.graphql @@ -0,0 +1,5 @@ +mutation DeleteGroupQuery($groupId: Int!) { + deleteGroup(groupId: $groupId) { + ok + } +} diff --git a/server/tests/queries/delete_user.graphql b/server/tests/queries/delete_user.graphql new file mode 100644 index 0000000..b26b111 --- /dev/null +++ b/server/tests/queries/delete_user.graphql @@ -0,0 +1,5 @@ +mutation DeleteUserQuery($user: String!) { + deleteUser(userId: $user) { + ok + } +} diff --git a/server/tests/queries/list_groups.graphql b/server/tests/queries/list_groups.graphql new file mode 100644 index 0000000..5997e19 --- /dev/null +++ b/server/tests/queries/list_groups.graphql @@ -0,0 +1,9 @@ +query ListGroups { + groups { + id + displayName + users { + id + } + } +} diff --git a/server/tests/queries/list_users.graphql b/server/tests/queries/list_users.graphql new file mode 100644 index 0000000..5dc8694 --- /dev/null +++ b/server/tests/queries/list_users.graphql @@ -0,0 +1,5 @@ +query ListUsers { + users(filters: null) { + id + } +}