diff --git a/Cargo.lock b/Cargo.lock index d7ff474..27ce62d 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]] @@ -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", @@ -2404,7 +2463,6 @@ dependencies = [ "tracing-forest", "tracing-log", "tracing-subscriber", - "urlencoding", "uuid 1.3.0", "webpki-roots", ] @@ -2621,7 +2679,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -2831,7 +2889,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -2914,7 +2972,7 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -2976,7 +3034,7 @@ checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -3064,7 +3122,7 @@ dependencies = [ "proc-macro-error-attr", "proc-macro2", "quote", - "syn", + "syn 1.0.109", "version_check", ] @@ -3081,9 +3139,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 +3154,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", ] @@ -3512,7 +3570,7 @@ dependencies = [ "heck 0.3.3", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -3547,7 +3605,7 @@ dependencies = [ "heck 0.4.1", "proc-macro2", "quote", - "syn", + "syn 1.0.109", "thiserror", ] @@ -3570,7 +3628,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn", + "syn 1.0.109", ] [[package]] @@ -3650,7 +3708,7 @@ checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -3677,6 +3735,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" @@ -3914,7 +3996,7 @@ dependencies = [ "quote", "sqlx-core", "sqlx-rt", - "syn", + "syn 1.0.109", "url", ] @@ -3968,6 +4050,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 +4069,7 @@ checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", "unicode-xid", ] @@ -4037,7 +4130,7 @@ checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -4131,7 +4224,7 @@ checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -4218,7 +4311,7 @@ checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -4444,7 +4537,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn", + "syn 1.0.109", "validator_types", ] @@ -4455,7 +4548,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ded9d97e1d42327632f5f3bae6403c04886e2de3036261ef42deebd931a6a291" dependencies = [ "proc-macro2", - "syn", + "syn 1.0.109", ] [[package]] @@ -4482,6 +4575,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 +4633,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 1.0.109", "wasm-bindgen-shared", ] @@ -4565,7 +4667,7 @@ checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -4790,7 +4892,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -4821,7 +4923,7 @@ checksum = "39049d193b52eaad4ffc80916bf08806d142c90b5edcebd527644de438a7e19a" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -4844,7 +4946,7 @@ source = "git+https://github.com/jfbilodeau/yew_form?rev=4b9fabffb63393ec7626a44 dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", "yew_form", ] @@ -4865,7 +4967,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..6208bc5 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -120,3 +120,26 @@ 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"] \ No newline at end of file diff --git a/server/tests/common/fixture.rs b/server/tests/common/fixture.rs new file mode 100644 index 0000000..f2bde38 --- /dev/null +++ b/server/tests/common/fixture.rs @@ -0,0 +1,342 @@ +use anyhow::{anyhow, bail, Context, Result}; +use assert_cmd::prelude::*; +use graphql_client::GraphQLQuery; +use reqwest::blocking::{Client, ClientBuilder}; +use std::collections::{HashMap, HashSet}; +use std::process::{Child, Command}; +use std::{env::var, fs::canonicalize, thread, time::Duration}; + +const DB_KEY: &str = "LLDAP_DATABASE_URL"; + +#[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" +)] +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" +)] +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" +)] +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" +)] +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" +)] +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" +)] +struct DeleteUserQuery; + +#[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 found"); + + let path = canonicalize("..").expect("canonical path"); + let db_url = get_database_url(); + println!("Running from directory: {:?}", path); + println!("Using database: {db_url}"); + cmd.current_dir(path.clone()); + cmd.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).expect("failed to get token"); + 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: &String) { + let id = post::( + &self.client, + &self.token, + create_group::Variables { + name: group.clone(), + }, + ) + .expect("failed to add group") + .create_group + .id; + self.groups.insert(group.clone(), 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: &String, group: &String) { + let group_id = self.groups.get(group).unwrap(); + post::( + &self.client, + &self.token, + add_user_to_group::Variables { + user: user.clone(), + 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(); + } +} + +fn get_database_url() -> String { + let url = var(DB_KEY).ok(); + let url = url.unwrap_or("sqlite://e2e_test.db?mode=rwc".to_string()); + url.to_string() +} + +pub fn get_ldap_url() -> String { + let port = option_env!("LLDAP_LDAP_PORT"); + let port = port.unwrap_or("3890"); + let mut url = String::from("ldap://localhost:"); + url += port; + url +} + +pub fn get_http_url() -> String { + let port = option_env!("LLDAP_HTTP_PORT"); + let port = port.unwrap_or("17170"); + let mut url = String::from("http://localhost:"); + url += port; + url +} + +pub fn get_admin_dn() -> String { + let user = option_env!("LLDAP_LDAP_USER_DN"); + let user = user.unwrap_or("admin"); + user.to_string() +} + +pub fn get_admin_password() -> String { + let pass = option_env!("LLDAP_LDAP_USER_PASS"); + let pass = pass.unwrap_or("password"); + pass.to_string() +} + +pub fn get_base_dn() -> String { + let dn = option_env!("LLDAP_LDAP_BASE_DN"); + let dn = dn.unwrap_or("dc=example,dc=com"); + dn.to_string() +} + +pub fn get_token(client: &Client) -> Result { + let username = get_admin_dn(); + let password = get_admin_password(); + let base_url = get_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: username, + password: password, + }) + .expect("Failed to encode the username/password as json to log in"), + ) + .send()? + .error_for_status()?; + Ok(serde_json::from_str::(&response.text()?)?.token) +} +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 = get_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..97b7845 --- /dev/null +++ b/server/tests/common/mod.rs @@ -0,0 +1 @@ +pub mod fixture; \ No newline at end of file diff --git a/server/tests/graphql.rs b/server/tests/graphql.rs new file mode 100644 index 0000000..937f880 --- /dev/null +++ b/server/tests/graphql.rs @@ -0,0 +1,42 @@ +use crate::common::fixture::{LLDAPFixture, User}; +use graphql_client::GraphQLQuery; +use ldap3::{LdapConn, Scope, SearchEntry}; +use reqwest::blocking::{Client, ClientBuilder}; +use serial_test::file_serial; +use std::collections::{HashMap, HashSet}; +mod common; + +#[test] +#[file_serial] +fn list_users() { + let mut fixture = LLDAPFixture::new(); + let initial_state = vec![ + User::new("user1", vec!["group-one"]), + User::new("user2", vec!["group-one", "group-two"]), + User::new("user3", 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 = common::fixture::get_token(&client).expect("failed to get token"); + let result = common::fixture::post::(&client, &token, list_users::Variables {}) + .expect("failed to list users"); + let users: HashSet = result.users.iter().map(|user| user.id.clone()).collect(); + assert!(users.contains("user1")); + assert!(users.contains("user2")); + assert!(users.contains("user3")); +} + +#[derive(GraphQLQuery)] +#[graphql( + schema_path = "../schema.graphql", + query_path = "tests/queries/list_users.graphql", + response_derives = "Debug", + custom_scalars_module = "crate::infra::graphql" +)] +struct ListUsers; diff --git a/server/tests/integrations.rs b/server/tests/integrations.rs new file mode 100644 index 0000000..64cf264 --- /dev/null +++ b/server/tests/integrations.rs @@ -0,0 +1,57 @@ +use std::collections::{HashMap, HashSet}; + +use crate::common::fixture::{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 = "gitea_user"; + let initial_state = vec![ + User::new("bob", vec![gitea_user_group, "gitea-admin"]), + User::new("alice", vec![gitea_user_group]), + User::new("james", vec![]), + ]; + fixture.load_state(&initial_state); + + let mut ldap = LdapConn::new(common::fixture::get_ldap_url().as_str()) + .expect("failed to create ldap connection"); + let base_dn = common::fixture::get_base_dn(); + let bind_dn = format!( + "uid={},ou=people,{}", + common::fixture::get_admin_dn(), + base_dn + ); + ldap.simple_bind( + bind_dn.as_str(), + common::fixture::get_admin_password().as_str(), + ) + .expect("failed to bind to ldap"); + + let user_base = format!("ou=people,{}", common::fixture::get_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("bob")); + assert!(found_users.contains("alice")); + assert!(!found_users.contains("james")); + 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..2c60d43 --- /dev/null +++ b/server/tests/ldap.rs @@ -0,0 +1,71 @@ +use std::collections::{HashMap, HashSet}; + +use crate::common::fixture::{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 initial_state = vec![ + User::new("user1", vec!["group-one"]), + User::new("user2", vec!["group-one", "group-two"]), + User::new("user3", vec![]), + ]; + fixture.load_state(&initial_state); + + let mut ldap = LdapConn::new(common::fixture::get_ldap_url().as_str()) + .expect("failed to create ldap connection"); + let base_dn = common::fixture::get_base_dn(); + let bind_dn = format!( + "uid={},ou=people,{}", + common::fixture::get_admin_dn(), + base_dn + ); + ldap.simple_bind( + bind_dn.as_str(), + common::fixture::get_admin_password().as_str(), + ) + .expect("failed to bind to ldap"); + + let attrs = vec!["uid", "memberof"]; + let results = ldap + .search( + common::fixture::get_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")); + assert!(found_users + .get("user1") + .unwrap() + .contains(format!("cn={},ou=groups,{}", "group-one", base_dn).as_str())); + assert!(found_users.contains_key("user2")); + assert!(found_users + .get("user2") + .unwrap() + .contains(format!("cn={},ou=groups,{}", "group-one", base_dn).as_str())); + assert!(found_users + .get("user2") + .unwrap() + .contains(format!("cn={},ou=groups,{}", "group-two", base_dn).as_str())); + assert!(found_users.contains_key("user3")); + assert!(found_users.get("user3").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 + } +}