mirror of
				https://github.com/nitnelave/lldap.git
				synced 2023-04-12 14:25:13 +00:00 
			
		
		
		
	server: Add a Uuid attribute to every user and group
This commit is contained in:
		
							parent
							
								
									cbde363fde
								
							
						
					
					
						commit
						d609363874
					
				
							
								
								
									
										86
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										86
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							@ -501,6 +501,15 @@ dependencies = [
 | 
				
			|||||||
 "generic-array",
 | 
					 "generic-array",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "block-buffer"
 | 
				
			||||||
 | 
					version = "0.10.2"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "generic-array",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "boolinator"
 | 
					name = "boolinator"
 | 
				
			||||||
version = "2.4.0"
 | 
					version = "2.4.0"
 | 
				
			||||||
@ -541,7 +550,7 @@ dependencies = [
 | 
				
			|||||||
 "rand 0.7.3",
 | 
					 "rand 0.7.3",
 | 
				
			||||||
 "serde",
 | 
					 "serde",
 | 
				
			||||||
 "serde_json",
 | 
					 "serde_json",
 | 
				
			||||||
 "uuid",
 | 
					 "uuid 0.8.2",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
@ -837,6 +846,16 @@ dependencies = [
 | 
				
			|||||||
 "subtle",
 | 
					 "subtle",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "crypto-common"
 | 
				
			||||||
 | 
					version = "0.1.3"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "generic-array",
 | 
				
			||||||
 | 
					 "typenum",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "crypto-mac"
 | 
					name = "crypto-mac"
 | 
				
			||||||
version = "0.10.1"
 | 
					version = "0.10.1"
 | 
				
			||||||
@ -870,7 +889,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			|||||||
checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61"
 | 
					checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61"
 | 
				
			||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 "byteorder",
 | 
					 "byteorder",
 | 
				
			||||||
 "digest",
 | 
					 "digest 0.9.0",
 | 
				
			||||||
 "rand_core 0.5.1",
 | 
					 "rand_core 0.5.1",
 | 
				
			||||||
 "serde",
 | 
					 "serde",
 | 
				
			||||||
 "subtle",
 | 
					 "subtle",
 | 
				
			||||||
@ -992,6 +1011,16 @@ dependencies = [
 | 
				
			|||||||
 "generic-array",
 | 
					 "generic-array",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "digest"
 | 
				
			||||||
 | 
					version = "0.10.3"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "block-buffer 0.10.2",
 | 
				
			||||||
 | 
					 "crypto-common",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "dirs"
 | 
					name = "dirs"
 | 
				
			||||||
version = "4.0.0"
 | 
					version = "4.0.0"
 | 
				
			||||||
@ -1539,7 +1568,7 @@ version = "0.11.0"
 | 
				
			|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "01706d578d5c281058480e673ae4086a9f4710d8df1ad80a5b03e39ece5f886b"
 | 
					checksum = "01706d578d5c281058480e673ae4086a9f4710d8df1ad80a5b03e39ece5f886b"
 | 
				
			||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 "digest",
 | 
					 "digest 0.9.0",
 | 
				
			||||||
 "hmac 0.11.0",
 | 
					 "hmac 0.11.0",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -1550,7 +1579,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			|||||||
checksum = "c1441c6b1e930e2817404b5046f1f989899143a12bf92de603b69f4e0aee1e15"
 | 
					checksum = "c1441c6b1e930e2817404b5046f1f989899143a12bf92de603b69f4e0aee1e15"
 | 
				
			||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 "crypto-mac 0.10.1",
 | 
					 "crypto-mac 0.10.1",
 | 
				
			||||||
 "digest",
 | 
					 "digest 0.9.0",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
@ -1560,7 +1589,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			|||||||
checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b"
 | 
					checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b"
 | 
				
			||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 "crypto-mac 0.11.1",
 | 
					 "crypto-mac 0.11.1",
 | 
				
			||||||
 "digest",
 | 
					 "digest 0.9.0",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
@ -1764,7 +1793,7 @@ dependencies = [
 | 
				
			|||||||
 "smartstring",
 | 
					 "smartstring",
 | 
				
			||||||
 "static_assertions",
 | 
					 "static_assertions",
 | 
				
			||||||
 "url",
 | 
					 "url",
 | 
				
			||||||
 "uuid",
 | 
					 "uuid 0.8.2",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
@ -1806,7 +1835,7 @@ checksum = "86e46349d67dc03bdbdb28da0337a355a53ca1d5156452722c36fe21d0e6389b"
 | 
				
			|||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 "base64",
 | 
					 "base64",
 | 
				
			||||||
 "crypto-mac 0.10.1",
 | 
					 "crypto-mac 0.10.1",
 | 
				
			||||||
 "digest",
 | 
					 "digest 0.9.0",
 | 
				
			||||||
 "hmac 0.10.1",
 | 
					 "hmac 0.10.1",
 | 
				
			||||||
 "serde",
 | 
					 "serde",
 | 
				
			||||||
 "serde_json",
 | 
					 "serde_json",
 | 
				
			||||||
@ -2002,6 +2031,7 @@ dependencies = [
 | 
				
			|||||||
 "tracing-forest",
 | 
					 "tracing-forest",
 | 
				
			||||||
 "tracing-log",
 | 
					 "tracing-log",
 | 
				
			||||||
 "tracing-subscriber",
 | 
					 "tracing-subscriber",
 | 
				
			||||||
 | 
					 "uuid 1.1.1",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
@ -2035,7 +2065,7 @@ version = "0.3.0-alpha.1"
 | 
				
			|||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 "chrono",
 | 
					 "chrono",
 | 
				
			||||||
 "curve25519-dalek",
 | 
					 "curve25519-dalek",
 | 
				
			||||||
 "digest",
 | 
					 "digest 0.9.0",
 | 
				
			||||||
 "generic-array",
 | 
					 "generic-array",
 | 
				
			||||||
 "getrandom 0.2.3",
 | 
					 "getrandom 0.2.3",
 | 
				
			||||||
 "opaque-ke",
 | 
					 "opaque-ke",
 | 
				
			||||||
@ -2115,11 +2145,20 @@ version = "0.9.1"
 | 
				
			|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "7b5a279bb9607f9f53c22d496eade00d138d1bdcccd07d74650387cf94942a15"
 | 
					checksum = "7b5a279bb9607f9f53c22d496eade00d138d1bdcccd07d74650387cf94942a15"
 | 
				
			||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 "block-buffer",
 | 
					 "block-buffer 0.9.0",
 | 
				
			||||||
 "digest",
 | 
					 "digest 0.9.0",
 | 
				
			||||||
 "opaque-debug",
 | 
					 "opaque-debug",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "md-5"
 | 
				
			||||||
 | 
					version = "0.10.1"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "658646b21e0b72f7866c7038ab086d3d5e1cd6271f060fd37defb241949d0582"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "digest 0.10.3",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "memchr"
 | 
					name = "memchr"
 | 
				
			||||||
version = "2.4.1"
 | 
					version = "2.4.1"
 | 
				
			||||||
@ -2404,7 +2443,7 @@ checksum = "26772682ba4fa69f11ae6e4af8bc83946372981ff31a026648d4acb2553c9ee8"
 | 
				
			|||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 "base64",
 | 
					 "base64",
 | 
				
			||||||
 "curve25519-dalek",
 | 
					 "curve25519-dalek",
 | 
				
			||||||
 "digest",
 | 
					 "digest 0.9.0",
 | 
				
			||||||
 "displaydoc",
 | 
					 "displaydoc",
 | 
				
			||||||
 "generic-array",
 | 
					 "generic-array",
 | 
				
			||||||
 "generic-bytes",
 | 
					 "generic-bytes",
 | 
				
			||||||
@ -2938,7 +2977,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			|||||||
checksum = "e05c2603e2823634ab331437001b411b9ed11660fbc4066f3908c84a9439260d"
 | 
					checksum = "e05c2603e2823634ab331437001b411b9ed11660fbc4066f3908c84a9439260d"
 | 
				
			||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 "byteorder",
 | 
					 "byteorder",
 | 
				
			||||||
 "digest",
 | 
					 "digest 0.9.0",
 | 
				
			||||||
 "lazy_static",
 | 
					 "lazy_static",
 | 
				
			||||||
 "num-bigint-dig",
 | 
					 "num-bigint-dig",
 | 
				
			||||||
 "num-integer",
 | 
					 "num-integer",
 | 
				
			||||||
@ -3175,10 +3214,10 @@ version = "0.9.8"
 | 
				
			|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6"
 | 
					checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6"
 | 
				
			||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 "block-buffer",
 | 
					 "block-buffer 0.9.0",
 | 
				
			||||||
 "cfg-if 1.0.0",
 | 
					 "cfg-if 1.0.0",
 | 
				
			||||||
 "cpufeatures",
 | 
					 "cpufeatures",
 | 
				
			||||||
 "digest",
 | 
					 "digest 0.9.0",
 | 
				
			||||||
 "opaque-debug",
 | 
					 "opaque-debug",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -3194,10 +3233,10 @@ version = "0.9.9"
 | 
				
			|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800"
 | 
					checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800"
 | 
				
			||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 "block-buffer",
 | 
					 "block-buffer 0.9.0",
 | 
				
			||||||
 "cfg-if 1.0.0",
 | 
					 "cfg-if 1.0.0",
 | 
				
			||||||
 "cpufeatures",
 | 
					 "cpufeatures",
 | 
				
			||||||
 "digest",
 | 
					 "digest 0.9.0",
 | 
				
			||||||
 "opaque-debug",
 | 
					 "opaque-debug",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -3337,7 +3376,7 @@ dependencies = [
 | 
				
			|||||||
 "chrono",
 | 
					 "chrono",
 | 
				
			||||||
 "crc",
 | 
					 "crc",
 | 
				
			||||||
 "crossbeam-queue",
 | 
					 "crossbeam-queue",
 | 
				
			||||||
 "digest",
 | 
					 "digest 0.9.0",
 | 
				
			||||||
 "dirs",
 | 
					 "dirs",
 | 
				
			||||||
 "either",
 | 
					 "either",
 | 
				
			||||||
 "flume",
 | 
					 "flume",
 | 
				
			||||||
@ -3355,7 +3394,7 @@ dependencies = [
 | 
				
			|||||||
 "libc",
 | 
					 "libc",
 | 
				
			||||||
 "libsqlite3-sys",
 | 
					 "libsqlite3-sys",
 | 
				
			||||||
 "log",
 | 
					 "log",
 | 
				
			||||||
 "md-5",
 | 
					 "md-5 0.9.1",
 | 
				
			||||||
 "memchr",
 | 
					 "memchr",
 | 
				
			||||||
 "num-bigint",
 | 
					 "num-bigint",
 | 
				
			||||||
 "once_cell",
 | 
					 "once_cell",
 | 
				
			||||||
@ -3759,7 +3798,7 @@ dependencies = [
 | 
				
			|||||||
 "futures",
 | 
					 "futures",
 | 
				
			||||||
 "tracing",
 | 
					 "tracing",
 | 
				
			||||||
 "tracing-futures",
 | 
					 "tracing-futures",
 | 
				
			||||||
 "uuid",
 | 
					 "uuid 0.8.2",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
@ -3955,6 +3994,15 @@ dependencies = [
 | 
				
			|||||||
 "getrandom 0.2.3",
 | 
					 "getrandom 0.2.3",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "uuid"
 | 
				
			||||||
 | 
					version = "1.1.1"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "c6d5d669b51467dcf7b2f1a796ce0f955f05f01cafda6c19d6e95f730df29238"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "md-5 0.10.1",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "validator"
 | 
					name = "validator"
 | 
				
			||||||
version = "0.14.0"
 | 
					version = "0.14.0"
 | 
				
			||||||
 | 
				
			|||||||
@ -17,54 +17,53 @@ anyhow = "*"
 | 
				
			|||||||
async-trait = "0.1"
 | 
					async-trait = "0.1"
 | 
				
			||||||
base64 = "0.13"
 | 
					base64 = "0.13"
 | 
				
			||||||
bincode = "1.3"
 | 
					bincode = "1.3"
 | 
				
			||||||
chrono = { version = "*", features = [ "serde" ]}
 | 
					 | 
				
			||||||
clap = { version = "3.1.15", features = [ "std", "color", "suggestions", "derive", "env" ] }
 | 
					 | 
				
			||||||
cron = "*"
 | 
					cron = "*"
 | 
				
			||||||
derive_builder = "0.10.2"
 | 
					derive_builder = "0.10.2"
 | 
				
			||||||
futures = "*"
 | 
					futures = "*"
 | 
				
			||||||
futures-util = "*"
 | 
					futures-util = "*"
 | 
				
			||||||
hmac = "0.10"
 | 
					hmac = "0.10"
 | 
				
			||||||
http = "*"
 | 
					http = "*"
 | 
				
			||||||
 | 
					itertools = "0.10.1"
 | 
				
			||||||
 | 
					juniper = "0.15.6"
 | 
				
			||||||
 | 
					juniper_actix = "0.4.0"
 | 
				
			||||||
jwt = "0.13"
 | 
					jwt = "0.13"
 | 
				
			||||||
ldap3_server = ">=0.1.9"
 | 
					ldap3_server = ">=0.1.9"
 | 
				
			||||||
lldap_auth = { path = "../auth" }
 | 
					 | 
				
			||||||
log = "*"
 | 
					log = "*"
 | 
				
			||||||
orion = "0.16"
 | 
					 | 
				
			||||||
native-tls = "0.2.10"
 | 
					native-tls = "0.2.10"
 | 
				
			||||||
 | 
					orion = "0.16"
 | 
				
			||||||
serde = "*"
 | 
					serde = "*"
 | 
				
			||||||
serde_json = "1"
 | 
					serde_json = "1"
 | 
				
			||||||
sha2 = "0.9"
 | 
					sha2 = "0.9"
 | 
				
			||||||
sqlx-core = "0.5.11"
 | 
					sqlx-core = "0.5.11"
 | 
				
			||||||
thiserror = "*"
 | 
					thiserror = "*"
 | 
				
			||||||
time = "0.2"
 | 
					time = "0.2"
 | 
				
			||||||
tokio = { version = "1.13.1", features = ["full"] }
 | 
					 | 
				
			||||||
tokio-native-tls = "0.3"
 | 
					tokio-native-tls = "0.3"
 | 
				
			||||||
tokio-util = "0.6.3"
 | 
					 | 
				
			||||||
tokio-stream = "*"
 | 
					tokio-stream = "*"
 | 
				
			||||||
 | 
					tokio-util = "0.6.3"
 | 
				
			||||||
 | 
					tracing = "*"
 | 
				
			||||||
tracing-actix-web = "0.4.0-beta.7"
 | 
					tracing-actix-web = "0.4.0-beta.7"
 | 
				
			||||||
tracing-attributes = "^0.1.21"
 | 
					tracing-attributes = "^0.1.21"
 | 
				
			||||||
tracing-log = "*"
 | 
					tracing-log = "*"
 | 
				
			||||||
rand = { version = "0.8", features = ["small_rng", "getrandom"] }
 | 
					 | 
				
			||||||
juniper_actix = "0.4.0"
 | 
					 | 
				
			||||||
juniper = "0.15.6"
 | 
					 | 
				
			||||||
itertools = "0.10.1"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
[dependencies.opaque-ke]
 | 
					[dependencies.chrono]
 | 
				
			||||||
version = "0.6"
 | 
					features = ["serde"]
 | 
				
			||||||
 | 
					version = "*"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[dependencies.clap]
 | 
				
			||||||
 | 
					features = ["std", "color", "suggestions", "derive", "env"]
 | 
				
			||||||
 | 
					version = "3.1.15"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[dependencies.figment]
 | 
				
			||||||
 | 
					features = ["env", "toml"]
 | 
				
			||||||
 | 
					version = "*"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[dependencies.tracing-subscriber]
 | 
					[dependencies.tracing-subscriber]
 | 
				
			||||||
version = "0.3"
 | 
					version = "0.3"
 | 
				
			||||||
features = ["env-filter", "tracing-log"]
 | 
					features = ["env-filter", "tracing-log"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[dependencies.lettre]
 | 
					[dependencies.lettre]
 | 
				
			||||||
 | 
					features = ["builder", "serde", "smtp-transport", "tokio1-native-tls", "tokio1"]
 | 
				
			||||||
version = "0.10.0-rc.3"
 | 
					version = "0.10.0-rc.3"
 | 
				
			||||||
features = [
 | 
					 | 
				
			||||||
  "builder",
 | 
					 | 
				
			||||||
  "serde",
 | 
					 | 
				
			||||||
  "smtp-transport",
 | 
					 | 
				
			||||||
  "tokio1-native-tls",
 | 
					 | 
				
			||||||
  "tokio1",
 | 
					 | 
				
			||||||
]
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
[dependencies.sqlx]
 | 
					[dependencies.sqlx]
 | 
				
			||||||
version = "0.5.11"
 | 
					version = "0.5.11"
 | 
				
			||||||
@ -78,6 +77,9 @@ features = [
 | 
				
			|||||||
  "sqlite",
 | 
					  "sqlite",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[dependencies.lldap_auth]
 | 
				
			||||||
 | 
					path = "../auth"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[dependencies.sea-query]
 | 
					[dependencies.sea-query]
 | 
				
			||||||
version = "^0.25"
 | 
					version = "^0.25"
 | 
				
			||||||
features = ["with-chrono", "sqlx-sqlite"]
 | 
					features = ["with-chrono", "sqlx-sqlite"]
 | 
				
			||||||
@ -86,24 +88,32 @@ features = ["with-chrono", "sqlx-sqlite"]
 | 
				
			|||||||
version = "*"
 | 
					version = "*"
 | 
				
			||||||
features = ["with-chrono", "sqlx-sqlite", "sqlx-any"]
 | 
					features = ["with-chrono", "sqlx-sqlite", "sqlx-any"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[dependencies.figment]
 | 
					[dependencies.opaque-ke]
 | 
				
			||||||
features = ["env", "toml"]
 | 
					version = "0.6"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[dependencies.openssl-sys]
 | 
				
			||||||
 | 
					features = ["vendored"]
 | 
				
			||||||
version = "*"
 | 
					version = "*"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[dependencies.rand]
 | 
				
			||||||
 | 
					features = ["small_rng", "getrandom"]
 | 
				
			||||||
 | 
					version = "0.8"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[dependencies.secstr]
 | 
					[dependencies.secstr]
 | 
				
			||||||
features = ["serde"]
 | 
					features = ["serde"]
 | 
				
			||||||
version = "*"
 | 
					version = "*"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[dependencies.openssl-sys]
 | 
					[dependencies.tokio]
 | 
				
			||||||
features = ["vendored"]
 | 
					features = ["full"]
 | 
				
			||||||
 | 
					version = "1.13.1"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[dependencies.uuid]
 | 
				
			||||||
 | 
					features = ["v3"]
 | 
				
			||||||
version = "*"
 | 
					version = "*"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[dependencies.tracing-forest]
 | 
					[dependencies.tracing-forest]
 | 
				
			||||||
features = ["smallvec", "chrono", "tokio"]
 | 
					features = ["smallvec", "chrono", "tokio"]
 | 
				
			||||||
version = "^0.1.4"
 | 
					version = "^0.1.4"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[dependencies.tracing]
 | 
					 | 
				
			||||||
version = "*"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[dev-dependencies]
 | 
					[dev-dependencies]
 | 
				
			||||||
mockall = "0.9.1"
 | 
					mockall = "0.9.1"
 | 
				
			||||||
 | 
				
			|||||||
@ -3,9 +3,57 @@ use async_trait::async_trait;
 | 
				
			|||||||
use serde::{Deserialize, Serialize};
 | 
					use serde::{Deserialize, Serialize};
 | 
				
			||||||
use std::collections::HashSet;
 | 
					use std::collections::HashSet;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(PartialEq, Eq, Clone, Debug, Default, Serialize, Deserialize)]
 | 
					#[derive(
 | 
				
			||||||
#[cfg_attr(not(target_arch = "wasm32"), derive(sqlx::FromRow))]
 | 
					    PartialEq, Hash, Eq, Clone, Debug, Default, Serialize, Deserialize, sqlx::FromRow, sqlx::Type,
 | 
				
			||||||
 | 
					)]
 | 
				
			||||||
 | 
					#[serde(try_from = "&str")]
 | 
				
			||||||
 | 
					#[sqlx(transparent)]
 | 
				
			||||||
 | 
					pub struct Uuid(String);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Uuid {
 | 
				
			||||||
 | 
					    pub fn from_name_and_date(name: &str, creation_date: &chrono::DateTime<chrono::Utc>) -> Self {
 | 
				
			||||||
 | 
					        Uuid(
 | 
				
			||||||
 | 
					            uuid::Uuid::new_v3(
 | 
				
			||||||
 | 
					                &uuid::Uuid::NAMESPACE_X500,
 | 
				
			||||||
 | 
					                &[name.as_bytes(), creation_date.to_rfc3339().as_bytes()].concat(),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .to_string(),
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn as_str(&self) -> &str {
 | 
				
			||||||
 | 
					        &self.0
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn into_string(self) -> String {
 | 
				
			||||||
 | 
					        self.0
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<'a> std::convert::TryFrom<&'a str> for Uuid {
 | 
				
			||||||
 | 
					    type Error = anyhow::Error;
 | 
				
			||||||
 | 
					    fn try_from(s: &'a str) -> anyhow::Result<Self> {
 | 
				
			||||||
 | 
					        Ok(Uuid(uuid::Uuid::parse_str(s)?.to_string()))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl std::string::ToString for Uuid {
 | 
				
			||||||
 | 
					    fn to_string(&self) -> String {
 | 
				
			||||||
 | 
					        self.0.clone()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[cfg(test)]
 | 
				
			||||||
 | 
					#[macro_export]
 | 
				
			||||||
 | 
					macro_rules! uuid {
 | 
				
			||||||
 | 
					    ($s:literal) => {
 | 
				
			||||||
 | 
					        crate::domain::handler::Uuid::try_from($s).unwrap()
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(PartialEq, Eq, Clone, Debug, Default, Serialize, Deserialize, sqlx::Type)]
 | 
				
			||||||
#[serde(from = "String")]
 | 
					#[serde(from = "String")]
 | 
				
			||||||
 | 
					#[sqlx(transparent)]
 | 
				
			||||||
pub struct UserId(String);
 | 
					pub struct UserId(String);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl UserId {
 | 
					impl UserId {
 | 
				
			||||||
@ -34,8 +82,7 @@ impl From<String> for UserId {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)]
 | 
					#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
 | 
				
			||||||
#[cfg_attr(not(target_arch = "wasm32"), derive(sqlx::FromRow))]
 | 
					 | 
				
			||||||
pub struct User {
 | 
					pub struct User {
 | 
				
			||||||
    pub user_id: UserId,
 | 
					    pub user_id: UserId,
 | 
				
			||||||
    pub email: String,
 | 
					    pub email: String,
 | 
				
			||||||
@ -44,18 +91,22 @@ pub struct User {
 | 
				
			|||||||
    pub last_name: String,
 | 
					    pub last_name: String,
 | 
				
			||||||
    // pub avatar: ?,
 | 
					    // pub avatar: ?,
 | 
				
			||||||
    pub creation_date: chrono::DateTime<chrono::Utc>,
 | 
					    pub creation_date: chrono::DateTime<chrono::Utc>,
 | 
				
			||||||
 | 
					    pub uuid: Uuid,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[cfg(test)]
 | 
				
			||||||
impl Default for User {
 | 
					impl Default for User {
 | 
				
			||||||
    fn default() -> Self {
 | 
					    fn default() -> Self {
 | 
				
			||||||
        use chrono::TimeZone;
 | 
					        use chrono::TimeZone;
 | 
				
			||||||
 | 
					        let epoch = chrono::Utc.timestamp(0, 0);
 | 
				
			||||||
        User {
 | 
					        User {
 | 
				
			||||||
            user_id: UserId::default(),
 | 
					            user_id: UserId::default(),
 | 
				
			||||||
            email: String::new(),
 | 
					            email: String::new(),
 | 
				
			||||||
            display_name: String::new(),
 | 
					            display_name: String::new(),
 | 
				
			||||||
            first_name: String::new(),
 | 
					            first_name: String::new(),
 | 
				
			||||||
            last_name: String::new(),
 | 
					            last_name: String::new(),
 | 
				
			||||||
            creation_date: chrono::Utc.timestamp(0, 0),
 | 
					            creation_date: epoch,
 | 
				
			||||||
 | 
					            uuid: Uuid::from_name_and_date("", &epoch),
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -64,6 +115,8 @@ impl Default for User {
 | 
				
			|||||||
pub struct Group {
 | 
					pub struct Group {
 | 
				
			||||||
    pub id: GroupId,
 | 
					    pub id: GroupId,
 | 
				
			||||||
    pub display_name: String,
 | 
					    pub display_name: String,
 | 
				
			||||||
 | 
					    pub creation_date: chrono::DateTime<chrono::Utc>,
 | 
				
			||||||
 | 
					    pub uuid: Uuid,
 | 
				
			||||||
    pub users: Vec<UserId>,
 | 
					    pub users: Vec<UserId>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -92,6 +145,7 @@ pub enum GroupRequestFilter {
 | 
				
			|||||||
    Or(Vec<GroupRequestFilter>),
 | 
					    Or(Vec<GroupRequestFilter>),
 | 
				
			||||||
    Not(Box<GroupRequestFilter>),
 | 
					    Not(Box<GroupRequestFilter>),
 | 
				
			||||||
    DisplayName(String),
 | 
					    DisplayName(String),
 | 
				
			||||||
 | 
					    Uuid(Uuid),
 | 
				
			||||||
    GroupId(GroupId),
 | 
					    GroupId(GroupId),
 | 
				
			||||||
    // Check if the group contains a user identified by uid.
 | 
					    // Check if the group contains a user identified by uid.
 | 
				
			||||||
    Member(UserId),
 | 
					    Member(UserId),
 | 
				
			||||||
@ -128,16 +182,22 @@ pub trait LoginHandler: Clone + Send {
 | 
				
			|||||||
    async fn bind(&self, request: BindRequest) -> Result<()>;
 | 
					    async fn bind(&self, request: BindRequest) -> Result<()>;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
 | 
					#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, sqlx::Type)]
 | 
				
			||||||
 | 
					#[sqlx(transparent)]
 | 
				
			||||||
pub struct GroupId(pub i32);
 | 
					pub struct GroupId(pub i32);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, sqlx::FromRow)]
 | 
					#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, sqlx::FromRow)]
 | 
				
			||||||
pub struct GroupIdAndName(pub GroupId, pub String);
 | 
					pub struct GroupDetails {
 | 
				
			||||||
 | 
					    pub group_id: GroupId,
 | 
				
			||||||
 | 
					    pub display_name: String,
 | 
				
			||||||
 | 
					    pub creation_date: chrono::DateTime<chrono::Utc>,
 | 
				
			||||||
 | 
					    pub uuid: Uuid,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, Clone, PartialEq)]
 | 
					#[derive(Debug, Clone, PartialEq)]
 | 
				
			||||||
pub struct UserAndGroups {
 | 
					pub struct UserAndGroups {
 | 
				
			||||||
    pub user: User,
 | 
					    pub user: User,
 | 
				
			||||||
    pub groups: Option<Vec<GroupIdAndName>>,
 | 
					    pub groups: Option<Vec<GroupDetails>>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[async_trait]
 | 
					#[async_trait]
 | 
				
			||||||
@ -149,7 +209,7 @@ pub trait BackendHandler: Clone + Send {
 | 
				
			|||||||
    ) -> Result<Vec<UserAndGroups>>;
 | 
					    ) -> Result<Vec<UserAndGroups>>;
 | 
				
			||||||
    async fn list_groups(&self, filters: Option<GroupRequestFilter>) -> Result<Vec<Group>>;
 | 
					    async fn list_groups(&self, filters: Option<GroupRequestFilter>) -> Result<Vec<Group>>;
 | 
				
			||||||
    async fn get_user_details(&self, user_id: &UserId) -> Result<User>;
 | 
					    async fn get_user_details(&self, user_id: &UserId) -> Result<User>;
 | 
				
			||||||
    async fn get_group_details(&self, group_id: GroupId) -> Result<GroupIdAndName>;
 | 
					    async fn get_group_details(&self, group_id: GroupId) -> Result<GroupDetails>;
 | 
				
			||||||
    async fn create_user(&self, request: CreateUserRequest) -> Result<()>;
 | 
					    async fn create_user(&self, request: CreateUserRequest) -> Result<()>;
 | 
				
			||||||
    async fn update_user(&self, request: UpdateUserRequest) -> Result<()>;
 | 
					    async fn update_user(&self, request: UpdateUserRequest) -> Result<()>;
 | 
				
			||||||
    async fn update_group(&self, request: UpdateGroupRequest) -> Result<()>;
 | 
					    async fn update_group(&self, request: UpdateGroupRequest) -> Result<()>;
 | 
				
			||||||
@ -158,7 +218,7 @@ pub trait BackendHandler: Clone + Send {
 | 
				
			|||||||
    async fn delete_group(&self, group_id: GroupId) -> Result<()>;
 | 
					    async fn delete_group(&self, group_id: GroupId) -> Result<()>;
 | 
				
			||||||
    async fn add_user_to_group(&self, user_id: &UserId, group_id: GroupId) -> Result<()>;
 | 
					    async fn add_user_to_group(&self, user_id: &UserId, group_id: GroupId) -> Result<()>;
 | 
				
			||||||
    async fn remove_user_from_group(&self, user_id: &UserId, group_id: GroupId) -> Result<()>;
 | 
					    async fn remove_user_from_group(&self, user_id: &UserId, group_id: GroupId) -> Result<()>;
 | 
				
			||||||
    async fn get_user_groups(&self, user_id: &UserId) -> Result<HashSet<GroupIdAndName>>;
 | 
					    async fn get_user_groups(&self, user_id: &UserId) -> Result<HashSet<GroupDetails>>;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[cfg(test)]
 | 
					#[cfg(test)]
 | 
				
			||||||
@ -172,14 +232,14 @@ mockall::mock! {
 | 
				
			|||||||
        async fn list_users(&self, filters: Option<UserRequestFilter>, get_groups: bool) -> Result<Vec<UserAndGroups>>;
 | 
					        async fn list_users(&self, filters: Option<UserRequestFilter>, get_groups: bool) -> Result<Vec<UserAndGroups>>;
 | 
				
			||||||
        async fn list_groups(&self, filters: Option<GroupRequestFilter>) -> Result<Vec<Group>>;
 | 
					        async fn list_groups(&self, filters: Option<GroupRequestFilter>) -> Result<Vec<Group>>;
 | 
				
			||||||
        async fn get_user_details(&self, user_id: &UserId) -> Result<User>;
 | 
					        async fn get_user_details(&self, user_id: &UserId) -> Result<User>;
 | 
				
			||||||
        async fn get_group_details(&self, group_id: GroupId) -> Result<GroupIdAndName>;
 | 
					        async fn get_group_details(&self, group_id: GroupId) -> Result<GroupDetails>;
 | 
				
			||||||
        async fn create_user(&self, request: CreateUserRequest) -> Result<()>;
 | 
					        async fn create_user(&self, request: CreateUserRequest) -> Result<()>;
 | 
				
			||||||
        async fn update_user(&self, request: UpdateUserRequest) -> Result<()>;
 | 
					        async fn update_user(&self, request: UpdateUserRequest) -> Result<()>;
 | 
				
			||||||
        async fn update_group(&self, request: UpdateGroupRequest) -> Result<()>;
 | 
					        async fn update_group(&self, request: UpdateGroupRequest) -> Result<()>;
 | 
				
			||||||
        async fn delete_user(&self, user_id: &UserId) -> Result<()>;
 | 
					        async fn delete_user(&self, user_id: &UserId) -> Result<()>;
 | 
				
			||||||
        async fn create_group(&self, group_name: &str) -> Result<GroupId>;
 | 
					        async fn create_group(&self, group_name: &str) -> Result<GroupId>;
 | 
				
			||||||
        async fn delete_group(&self, group_id: GroupId) -> Result<()>;
 | 
					        async fn delete_group(&self, group_id: GroupId) -> Result<()>;
 | 
				
			||||||
        async fn get_user_groups(&self, user_id: &UserId) -> Result<HashSet<GroupIdAndName>>;
 | 
					        async fn get_user_groups(&self, user_id: &UserId) -> Result<HashSet<GroupDetails>>;
 | 
				
			||||||
        async fn add_user_to_group(&self, user_id: &UserId, group_id: GroupId) -> Result<()>;
 | 
					        async fn add_user_to_group(&self, user_id: &UserId, group_id: GroupId) -> Result<()>;
 | 
				
			||||||
        async fn remove_user_from_group(&self, user_id: &UserId, group_id: GroupId) -> Result<()>;
 | 
					        async fn remove_user_from_group(&self, user_id: &UserId, group_id: GroupId) -> Result<()>;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -188,3 +248,19 @@ mockall::mock! {
 | 
				
			|||||||
        async fn bind(&self, request: BindRequest) -> Result<()>;
 | 
					        async fn bind(&self, request: BindRequest) -> Result<()>;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[cfg(test)]
 | 
				
			||||||
 | 
					mod tests {
 | 
				
			||||||
 | 
					    use super::*;
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn test_uuid_time() {
 | 
				
			||||||
 | 
					        use chrono::prelude::*;
 | 
				
			||||||
 | 
					        let user_id = "bob";
 | 
				
			||||||
 | 
					        let date1 = Utc.ymd(2014, 7, 8).and_hms(9, 10, 11);
 | 
				
			||||||
 | 
					        let date2 = Utc.ymd(2014, 7, 8).and_hms(9, 10, 12);
 | 
				
			||||||
 | 
					        assert_ne!(
 | 
				
			||||||
 | 
					            Uuid::from_name_and_date(user_id, &date1),
 | 
				
			||||||
 | 
					            Uuid::from_name_and_date(user_id, &date2)
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -98,6 +98,7 @@ fn get_group_filter_expr(filter: GroupRequestFilter) -> SimpleExpr {
 | 
				
			|||||||
        Not(f) => Expr::not(Expr::expr(get_group_filter_expr(*f))),
 | 
					        Not(f) => Expr::not(Expr::expr(get_group_filter_expr(*f))),
 | 
				
			||||||
        DisplayName(name) => Expr::col((Groups::Table, Groups::DisplayName)).eq(name),
 | 
					        DisplayName(name) => Expr::col((Groups::Table, Groups::DisplayName)).eq(name),
 | 
				
			||||||
        GroupId(id) => Expr::col((Groups::Table, Groups::GroupId)).eq(id.0),
 | 
					        GroupId(id) => Expr::col((Groups::Table, Groups::GroupId)).eq(id.0),
 | 
				
			||||||
 | 
					        Uuid(uuid) => Expr::col((Groups::Table, Groups::Uuid)).eq(uuid.to_string()),
 | 
				
			||||||
        // WHERE (group_id in (SELECT group_id FROM memberships WHERE user_id = user))
 | 
					        // WHERE (group_id in (SELECT group_id FROM memberships WHERE user_id = user))
 | 
				
			||||||
        Member(user) => Expr::col((Memberships::Table, Memberships::GroupId)).in_subquery(
 | 
					        Member(user) => Expr::col((Memberships::Table, Memberships::GroupId)).in_subquery(
 | 
				
			||||||
            Query::select()
 | 
					            Query::select()
 | 
				
			||||||
@ -126,7 +127,8 @@ impl BackendHandler for SqlBackendHandler {
 | 
				
			|||||||
                .column(Users::FirstName)
 | 
					                .column(Users::FirstName)
 | 
				
			||||||
                .column(Users::LastName)
 | 
					                .column(Users::LastName)
 | 
				
			||||||
                .column(Users::Avatar)
 | 
					                .column(Users::Avatar)
 | 
				
			||||||
                .column(Users::CreationDate)
 | 
					                .column((Users::Table, Users::CreationDate))
 | 
				
			||||||
 | 
					                .column((Users::Table, Users::Uuid))
 | 
				
			||||||
                .from(Users::Table)
 | 
					                .from(Users::Table)
 | 
				
			||||||
                .order_by((Users::Table, Users::UserId), Order::Asc)
 | 
					                .order_by((Users::Table, Users::UserId), Order::Asc)
 | 
				
			||||||
                .to_owned();
 | 
					                .to_owned();
 | 
				
			||||||
@ -151,6 +153,14 @@ impl BackendHandler for SqlBackendHandler {
 | 
				
			|||||||
                        Expr::col((Groups::Table, Groups::DisplayName)),
 | 
					                        Expr::col((Groups::Table, Groups::DisplayName)),
 | 
				
			||||||
                        Alias::new("group_display_name"),
 | 
					                        Alias::new("group_display_name"),
 | 
				
			||||||
                    )
 | 
					                    )
 | 
				
			||||||
 | 
					                    .expr_as(
 | 
				
			||||||
 | 
					                        Expr::col((Groups::Table, Groups::CreationDate)),
 | 
				
			||||||
 | 
					                        sea_query::Alias::new("group_creation_date"),
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                    .expr_as(
 | 
				
			||||||
 | 
					                        Expr::col((Groups::Table, Groups::Uuid)),
 | 
				
			||||||
 | 
					                        sea_query::Alias::new("group_uuid"),
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
                    .order_by(Alias::new("group_display_name"), Order::Asc);
 | 
					                    .order_by(Alias::new("group_display_name"), Order::Asc);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            if let Some(filter) = filters {
 | 
					            if let Some(filter) = filters {
 | 
				
			||||||
@ -189,13 +199,14 @@ impl BackendHandler for SqlBackendHandler {
 | 
				
			|||||||
                user: User::from_row(rows.peek().unwrap()).unwrap(),
 | 
					                user: User::from_row(rows.peek().unwrap()).unwrap(),
 | 
				
			||||||
                groups: if get_groups {
 | 
					                groups: if get_groups {
 | 
				
			||||||
                    Some(
 | 
					                    Some(
 | 
				
			||||||
                        rows.map(|row| {
 | 
					                        rows.map(|row| GroupDetails {
 | 
				
			||||||
                            GroupIdAndName(
 | 
					                            group_id: row.get::<GroupId, _>(&*Groups::GroupId.to_string()),
 | 
				
			||||||
                                GroupId(row.get::<i32, _>(&*Groups::GroupId.to_string())),
 | 
					                            display_name: row.get::<String, _>("group_display_name"),
 | 
				
			||||||
                                row.get::<String, _>("group_display_name"),
 | 
					                            creation_date: row
 | 
				
			||||||
                            )
 | 
					                                .get::<chrono::DateTime<chrono::Utc>, _>("group_creation_date"),
 | 
				
			||||||
 | 
					                            uuid: row.get::<Uuid, _>("group_uuid"),
 | 
				
			||||||
                        })
 | 
					                        })
 | 
				
			||||||
                        .filter(|g| !g.1.is_empty())
 | 
					                        .filter(|g| !g.display_name.is_empty())
 | 
				
			||||||
                        .collect(),
 | 
					                        .collect(),
 | 
				
			||||||
                    )
 | 
					                    )
 | 
				
			||||||
                } else {
 | 
					                } else {
 | 
				
			||||||
@ -213,6 +224,8 @@ impl BackendHandler for SqlBackendHandler {
 | 
				
			|||||||
            let mut query_builder = Query::select()
 | 
					            let mut query_builder = Query::select()
 | 
				
			||||||
                .column((Groups::Table, Groups::GroupId))
 | 
					                .column((Groups::Table, Groups::GroupId))
 | 
				
			||||||
                .column(Groups::DisplayName)
 | 
					                .column(Groups::DisplayName)
 | 
				
			||||||
 | 
					                .column(Groups::CreationDate)
 | 
				
			||||||
 | 
					                .column(Groups::Uuid)
 | 
				
			||||||
                .column(Memberships::UserId)
 | 
					                .column(Memberships::UserId)
 | 
				
			||||||
                .from(Groups::Table)
 | 
					                .from(Groups::Table)
 | 
				
			||||||
                .left_join(
 | 
					                .left_join(
 | 
				
			||||||
@ -245,20 +258,17 @@ impl BackendHandler for SqlBackendHandler {
 | 
				
			|||||||
        let mut groups = Vec::new();
 | 
					        let mut groups = Vec::new();
 | 
				
			||||||
        // The rows are returned sorted by display_name, equivalent to group_id. We group them by
 | 
					        // The rows are returned sorted by display_name, equivalent to group_id. We group them by
 | 
				
			||||||
        // this key which gives us one element (`rows`) per group.
 | 
					        // this key which gives us one element (`rows`) per group.
 | 
				
			||||||
        for ((group_id, display_name), rows) in &query_with(query.as_str(), values)
 | 
					        for (group_details, rows) in &query_with(&query, values)
 | 
				
			||||||
            .fetch_all(&self.sql_pool)
 | 
					            .fetch_all(&self.sql_pool)
 | 
				
			||||||
            .await?
 | 
					            .await?
 | 
				
			||||||
            .into_iter()
 | 
					            .into_iter()
 | 
				
			||||||
            .group_by(|row| {
 | 
					            .group_by(|row| GroupDetails::from_row(row).unwrap())
 | 
				
			||||||
                (
 | 
					 | 
				
			||||||
                    GroupId(row.get::<i32, _>(&*Groups::GroupId.to_string())),
 | 
					 | 
				
			||||||
                    row.get::<String, _>(&*Groups::DisplayName.to_string()),
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
            })
 | 
					 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            groups.push(Group {
 | 
					            groups.push(Group {
 | 
				
			||||||
                id: group_id,
 | 
					                id: group_details.group_id,
 | 
				
			||||||
                display_name,
 | 
					                display_name: group_details.display_name,
 | 
				
			||||||
 | 
					                creation_date: group_details.creation_date,
 | 
				
			||||||
 | 
					                uuid: group_details.uuid,
 | 
				
			||||||
                users: rows
 | 
					                users: rows
 | 
				
			||||||
                    .map(|row| row.get::<UserId, _>(&*Memberships::UserId.to_string()))
 | 
					                    .map(|row| row.get::<UserId, _>(&*Memberships::UserId.to_string()))
 | 
				
			||||||
                    // If a group has no users, an empty string is returned because of the left
 | 
					                    // If a group has no users, an empty string is returned because of the left
 | 
				
			||||||
@ -281,6 +291,7 @@ impl BackendHandler for SqlBackendHandler {
 | 
				
			|||||||
            .column(Users::LastName)
 | 
					            .column(Users::LastName)
 | 
				
			||||||
            .column(Users::Avatar)
 | 
					            .column(Users::Avatar)
 | 
				
			||||||
            .column(Users::CreationDate)
 | 
					            .column(Users::CreationDate)
 | 
				
			||||||
 | 
					            .column(Users::Uuid)
 | 
				
			||||||
            .from(Users::Table)
 | 
					            .from(Users::Table)
 | 
				
			||||||
            .cond_where(Expr::col(Users::UserId).eq(user_id))
 | 
					            .cond_where(Expr::col(Users::UserId).eq(user_id))
 | 
				
			||||||
            .build_sqlx(DbQueryBuilder {});
 | 
					            .build_sqlx(DbQueryBuilder {});
 | 
				
			||||||
@ -292,29 +303,31 @@ impl BackendHandler for SqlBackendHandler {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    #[instrument(skip_all, level = "debug", ret, err)]
 | 
					    #[instrument(skip_all, level = "debug", ret, err)]
 | 
				
			||||||
    async fn get_group_details(&self, group_id: GroupId) -> Result<GroupIdAndName> {
 | 
					    async fn get_group_details(&self, group_id: GroupId) -> Result<GroupDetails> {
 | 
				
			||||||
        debug!(?group_id);
 | 
					        debug!(?group_id);
 | 
				
			||||||
        let (query, values) = Query::select()
 | 
					        let (query, values) = Query::select()
 | 
				
			||||||
            .column(Groups::GroupId)
 | 
					            .column(Groups::GroupId)
 | 
				
			||||||
            .column(Groups::DisplayName)
 | 
					            .column(Groups::DisplayName)
 | 
				
			||||||
 | 
					            .column(Groups::CreationDate)
 | 
				
			||||||
 | 
					            .column(Groups::Uuid)
 | 
				
			||||||
            .from(Groups::Table)
 | 
					            .from(Groups::Table)
 | 
				
			||||||
            .cond_where(Expr::col(Groups::GroupId).eq(group_id))
 | 
					            .cond_where(Expr::col(Groups::GroupId).eq(group_id))
 | 
				
			||||||
            .build_sqlx(DbQueryBuilder {});
 | 
					            .build_sqlx(DbQueryBuilder {});
 | 
				
			||||||
        debug!(%query);
 | 
					        debug!(%query);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Ok(
 | 
					        Ok(query_as_with::<_, GroupDetails, _>(&query, values)
 | 
				
			||||||
            query_as_with::<_, GroupIdAndName, _>(query.as_str(), values)
 | 
					            .fetch_one(&self.sql_pool)
 | 
				
			||||||
                .fetch_one(&self.sql_pool)
 | 
					            .await?)
 | 
				
			||||||
                .await?,
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    #[instrument(skip_all, level = "debug", ret, err)]
 | 
					    #[instrument(skip_all, level = "debug", ret, err)]
 | 
				
			||||||
    async fn get_user_groups(&self, user_id: &UserId) -> Result<HashSet<GroupIdAndName>> {
 | 
					    async fn get_user_groups(&self, user_id: &UserId) -> Result<HashSet<GroupDetails>> {
 | 
				
			||||||
        debug!(?user_id);
 | 
					        debug!(?user_id);
 | 
				
			||||||
        let (query, values) = Query::select()
 | 
					        let (query, values) = Query::select()
 | 
				
			||||||
            .column((Groups::Table, Groups::GroupId))
 | 
					            .column((Groups::Table, Groups::GroupId))
 | 
				
			||||||
            .column(Groups::DisplayName)
 | 
					            .column(Groups::DisplayName)
 | 
				
			||||||
 | 
					            .column(Groups::CreationDate)
 | 
				
			||||||
 | 
					            .column(Groups::Uuid)
 | 
				
			||||||
            .from(Groups::Table)
 | 
					            .from(Groups::Table)
 | 
				
			||||||
            .inner_join(
 | 
					            .inner_join(
 | 
				
			||||||
                Memberships::Table,
 | 
					                Memberships::Table,
 | 
				
			||||||
@ -325,17 +338,10 @@ impl BackendHandler for SqlBackendHandler {
 | 
				
			|||||||
            .build_sqlx(DbQueryBuilder {});
 | 
					            .build_sqlx(DbQueryBuilder {});
 | 
				
			||||||
        debug!(%query);
 | 
					        debug!(%query);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        query_with(query.as_str(), values)
 | 
					        query_as_with::<_, GroupDetails, _>(&query, values)
 | 
				
			||||||
            // Extract the group id from the row.
 | 
					 | 
				
			||||||
            .map(|row: DbRow| {
 | 
					 | 
				
			||||||
                GroupIdAndName(
 | 
					 | 
				
			||||||
                    row.get::<GroupId, _>(&*Groups::GroupId.to_string()),
 | 
					 | 
				
			||||||
                    row.get::<String, _>(&*Groups::DisplayName.to_string()),
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
            })
 | 
					 | 
				
			||||||
            .fetch(&self.sql_pool)
 | 
					            .fetch(&self.sql_pool)
 | 
				
			||||||
            // Collect the vector of rows, each potentially an error.
 | 
					            // Collect the vector of rows, each potentially an error.
 | 
				
			||||||
            .collect::<Vec<sqlx::Result<GroupIdAndName>>>()
 | 
					            .collect::<Vec<sqlx::Result<GroupDetails>>>()
 | 
				
			||||||
            .await
 | 
					            .await
 | 
				
			||||||
            .into_iter()
 | 
					            .into_iter()
 | 
				
			||||||
            // Transform it into a single result (the first error if any), and group the group_ids
 | 
					            // Transform it into a single result (the first error if any), and group the group_ids
 | 
				
			||||||
@ -355,18 +361,23 @@ impl BackendHandler for SqlBackendHandler {
 | 
				
			|||||||
            Users::FirstName,
 | 
					            Users::FirstName,
 | 
				
			||||||
            Users::LastName,
 | 
					            Users::LastName,
 | 
				
			||||||
            Users::CreationDate,
 | 
					            Users::CreationDate,
 | 
				
			||||||
 | 
					            Users::Uuid,
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					        let now = chrono::Utc::now();
 | 
				
			||||||
 | 
					        let uuid = Uuid::from_name_and_date(request.user_id.as_str(), &now);
 | 
				
			||||||
 | 
					        let values = vec![
 | 
				
			||||||
 | 
					            request.user_id.into(),
 | 
				
			||||||
 | 
					            request.email.into(),
 | 
				
			||||||
 | 
					            request.display_name.unwrap_or_default().into(),
 | 
				
			||||||
 | 
					            request.first_name.unwrap_or_default().into(),
 | 
				
			||||||
 | 
					            request.last_name.unwrap_or_default().into(),
 | 
				
			||||||
 | 
					            now.naive_utc().into(),
 | 
				
			||||||
 | 
					            uuid.into(),
 | 
				
			||||||
        ];
 | 
					        ];
 | 
				
			||||||
        let (query, values) = Query::insert()
 | 
					        let (query, values) = Query::insert()
 | 
				
			||||||
            .into_table(Users::Table)
 | 
					            .into_table(Users::Table)
 | 
				
			||||||
            .columns(columns)
 | 
					            .columns(columns)
 | 
				
			||||||
            .values_panic(vec![
 | 
					            .values_panic(values)
 | 
				
			||||||
                request.user_id.into(),
 | 
					 | 
				
			||||||
                request.email.into(),
 | 
					 | 
				
			||||||
                request.display_name.unwrap_or_default().into(),
 | 
					 | 
				
			||||||
                request.first_name.unwrap_or_default().into(),
 | 
					 | 
				
			||||||
                request.last_name.unwrap_or_default().into(),
 | 
					 | 
				
			||||||
                chrono::Utc::now().naive_utc().into(),
 | 
					 | 
				
			||||||
            ])
 | 
					 | 
				
			||||||
            .build_sqlx(DbQueryBuilder {});
 | 
					            .build_sqlx(DbQueryBuilder {});
 | 
				
			||||||
        debug!(%query);
 | 
					        debug!(%query);
 | 
				
			||||||
        query_with(query.as_str(), values)
 | 
					        query_with(query.as_str(), values)
 | 
				
			||||||
@ -445,10 +456,19 @@ impl BackendHandler for SqlBackendHandler {
 | 
				
			|||||||
    #[instrument(skip_all, level = "debug", ret, err)]
 | 
					    #[instrument(skip_all, level = "debug", ret, err)]
 | 
				
			||||||
    async fn create_group(&self, group_name: &str) -> Result<GroupId> {
 | 
					    async fn create_group(&self, group_name: &str) -> Result<GroupId> {
 | 
				
			||||||
        debug!(?group_name);
 | 
					        debug!(?group_name);
 | 
				
			||||||
 | 
					        let now = chrono::Utc::now();
 | 
				
			||||||
        let (query, values) = Query::insert()
 | 
					        let (query, values) = Query::insert()
 | 
				
			||||||
            .into_table(Groups::Table)
 | 
					            .into_table(Groups::Table)
 | 
				
			||||||
            .columns(vec![Groups::DisplayName])
 | 
					            .columns(vec![
 | 
				
			||||||
            .values_panic(vec![group_name.into()])
 | 
					                Groups::DisplayName,
 | 
				
			||||||
 | 
					                Groups::CreationDate,
 | 
				
			||||||
 | 
					                Groups::Uuid,
 | 
				
			||||||
 | 
					            ])
 | 
				
			||||||
 | 
					            .values_panic(vec![
 | 
				
			||||||
 | 
					                group_name.into(),
 | 
				
			||||||
 | 
					                now.naive_utc().into(),
 | 
				
			||||||
 | 
					                Uuid::from_name_and_date(group_name, &now).into(),
 | 
				
			||||||
 | 
					            ])
 | 
				
			||||||
            .build_sqlx(DbQueryBuilder {});
 | 
					            .build_sqlx(DbQueryBuilder {});
 | 
				
			||||||
        debug!(%query);
 | 
					        debug!(%query);
 | 
				
			||||||
        query_with(query.as_str(), values)
 | 
					        query_with(query.as_str(), values)
 | 
				
			||||||
@ -707,7 +727,7 @@ mod tests {
 | 
				
			|||||||
                        u.groups
 | 
					                        u.groups
 | 
				
			||||||
                            .unwrap()
 | 
					                            .unwrap()
 | 
				
			||||||
                            .into_iter()
 | 
					                            .into_iter()
 | 
				
			||||||
                            .map(|g| g.0)
 | 
					                            .map(|g| g.group_id)
 | 
				
			||||||
                            .collect::<Vec<_>>(),
 | 
					                            .collect::<Vec<_>>(),
 | 
				
			||||||
                    )
 | 
					                    )
 | 
				
			||||||
                })
 | 
					                })
 | 
				
			||||||
@ -721,6 +741,29 @@ mod tests {
 | 
				
			|||||||
                ]
 | 
					                ]
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            let users = handler
 | 
				
			||||||
 | 
					                .list_users(None, true)
 | 
				
			||||||
 | 
					                .await
 | 
				
			||||||
 | 
					                .unwrap()
 | 
				
			||||||
 | 
					                .into_iter()
 | 
				
			||||||
 | 
					                .map(|u| {
 | 
				
			||||||
 | 
					                    (
 | 
				
			||||||
 | 
					                        u.user.creation_date,
 | 
				
			||||||
 | 
					                        u.groups
 | 
				
			||||||
 | 
					                            .unwrap()
 | 
				
			||||||
 | 
					                            .into_iter()
 | 
				
			||||||
 | 
					                            .map(|g| g.creation_date)
 | 
				
			||||||
 | 
					                            .collect::<Vec<_>>(),
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
 | 
					                .collect::<Vec<_>>();
 | 
				
			||||||
 | 
					            for (user_date, groups) in users {
 | 
				
			||||||
 | 
					                for group_date in groups {
 | 
				
			||||||
 | 
					                    assert_ne!(user_date, group_date);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    #[tokio::test]
 | 
					    #[tokio::test]
 | 
				
			||||||
@ -738,62 +781,33 @@ mod tests {
 | 
				
			|||||||
        insert_membership(&handler, group_1, "patrick").await;
 | 
					        insert_membership(&handler, group_1, "patrick").await;
 | 
				
			||||||
        insert_membership(&handler, group_2, "patrick").await;
 | 
					        insert_membership(&handler, group_2, "patrick").await;
 | 
				
			||||||
        insert_membership(&handler, group_2, "John").await;
 | 
					        insert_membership(&handler, group_2, "John").await;
 | 
				
			||||||
 | 
					        let get_group_ids = |filter| async {
 | 
				
			||||||
 | 
					            handler
 | 
				
			||||||
 | 
					                .list_groups(filter)
 | 
				
			||||||
 | 
					                .await
 | 
				
			||||||
 | 
					                .unwrap()
 | 
				
			||||||
 | 
					                .into_iter()
 | 
				
			||||||
 | 
					                .map(|g| g.id)
 | 
				
			||||||
 | 
					                .collect::<Vec<_>>()
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        assert_eq!(get_group_ids(None).await, vec![group_1, group_3, group_2]);
 | 
				
			||||||
        assert_eq!(
 | 
					        assert_eq!(
 | 
				
			||||||
            handler.list_groups(None).await.unwrap(),
 | 
					            get_group_ids(Some(GroupRequestFilter::Or(vec![
 | 
				
			||||||
            vec![
 | 
					                GroupRequestFilter::DisplayName("Empty Group".to_string()),
 | 
				
			||||||
                Group {
 | 
					                GroupRequestFilter::Member(UserId::new("bob")),
 | 
				
			||||||
                    id: group_1,
 | 
					            ])))
 | 
				
			||||||
                    display_name: "Best Group".to_string(),
 | 
					            .await,
 | 
				
			||||||
                    users: vec![UserId::new("bob"), UserId::new("patrick")]
 | 
					            vec![group_1, group_3]
 | 
				
			||||||
                },
 | 
					 | 
				
			||||||
                Group {
 | 
					 | 
				
			||||||
                    id: group_3,
 | 
					 | 
				
			||||||
                    display_name: "Empty Group".to_string(),
 | 
					 | 
				
			||||||
                    users: vec![]
 | 
					 | 
				
			||||||
                },
 | 
					 | 
				
			||||||
                Group {
 | 
					 | 
				
			||||||
                    id: group_2,
 | 
					 | 
				
			||||||
                    display_name: "Worst Group".to_string(),
 | 
					 | 
				
			||||||
                    users: vec![UserId::new("john"), UserId::new("patrick")]
 | 
					 | 
				
			||||||
                },
 | 
					 | 
				
			||||||
            ]
 | 
					 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
        assert_eq!(
 | 
					        assert_eq!(
 | 
				
			||||||
            handler
 | 
					            get_group_ids(Some(GroupRequestFilter::And(vec![
 | 
				
			||||||
                .list_groups(Some(GroupRequestFilter::Or(vec![
 | 
					                GroupRequestFilter::Not(Box::new(GroupRequestFilter::DisplayName(
 | 
				
			||||||
                    GroupRequestFilter::DisplayName("Empty Group".to_string()),
 | 
					                    "value".to_string()
 | 
				
			||||||
                    GroupRequestFilter::Member(UserId::new("bob")),
 | 
					                ))),
 | 
				
			||||||
                ])))
 | 
					                GroupRequestFilter::GroupId(group_1),
 | 
				
			||||||
                .await
 | 
					            ])))
 | 
				
			||||||
                .unwrap(),
 | 
					            .await,
 | 
				
			||||||
            vec![
 | 
					            vec![group_1]
 | 
				
			||||||
                Group {
 | 
					 | 
				
			||||||
                    id: group_1,
 | 
					 | 
				
			||||||
                    display_name: "Best Group".to_string(),
 | 
					 | 
				
			||||||
                    users: vec![UserId::new("bob"), UserId::new("patrick")]
 | 
					 | 
				
			||||||
                },
 | 
					 | 
				
			||||||
                Group {
 | 
					 | 
				
			||||||
                    id: group_3,
 | 
					 | 
				
			||||||
                    display_name: "Empty Group".to_string(),
 | 
					 | 
				
			||||||
                    users: vec![]
 | 
					 | 
				
			||||||
                },
 | 
					 | 
				
			||||||
            ]
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
        assert_eq!(
 | 
					 | 
				
			||||||
            handler
 | 
					 | 
				
			||||||
                .list_groups(Some(GroupRequestFilter::And(vec![
 | 
					 | 
				
			||||||
                    GroupRequestFilter::Not(Box::new(GroupRequestFilter::DisplayName(
 | 
					 | 
				
			||||||
                        "value".to_string()
 | 
					 | 
				
			||||||
                    ))),
 | 
					 | 
				
			||||||
                    GroupRequestFilter::GroupId(group_1),
 | 
					 | 
				
			||||||
                ])))
 | 
					 | 
				
			||||||
                .await
 | 
					 | 
				
			||||||
                .unwrap(),
 | 
					 | 
				
			||||||
            vec![Group {
 | 
					 | 
				
			||||||
                id: group_1,
 | 
					 | 
				
			||||||
                display_name: "Best Group".to_string(),
 | 
					 | 
				
			||||||
                users: vec![UserId::new("bob"), UserId::new("patrick")]
 | 
					 | 
				
			||||||
            }]
 | 
					 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -846,26 +860,20 @@ mod tests {
 | 
				
			|||||||
        insert_membership(&handler, group_1, "bob").await;
 | 
					        insert_membership(&handler, group_1, "bob").await;
 | 
				
			||||||
        insert_membership(&handler, group_1, "patrick").await;
 | 
					        insert_membership(&handler, group_1, "patrick").await;
 | 
				
			||||||
        insert_membership(&handler, group_2, "patrick").await;
 | 
					        insert_membership(&handler, group_2, "patrick").await;
 | 
				
			||||||
        let mut bob_groups = HashSet::new();
 | 
					        let get_group_ids = |user: &'static str| async {
 | 
				
			||||||
        bob_groups.insert(GroupIdAndName(group_1, "Group1".to_string()));
 | 
					            let mut groups = handler
 | 
				
			||||||
        let mut patrick_groups = HashSet::new();
 | 
					                .get_user_groups(&UserId::new(user))
 | 
				
			||||||
        patrick_groups.insert(GroupIdAndName(group_1, "Group1".to_string()));
 | 
					 | 
				
			||||||
        patrick_groups.insert(GroupIdAndName(group_2, "Group2".to_string()));
 | 
					 | 
				
			||||||
        assert_eq!(
 | 
					 | 
				
			||||||
            handler.get_user_groups(&UserId::new("bob")).await.unwrap(),
 | 
					 | 
				
			||||||
            bob_groups
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
        assert_eq!(
 | 
					 | 
				
			||||||
            handler
 | 
					 | 
				
			||||||
                .get_user_groups(&UserId::new("patrick"))
 | 
					 | 
				
			||||||
                .await
 | 
					                .await
 | 
				
			||||||
                .unwrap(),
 | 
					                .unwrap()
 | 
				
			||||||
            patrick_groups
 | 
					                .into_iter()
 | 
				
			||||||
        );
 | 
					                .map(|g| g.group_id)
 | 
				
			||||||
        assert_eq!(
 | 
					                .collect::<Vec<_>>();
 | 
				
			||||||
            handler.get_user_groups(&UserId::new("John")).await.unwrap(),
 | 
					            groups.sort_by(|g1, g2| g1.0.cmp(&g2.0));
 | 
				
			||||||
            HashSet::new()
 | 
					            groups
 | 
				
			||||||
        );
 | 
					        };
 | 
				
			||||||
 | 
					        assert_eq!(get_group_ids("bob").await, vec![group_1]);
 | 
				
			||||||
 | 
					        assert_eq!(get_group_ids("patrick").await, vec![group_1, group_2]);
 | 
				
			||||||
 | 
					        assert_eq!(get_group_ids("John").await, vec![]);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    #[tokio::test]
 | 
					    #[tokio::test]
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,7 @@
 | 
				
			|||||||
use super::handler::{GroupId, UserId};
 | 
					use super::handler::{GroupId, UserId, Uuid};
 | 
				
			||||||
use sea_query::*;
 | 
					use sea_query::*;
 | 
				
			||||||
 | 
					use sqlx::Row;
 | 
				
			||||||
 | 
					use tracing::warn;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub type Pool = sqlx::sqlite::SqlitePool;
 | 
					pub type Pool = sqlx::sqlite::SqlitePool;
 | 
				
			||||||
pub type PoolOptions = sqlx::sqlite::SqlitePoolOptions;
 | 
					pub type PoolOptions = sqlx::sqlite::SqlitePoolOptions;
 | 
				
			||||||
@ -12,56 +14,6 @@ impl From<GroupId> for Value {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl<DB> sqlx::Type<DB> for GroupId
 | 
					 | 
				
			||||||
where
 | 
					 | 
				
			||||||
    DB: sqlx::Database,
 | 
					 | 
				
			||||||
    i32: sqlx::Type<DB>,
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    fn type_info() -> <DB as sqlx::Database>::TypeInfo {
 | 
					 | 
				
			||||||
        <i32 as sqlx::Type<DB>>::type_info()
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    fn compatible(ty: &<DB as sqlx::Database>::TypeInfo) -> bool {
 | 
					 | 
				
			||||||
        <i32 as sqlx::Type<DB>>::compatible(ty)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl<'r, DB> sqlx::Decode<'r, DB> for GroupId
 | 
					 | 
				
			||||||
where
 | 
					 | 
				
			||||||
    DB: sqlx::Database,
 | 
					 | 
				
			||||||
    i32: sqlx::Decode<'r, DB>,
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    fn decode(
 | 
					 | 
				
			||||||
        value: <DB as sqlx::database::HasValueRef<'r>>::ValueRef,
 | 
					 | 
				
			||||||
    ) -> Result<Self, Box<dyn std::error::Error + Sync + Send + 'static>> {
 | 
					 | 
				
			||||||
        <i32 as sqlx::Decode<'r, DB>>::decode(value).map(GroupId)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl<DB> sqlx::Type<DB> for UserId
 | 
					 | 
				
			||||||
where
 | 
					 | 
				
			||||||
    DB: sqlx::Database,
 | 
					 | 
				
			||||||
    String: sqlx::Type<DB>,
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    fn type_info() -> <DB as sqlx::Database>::TypeInfo {
 | 
					 | 
				
			||||||
        <String as sqlx::Type<DB>>::type_info()
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    fn compatible(ty: &<DB as sqlx::Database>::TypeInfo) -> bool {
 | 
					 | 
				
			||||||
        <String as sqlx::Type<DB>>::compatible(ty)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl<'r, DB> sqlx::Decode<'r, DB> for UserId
 | 
					 | 
				
			||||||
where
 | 
					 | 
				
			||||||
    DB: sqlx::Database,
 | 
					 | 
				
			||||||
    String: sqlx::Decode<'r, DB>,
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    fn decode(
 | 
					 | 
				
			||||||
        value: <DB as sqlx::database::HasValueRef<'r>>::ValueRef,
 | 
					 | 
				
			||||||
    ) -> Result<Self, Box<dyn std::error::Error + Sync + Send + 'static>> {
 | 
					 | 
				
			||||||
        <String as sqlx::Decode<'r, DB>>::decode(value).map(|s| UserId::new(&s))
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl From<UserId> for sea_query::Value {
 | 
					impl From<UserId> for sea_query::Value {
 | 
				
			||||||
    fn from(user_id: UserId) -> Self {
 | 
					    fn from(user_id: UserId) -> Self {
 | 
				
			||||||
        user_id.into_string().into()
 | 
					        user_id.into_string().into()
 | 
				
			||||||
@ -74,6 +26,18 @@ impl From<&UserId> for sea_query::Value {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl From<Uuid> for sea_query::Value {
 | 
				
			||||||
 | 
					    fn from(uuid: Uuid) -> Self {
 | 
				
			||||||
 | 
					        uuid.as_str().into()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl From<&Uuid> for sea_query::Value {
 | 
				
			||||||
 | 
					    fn from(uuid: &Uuid) -> Self {
 | 
				
			||||||
 | 
					        uuid.as_str().into()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Iden)]
 | 
					#[derive(Iden)]
 | 
				
			||||||
pub enum Users {
 | 
					pub enum Users {
 | 
				
			||||||
    Table,
 | 
					    Table,
 | 
				
			||||||
@ -87,6 +51,7 @@ pub enum Users {
 | 
				
			|||||||
    PasswordHash,
 | 
					    PasswordHash,
 | 
				
			||||||
    TotpSecret,
 | 
					    TotpSecret,
 | 
				
			||||||
    MfaType,
 | 
					    MfaType,
 | 
				
			||||||
 | 
					    Uuid,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Iden)]
 | 
					#[derive(Iden)]
 | 
				
			||||||
@ -94,6 +59,8 @@ pub enum Groups {
 | 
				
			|||||||
    Table,
 | 
					    Table,
 | 
				
			||||||
    GroupId,
 | 
					    GroupId,
 | 
				
			||||||
    DisplayName,
 | 
					    DisplayName,
 | 
				
			||||||
 | 
					    CreationDate,
 | 
				
			||||||
 | 
					    Uuid,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Iden)]
 | 
					#[derive(Iden)]
 | 
				
			||||||
@ -103,6 +70,19 @@ pub enum Memberships {
 | 
				
			|||||||
    GroupId,
 | 
					    GroupId,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async fn column_exists(pool: &Pool, table_name: &str, column_name: &str) -> sqlx::Result<bool> {
 | 
				
			||||||
 | 
					    // Sqlite specific
 | 
				
			||||||
 | 
					    let query = format!(
 | 
				
			||||||
 | 
					        "SELECT COUNT(*) AS col_count FROM pragma_table_info('{}') WHERE name = '{}'",
 | 
				
			||||||
 | 
					        table_name, column_name
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    Ok(sqlx::query(&query)
 | 
				
			||||||
 | 
					        .fetch_one(pool)
 | 
				
			||||||
 | 
					        .await?
 | 
				
			||||||
 | 
					        .get::<i32, _>("col_count")
 | 
				
			||||||
 | 
					        > 0)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub async fn init_table(pool: &Pool) -> sqlx::Result<()> {
 | 
					pub async fn init_table(pool: &Pool) -> sqlx::Result<()> {
 | 
				
			||||||
    // SQLite needs this pragma to be turned on. Other DB might not understand this, so ignore the
 | 
					    // SQLite needs this pragma to be turned on. Other DB might not understand this, so ignore the
 | 
				
			||||||
    // error.
 | 
					    // error.
 | 
				
			||||||
@ -130,6 +110,7 @@ pub async fn init_table(pool: &Pool) -> sqlx::Result<()> {
 | 
				
			|||||||
            .col(ColumnDef::new(Users::PasswordHash).binary())
 | 
					            .col(ColumnDef::new(Users::PasswordHash).binary())
 | 
				
			||||||
            .col(ColumnDef::new(Users::TotpSecret).string_len(64))
 | 
					            .col(ColumnDef::new(Users::TotpSecret).string_len(64))
 | 
				
			||||||
            .col(ColumnDef::new(Users::MfaType).string_len(64))
 | 
					            .col(ColumnDef::new(Users::MfaType).string_len(64))
 | 
				
			||||||
 | 
					            .col(ColumnDef::new(Users::Uuid).string_len(36).not_null())
 | 
				
			||||||
            .to_string(DbQueryBuilder {}),
 | 
					            .to_string(DbQueryBuilder {}),
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    .execute(pool)
 | 
					    .execute(pool)
 | 
				
			||||||
@ -151,11 +132,141 @@ pub async fn init_table(pool: &Pool) -> sqlx::Result<()> {
 | 
				
			|||||||
                    .unique_key()
 | 
					                    .unique_key()
 | 
				
			||||||
                    .not_null(),
 | 
					                    .not_null(),
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					            .col(ColumnDef::new(Users::CreationDate).date_time().not_null())
 | 
				
			||||||
 | 
					            .col(ColumnDef::new(Users::Uuid).string_len(36).not_null())
 | 
				
			||||||
            .to_string(DbQueryBuilder {}),
 | 
					            .to_string(DbQueryBuilder {}),
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    .execute(pool)
 | 
					    .execute(pool)
 | 
				
			||||||
    .await?;
 | 
					    .await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // If the creation_date column doesn't exist, add it.
 | 
				
			||||||
 | 
					    if !column_exists(
 | 
				
			||||||
 | 
					        pool,
 | 
				
			||||||
 | 
					        &*Groups::Table.to_string(),
 | 
				
			||||||
 | 
					        &*Groups::CreationDate.to_string(),
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    .await?
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        warn!("`creation_date` column not found in `groups`, creating it");
 | 
				
			||||||
 | 
					        sqlx::query(
 | 
				
			||||||
 | 
					            &Table::alter()
 | 
				
			||||||
 | 
					                .table(Groups::Table)
 | 
				
			||||||
 | 
					                .add_column(
 | 
				
			||||||
 | 
					                    ColumnDef::new(Groups::CreationDate)
 | 
				
			||||||
 | 
					                        .date_time()
 | 
				
			||||||
 | 
					                        .not_null()
 | 
				
			||||||
 | 
					                        .default(chrono::Utc::now().naive_utc()),
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                .to_string(DbQueryBuilder {}),
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        .execute(pool)
 | 
				
			||||||
 | 
					        .await?;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // If the uuid column doesn't exist, add it.
 | 
				
			||||||
 | 
					    if !column_exists(
 | 
				
			||||||
 | 
					        pool,
 | 
				
			||||||
 | 
					        &*Groups::Table.to_string(),
 | 
				
			||||||
 | 
					        &*Groups::Uuid.to_string(),
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    .await?
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        warn!("`uuid` column not found in `groups`, creating it");
 | 
				
			||||||
 | 
					        sqlx::query(
 | 
				
			||||||
 | 
					            &Table::alter()
 | 
				
			||||||
 | 
					                .table(Groups::Table)
 | 
				
			||||||
 | 
					                .add_column(
 | 
				
			||||||
 | 
					                    ColumnDef::new(Groups::Uuid)
 | 
				
			||||||
 | 
					                        .string_len(36)
 | 
				
			||||||
 | 
					                        .not_null()
 | 
				
			||||||
 | 
					                        .default(""),
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                .to_string(DbQueryBuilder {}),
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        .execute(pool)
 | 
				
			||||||
 | 
					        .await?;
 | 
				
			||||||
 | 
					        for row in sqlx::query(
 | 
				
			||||||
 | 
					            &Query::select()
 | 
				
			||||||
 | 
					                .from(Groups::Table)
 | 
				
			||||||
 | 
					                .column(Groups::GroupId)
 | 
				
			||||||
 | 
					                .column(Groups::DisplayName)
 | 
				
			||||||
 | 
					                .column(Groups::CreationDate)
 | 
				
			||||||
 | 
					                .to_string(DbQueryBuilder {}),
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        .fetch_all(pool)
 | 
				
			||||||
 | 
					        .await?
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            sqlx::query(
 | 
				
			||||||
 | 
					                &Query::update()
 | 
				
			||||||
 | 
					                    .table(Groups::Table)
 | 
				
			||||||
 | 
					                    .value(
 | 
				
			||||||
 | 
					                        Groups::Uuid,
 | 
				
			||||||
 | 
					                        Uuid::from_name_and_date(
 | 
				
			||||||
 | 
					                            &row.get::<String, _>(&*Groups::DisplayName.to_string()),
 | 
				
			||||||
 | 
					                            &row.get::<chrono::DateTime<chrono::Utc>, _>(
 | 
				
			||||||
 | 
					                                &*Groups::CreationDate.to_string(),
 | 
				
			||||||
 | 
					                            ),
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
 | 
					                        .into(),
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                    .and_where(
 | 
				
			||||||
 | 
					                        Expr::col(Groups::GroupId)
 | 
				
			||||||
 | 
					                            .eq(row.get::<GroupId, _>(&*Groups::GroupId.to_string())),
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                    .to_string(DbQueryBuilder {}),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .execute(pool)
 | 
				
			||||||
 | 
					            .await?;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if !column_exists(pool, &*Users::Table.to_string(), &*Users::Uuid.to_string()).await? {
 | 
				
			||||||
 | 
					        warn!("`uuid` column not found in `users`, creating it");
 | 
				
			||||||
 | 
					        sqlx::query(
 | 
				
			||||||
 | 
					            &Table::alter()
 | 
				
			||||||
 | 
					                .table(Users::Table)
 | 
				
			||||||
 | 
					                .add_column(
 | 
				
			||||||
 | 
					                    ColumnDef::new(Users::Uuid)
 | 
				
			||||||
 | 
					                        .string_len(36)
 | 
				
			||||||
 | 
					                        .not_null()
 | 
				
			||||||
 | 
					                        .default(""),
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                .to_string(DbQueryBuilder {}),
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        .execute(pool)
 | 
				
			||||||
 | 
					        .await?;
 | 
				
			||||||
 | 
					        for row in sqlx::query(
 | 
				
			||||||
 | 
					            &Query::select()
 | 
				
			||||||
 | 
					                .from(Users::Table)
 | 
				
			||||||
 | 
					                .column(Users::UserId)
 | 
				
			||||||
 | 
					                .column(Users::CreationDate)
 | 
				
			||||||
 | 
					                .to_string(DbQueryBuilder {}),
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        .fetch_all(pool)
 | 
				
			||||||
 | 
					        .await?
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            let user_id = row.get::<UserId, _>(&*Users::UserId.to_string());
 | 
				
			||||||
 | 
					            sqlx::query(
 | 
				
			||||||
 | 
					                &Query::update()
 | 
				
			||||||
 | 
					                    .table(Users::Table)
 | 
				
			||||||
 | 
					                    .value(
 | 
				
			||||||
 | 
					                        Users::Uuid,
 | 
				
			||||||
 | 
					                        Uuid::from_name_and_date(
 | 
				
			||||||
 | 
					                            user_id.as_str(),
 | 
				
			||||||
 | 
					                            &row.get::<chrono::DateTime<chrono::Utc>, _>(
 | 
				
			||||||
 | 
					                                &*Users::CreationDate.to_string(),
 | 
				
			||||||
 | 
					                            ),
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
 | 
					                        .into(),
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                    .and_where(Expr::col(Users::UserId).eq(user_id))
 | 
				
			||||||
 | 
					                    .to_string(DbQueryBuilder {}),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .execute(pool)
 | 
				
			||||||
 | 
					            .await?;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    sqlx::query(
 | 
					    sqlx::query(
 | 
				
			||||||
        &Table::create()
 | 
					        &Table::create()
 | 
				
			||||||
            .table(Memberships::Table)
 | 
					            .table(Memberships::Table)
 | 
				
			||||||
@ -196,13 +307,13 @@ mod tests {
 | 
				
			|||||||
    use chrono::prelude::*;
 | 
					    use chrono::prelude::*;
 | 
				
			||||||
    use sqlx::{Column, Row};
 | 
					    use sqlx::{Column, Row};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    #[actix_rt::test]
 | 
					    #[tokio::test]
 | 
				
			||||||
    async fn test_init_table() {
 | 
					    async fn test_init_table() {
 | 
				
			||||||
        let sql_pool = PoolOptions::new().connect("sqlite::memory:").await.unwrap();
 | 
					        let sql_pool = PoolOptions::new().connect("sqlite::memory:").await.unwrap();
 | 
				
			||||||
        init_table(&sql_pool).await.unwrap();
 | 
					        init_table(&sql_pool).await.unwrap();
 | 
				
			||||||
        sqlx::query(r#"INSERT INTO users
 | 
					        sqlx::query(r#"INSERT INTO users
 | 
				
			||||||
      (user_id, email, display_name, first_name, last_name, creation_date, password_hash)
 | 
					      (user_id, email, display_name, first_name, last_name, creation_date, password_hash, uuid)
 | 
				
			||||||
      VALUES ("bôb", "böb@bob.bob", "Bob Bobbersön", "Bob", "Bobberson", "1970-01-01 00:00:00", "bob00")"#).execute(&sql_pool).await.unwrap();
 | 
					      VALUES ("bôb", "böb@bob.bob", "Bob Bobbersön", "Bob", "Bobberson", "1970-01-01 00:00:00", "bob00", "abc")"#).execute(&sql_pool).await.unwrap();
 | 
				
			||||||
        let row =
 | 
					        let row =
 | 
				
			||||||
            sqlx::query(r#"SELECT display_name, creation_date FROM users WHERE user_id = "bôb""#)
 | 
					            sqlx::query(r#"SELECT display_name, creation_date FROM users WHERE user_id = "bôb""#)
 | 
				
			||||||
                .fetch_one(&sql_pool)
 | 
					                .fetch_one(&sql_pool)
 | 
				
			||||||
@ -216,10 +327,49 @@ mod tests {
 | 
				
			|||||||
        );
 | 
					        );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    #[actix_rt::test]
 | 
					    #[tokio::test]
 | 
				
			||||||
    async fn test_already_init_table() {
 | 
					    async fn test_already_init_table() {
 | 
				
			||||||
        let sql_pool = PoolOptions::new().connect("sqlite::memory:").await.unwrap();
 | 
					        let sql_pool = PoolOptions::new().connect("sqlite::memory:").await.unwrap();
 | 
				
			||||||
        init_table(&sql_pool).await.unwrap();
 | 
					        init_table(&sql_pool).await.unwrap();
 | 
				
			||||||
        init_table(&sql_pool).await.unwrap();
 | 
					        init_table(&sql_pool).await.unwrap();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[tokio::test]
 | 
				
			||||||
 | 
					    async fn test_migrate_tables() {
 | 
				
			||||||
 | 
					        // Test that we add the column creation_date to groups and uuid to users and groups.
 | 
				
			||||||
 | 
					        let sql_pool = PoolOptions::new().connect("sqlite::memory:").await.unwrap();
 | 
				
			||||||
 | 
					        sqlx::query(r#"CREATE TABLE users ( user_id TEXT , creation_date TEXT);"#)
 | 
				
			||||||
 | 
					            .execute(&sql_pool)
 | 
				
			||||||
 | 
					            .await
 | 
				
			||||||
 | 
					            .unwrap();
 | 
				
			||||||
 | 
					        sqlx::query(
 | 
				
			||||||
 | 
					            r#"INSERT INTO users (user_id, creation_date)
 | 
				
			||||||
 | 
					                       VALUES ("bôb", "1970-01-01 00:00:00")"#,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        .execute(&sql_pool)
 | 
				
			||||||
 | 
					        .await
 | 
				
			||||||
 | 
					        .unwrap();
 | 
				
			||||||
 | 
					        sqlx::query(r#"CREATE TABLE groups ( group_id int, display_name TEXT );"#)
 | 
				
			||||||
 | 
					            .execute(&sql_pool)
 | 
				
			||||||
 | 
					            .await
 | 
				
			||||||
 | 
					            .unwrap();
 | 
				
			||||||
 | 
					        init_table(&sql_pool).await.unwrap();
 | 
				
			||||||
 | 
					        sqlx::query(
 | 
				
			||||||
 | 
					            r#"INSERT INTO groups (group_id, display_name, creation_date, uuid)
 | 
				
			||||||
 | 
					                      VALUES (3, "test", "1970-01-01 00:00:00", "abc")"#,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        .execute(&sql_pool)
 | 
				
			||||||
 | 
					        .await
 | 
				
			||||||
 | 
					        .unwrap();
 | 
				
			||||||
 | 
					        assert_eq!(
 | 
				
			||||||
 | 
					            sqlx::query(r#"SELECT uuid FROM users"#)
 | 
				
			||||||
 | 
					                .fetch_all(&sql_pool)
 | 
				
			||||||
 | 
					                .await
 | 
				
			||||||
 | 
					                .unwrap()
 | 
				
			||||||
 | 
					                .into_iter()
 | 
				
			||||||
 | 
					                .map(|row| row.get::<Uuid, _>("uuid"))
 | 
				
			||||||
 | 
					                .collect::<Vec<_>>(),
 | 
				
			||||||
 | 
					            vec![crate::uuid!("a02eaf13-48a7-30f6-a3d4-040ff7c52b04")]
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -25,7 +25,7 @@ use lldap_auth::{login, password_reset, registration, JWTClaims};
 | 
				
			|||||||
use crate::{
 | 
					use crate::{
 | 
				
			||||||
    domain::{
 | 
					    domain::{
 | 
				
			||||||
        error::DomainError,
 | 
					        error::DomainError,
 | 
				
			||||||
        handler::{BackendHandler, BindRequest, GroupIdAndName, LoginHandler, UserId},
 | 
					        handler::{BackendHandler, BindRequest, GroupDetails, LoginHandler, UserId},
 | 
				
			||||||
        opaque_handler::OpaqueHandler,
 | 
					        opaque_handler::OpaqueHandler,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    infra::{
 | 
					    infra::{
 | 
				
			||||||
@ -37,12 +37,12 @@ use crate::{
 | 
				
			|||||||
type Token<S> = jwt::Token<jwt::Header, JWTClaims, S>;
 | 
					type Token<S> = jwt::Token<jwt::Header, JWTClaims, S>;
 | 
				
			||||||
type SignedToken = Token<jwt::token::Signed>;
 | 
					type SignedToken = Token<jwt::token::Signed>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn create_jwt(key: &Hmac<Sha512>, user: String, groups: HashSet<GroupIdAndName>) -> SignedToken {
 | 
					fn create_jwt(key: &Hmac<Sha512>, user: String, groups: HashSet<GroupDetails>) -> SignedToken {
 | 
				
			||||||
    let claims = JWTClaims {
 | 
					    let claims = JWTClaims {
 | 
				
			||||||
        exp: Utc::now() + chrono::Duration::days(1),
 | 
					        exp: Utc::now() + chrono::Duration::days(1),
 | 
				
			||||||
        iat: Utc::now(),
 | 
					        iat: Utc::now(),
 | 
				
			||||||
        user,
 | 
					        user,
 | 
				
			||||||
        groups: groups.into_iter().map(|g| g.1).collect(),
 | 
					        groups: groups.into_iter().map(|g| g.display_name).collect(),
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    let header = jwt::Header {
 | 
					    let header = jwt::Header {
 | 
				
			||||||
        algorithm: jwt::AlgorithmType::Hs512,
 | 
					        algorithm: jwt::AlgorithmType::Hs512,
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,4 @@
 | 
				
			|||||||
use crate::domain::handler::{BackendHandler, GroupId, GroupIdAndName, UserId};
 | 
					use crate::domain::handler::{BackendHandler, GroupDetails, GroupId, UserId};
 | 
				
			||||||
use juniper::{graphql_object, FieldResult, GraphQLInputObject};
 | 
					use juniper::{graphql_object, FieldResult, GraphQLInputObject};
 | 
				
			||||||
use serde::{Deserialize, Serialize};
 | 
					use serde::{Deserialize, Serialize};
 | 
				
			||||||
use tracing::{debug, debug_span, Instrument};
 | 
					use tracing::{debug, debug_span, Instrument};
 | 
				
			||||||
@ -184,6 +184,7 @@ pub struct User<Handler: BackendHandler> {
 | 
				
			|||||||
    _phantom: std::marker::PhantomData<Box<Handler>>,
 | 
					    _phantom: std::marker::PhantomData<Box<Handler>>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[cfg(test)]
 | 
				
			||||||
impl<Handler: BackendHandler> Default for User<Handler> {
 | 
					impl<Handler: BackendHandler> Default for User<Handler> {
 | 
				
			||||||
    fn default() -> Self {
 | 
					    fn default() -> Self {
 | 
				
			||||||
        Self {
 | 
					        Self {
 | 
				
			||||||
@ -257,6 +258,7 @@ impl<Handler: BackendHandler> From<DomainUserAndGroups> for User<Handler> {
 | 
				
			|||||||
pub struct Group<Handler: BackendHandler> {
 | 
					pub struct Group<Handler: BackendHandler> {
 | 
				
			||||||
    group_id: i32,
 | 
					    group_id: i32,
 | 
				
			||||||
    display_name: String,
 | 
					    display_name: String,
 | 
				
			||||||
 | 
					    creation_date: chrono::DateTime<chrono::Utc>,
 | 
				
			||||||
    members: Option<Vec<String>>,
 | 
					    members: Option<Vec<String>>,
 | 
				
			||||||
    _phantom: std::marker::PhantomData<Box<Handler>>,
 | 
					    _phantom: std::marker::PhantomData<Box<Handler>>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -291,11 +293,12 @@ impl<Handler: BackendHandler + Sync> Group<Handler> {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl<Handler: BackendHandler> From<GroupIdAndName> for Group<Handler> {
 | 
					impl<Handler: BackendHandler> From<GroupDetails> for Group<Handler> {
 | 
				
			||||||
    fn from(group_id_and_name: GroupIdAndName) -> Self {
 | 
					    fn from(group_details: GroupDetails) -> Self {
 | 
				
			||||||
        Self {
 | 
					        Self {
 | 
				
			||||||
            group_id: group_id_and_name.0 .0,
 | 
					            group_id: group_details.group_id.0,
 | 
				
			||||||
            display_name: group_id_and_name.1,
 | 
					            display_name: group_details.display_name,
 | 
				
			||||||
 | 
					            creation_date: group_details.creation_date,
 | 
				
			||||||
            members: None,
 | 
					            members: None,
 | 
				
			||||||
            _phantom: std::marker::PhantomData,
 | 
					            _phantom: std::marker::PhantomData,
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -307,6 +310,7 @@ impl<Handler: BackendHandler> From<DomainGroup> for Group<Handler> {
 | 
				
			|||||||
        Self {
 | 
					        Self {
 | 
				
			||||||
            group_id: group.id.0,
 | 
					            group_id: group.id.0,
 | 
				
			||||||
            display_name: group.display_name,
 | 
					            display_name: group.display_name,
 | 
				
			||||||
 | 
					            creation_date: group.creation_date,
 | 
				
			||||||
            members: Some(group.users.into_iter().map(UserId::into_string).collect()),
 | 
					            members: Some(group.users.into_iter().map(UserId::into_string).collect()),
 | 
				
			||||||
            _phantom: std::marker::PhantomData,
 | 
					            _phantom: std::marker::PhantomData,
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -320,6 +324,7 @@ mod tests {
 | 
				
			|||||||
        domain::handler::{MockTestBackendHandler, UserRequestFilter},
 | 
					        domain::handler::{MockTestBackendHandler, UserRequestFilter},
 | 
				
			||||||
        infra::auth_service::ValidationResults,
 | 
					        infra::auth_service::ValidationResults,
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					    use chrono::TimeZone;
 | 
				
			||||||
    use juniper::{
 | 
					    use juniper::{
 | 
				
			||||||
        execute, graphql_value, DefaultScalarValue, EmptyMutation, EmptySubscription, GraphQLType,
 | 
					        execute, graphql_value, DefaultScalarValue, EmptyMutation, EmptySubscription, GraphQLType,
 | 
				
			||||||
        RootNode, Variables,
 | 
					        RootNode, Variables,
 | 
				
			||||||
@ -361,7 +366,12 @@ mod tests {
 | 
				
			|||||||
                })
 | 
					                })
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
        let mut groups = HashSet::new();
 | 
					        let mut groups = HashSet::new();
 | 
				
			||||||
        groups.insert(GroupIdAndName(GroupId(3), "Bobbersons".to_string()));
 | 
					        groups.insert(GroupDetails {
 | 
				
			||||||
 | 
					            group_id: GroupId(3),
 | 
				
			||||||
 | 
					            display_name: "Bobbersons".to_string(),
 | 
				
			||||||
 | 
					            creation_date: chrono::Utc.timestamp_nanos(42),
 | 
				
			||||||
 | 
					            uuid: crate::uuid!("a1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d8"),
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
        mock.expect_get_user_groups()
 | 
					        mock.expect_get_user_groups()
 | 
				
			||||||
            .with(eq(UserId::new("bob")))
 | 
					            .with(eq(UserId::new("bob")))
 | 
				
			||||||
            .return_once(|_| Ok(groups));
 | 
					            .return_once(|_| Ok(groups));
 | 
				
			||||||
 | 
				
			|||||||
@ -1,8 +1,8 @@
 | 
				
			|||||||
use crate::{
 | 
					use crate::{
 | 
				
			||||||
    domain::{
 | 
					    domain::{
 | 
				
			||||||
        handler::{
 | 
					        handler::{
 | 
				
			||||||
            BackendHandler, BindRequest, Group, GroupIdAndName, GroupRequestFilter, LoginHandler,
 | 
					            BackendHandler, BindRequest, Group, GroupDetails, GroupRequestFilter, LoginHandler,
 | 
				
			||||||
            User, UserId, UserRequestFilter,
 | 
					            User, UserId, UserRequestFilter, Uuid,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        opaque_handler::OpaqueHandler,
 | 
					        opaque_handler::OpaqueHandler,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
@ -153,7 +153,7 @@ fn get_user_attribute(
 | 
				
			|||||||
    attribute: &str,
 | 
					    attribute: &str,
 | 
				
			||||||
    dn: &str,
 | 
					    dn: &str,
 | 
				
			||||||
    base_dn_str: &str,
 | 
					    base_dn_str: &str,
 | 
				
			||||||
    groups: Option<&[GroupIdAndName]>,
 | 
					    groups: Option<&[GroupDetails]>,
 | 
				
			||||||
    ignored_user_attributes: &[String],
 | 
					    ignored_user_attributes: &[String],
 | 
				
			||||||
) -> Result<Option<Vec<String>>> {
 | 
					) -> Result<Option<Vec<String>>> {
 | 
				
			||||||
    let attribute = attribute.to_ascii_lowercase();
 | 
					    let attribute = attribute.to_ascii_lowercase();
 | 
				
			||||||
@ -166,13 +166,19 @@ fn get_user_attribute(
 | 
				
			|||||||
        ],
 | 
					        ],
 | 
				
			||||||
        "dn" | "distinguishedname" => vec![dn.to_string()],
 | 
					        "dn" | "distinguishedname" => vec![dn.to_string()],
 | 
				
			||||||
        "uid" => vec![user.user_id.to_string()],
 | 
					        "uid" => vec![user.user_id.to_string()],
 | 
				
			||||||
 | 
					        "entryuuid" => vec![user.uuid.to_string()],
 | 
				
			||||||
        "mail" => vec![user.email.clone()],
 | 
					        "mail" => vec![user.email.clone()],
 | 
				
			||||||
        "givenname" => vec![user.first_name.clone()],
 | 
					        "givenname" => vec![user.first_name.clone()],
 | 
				
			||||||
        "sn" => vec![user.last_name.clone()],
 | 
					        "sn" => vec![user.last_name.clone()],
 | 
				
			||||||
        "memberof" => groups
 | 
					        "memberof" => groups
 | 
				
			||||||
            .into_iter()
 | 
					            .into_iter()
 | 
				
			||||||
            .flatten()
 | 
					            .flatten()
 | 
				
			||||||
            .map(|id_and_name| format!("uid={},ou=groups,{}", &id_and_name.1, base_dn_str))
 | 
					            .map(|id_and_name| {
 | 
				
			||||||
 | 
					                format!(
 | 
				
			||||||
 | 
					                    "uid={},ou=groups,{}",
 | 
				
			||||||
 | 
					                    &id_and_name.display_name, base_dn_str
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
            .collect(),
 | 
					            .collect(),
 | 
				
			||||||
        "cn" | "displayname" => vec![user.display_name.clone()],
 | 
					        "cn" | "displayname" => vec![user.display_name.clone()],
 | 
				
			||||||
        "createtimestamp" | "modifytimestamp" => vec![user.creation_date.to_rfc3339()],
 | 
					        "createtimestamp" | "modifytimestamp" => vec![user.creation_date.to_rfc3339()],
 | 
				
			||||||
@ -233,7 +239,7 @@ fn make_ldap_search_user_result_entry(
 | 
				
			|||||||
    user: User,
 | 
					    user: User,
 | 
				
			||||||
    base_dn_str: &str,
 | 
					    base_dn_str: &str,
 | 
				
			||||||
    attributes: &[String],
 | 
					    attributes: &[String],
 | 
				
			||||||
    groups: Option<&[GroupIdAndName]>,
 | 
					    groups: Option<&[GroupDetails]>,
 | 
				
			||||||
    ignored_user_attributes: &[String],
 | 
					    ignored_user_attributes: &[String],
 | 
				
			||||||
) -> Result<LdapSearchResultEntry> {
 | 
					) -> Result<LdapSearchResultEntry> {
 | 
				
			||||||
    let dn = format!("uid={},ou=people,{}", user.user_id.as_str(), base_dn_str);
 | 
					    let dn = format!("uid={},ou=people,{}", user.user_id.as_str(), base_dn_str);
 | 
				
			||||||
@ -278,6 +284,7 @@ fn get_group_attribute(
 | 
				
			|||||||
            group.display_name, base_dn_str
 | 
					            group.display_name, base_dn_str
 | 
				
			||||||
        )],
 | 
					        )],
 | 
				
			||||||
        "cn" | "uid" => vec![group.display_name.clone()],
 | 
					        "cn" | "uid" => vec![group.display_name.clone()],
 | 
				
			||||||
 | 
					        "entryuuid" => vec![group.uuid.to_string()],
 | 
				
			||||||
        "member" | "uniquemember" => group
 | 
					        "member" | "uniquemember" => group
 | 
				
			||||||
            .users
 | 
					            .users
 | 
				
			||||||
            .iter()
 | 
					            .iter()
 | 
				
			||||||
@ -363,24 +370,18 @@ fn is_subtree(subtree: &[(String, String)], base_tree: &[(String, String)]) -> b
 | 
				
			|||||||
    true
 | 
					    true
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn map_field(field: &str) -> Result<String> {
 | 
					fn map_field(field: &str) -> Result<&'static str> {
 | 
				
			||||||
    assert!(field == field.to_ascii_lowercase());
 | 
					    assert!(field == field.to_ascii_lowercase());
 | 
				
			||||||
    Ok(if field == "uid" {
 | 
					    Ok(match field {
 | 
				
			||||||
        "user_id".to_string()
 | 
					        "uid" => "user_id",
 | 
				
			||||||
    } else if field == "mail" {
 | 
					        "mail" => "email",
 | 
				
			||||||
        "email".to_string()
 | 
					        "cn" | "displayname" => "display_name",
 | 
				
			||||||
    } else if field == "cn" || field == "displayname" {
 | 
					        "givenname" => "first_name",
 | 
				
			||||||
        "display_name".to_string()
 | 
					        "sn" => "last_name",
 | 
				
			||||||
    } else if field == "givenname" {
 | 
					        "avatar" => "avatar",
 | 
				
			||||||
        "first_name".to_string()
 | 
					        "creationdate" | "createtimestamp" | "modifytimestamp" => "creation_date",
 | 
				
			||||||
    } else if field == "sn" {
 | 
					        "entryuuid" => "uuid",
 | 
				
			||||||
        "last_name".to_string()
 | 
					        _ => bail!("Unknown field: {}", field),
 | 
				
			||||||
    } else if field == "avatar" {
 | 
					 | 
				
			||||||
        "avatar".to_string()
 | 
					 | 
				
			||||||
    } else if field == "creationdate" || field == "createtimestamp" || field == "modifytimestamp" {
 | 
					 | 
				
			||||||
        "creation_date".to_string()
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
        bail!("Unknown field: {}", field);
 | 
					 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -499,7 +500,7 @@ impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend
 | 
				
			|||||||
                let is_in_group = |name| {
 | 
					                let is_in_group = |name| {
 | 
				
			||||||
                    user_groups
 | 
					                    user_groups
 | 
				
			||||||
                        .as_ref()
 | 
					                        .as_ref()
 | 
				
			||||||
                        .map(|groups| groups.iter().any(|g| g.1 == name))
 | 
					                        .map(|groups| groups.iter().any(|g| g.display_name == name))
 | 
				
			||||||
                        .unwrap_or(false)
 | 
					                        .unwrap_or(false)
 | 
				
			||||||
                };
 | 
					                };
 | 
				
			||||||
                self.user_info = Some((
 | 
					                self.user_info = Some((
 | 
				
			||||||
@ -845,29 +846,36 @@ impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend
 | 
				
			|||||||
            LdapFilter::Equality(field, value) => {
 | 
					            LdapFilter::Equality(field, value) => {
 | 
				
			||||||
                let field = &field.to_ascii_lowercase();
 | 
					                let field = &field.to_ascii_lowercase();
 | 
				
			||||||
                let value = &value.to_ascii_lowercase();
 | 
					                let value = &value.to_ascii_lowercase();
 | 
				
			||||||
                if field == "member" || field == "uniquemember" {
 | 
					                match field.as_str() {
 | 
				
			||||||
                    let user_name = get_user_id_from_distinguished_name(
 | 
					                    "member" | "uniquemember" => {
 | 
				
			||||||
                        value,
 | 
					                        let user_name = get_user_id_from_distinguished_name(
 | 
				
			||||||
                        &self.base_dn,
 | 
					                            value,
 | 
				
			||||||
                        &self.base_dn_str,
 | 
					                            &self.base_dn,
 | 
				
			||||||
                    )?;
 | 
					                            &self.base_dn_str,
 | 
				
			||||||
                    Ok(GroupRequestFilter::Member(user_name))
 | 
					                        )?;
 | 
				
			||||||
                } else if field == "objectclass" {
 | 
					                        Ok(GroupRequestFilter::Member(user_name))
 | 
				
			||||||
                    if value == "groupofuniquenames" || value == "groupofnames" {
 | 
					 | 
				
			||||||
                        Ok(GroupRequestFilter::And(vec![]))
 | 
					 | 
				
			||||||
                    } else {
 | 
					 | 
				
			||||||
                        Ok(GroupRequestFilter::Not(Box::new(GroupRequestFilter::And(
 | 
					 | 
				
			||||||
                            vec![],
 | 
					 | 
				
			||||||
                        ))))
 | 
					 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                } else {
 | 
					                    "objectclass" => {
 | 
				
			||||||
                    let mapped_field = map_field(field);
 | 
					                        if value == "groupofuniquenames" || value == "groupofnames" {
 | 
				
			||||||
                    if mapped_field.is_ok()
 | 
					                            Ok(GroupRequestFilter::And(vec![]))
 | 
				
			||||||
                        && (mapped_field.as_ref().unwrap() == "display_name"
 | 
					                        } else {
 | 
				
			||||||
                            || mapped_field.as_ref().unwrap() == "user_id")
 | 
					                            Ok(GroupRequestFilter::Not(Box::new(GroupRequestFilter::And(
 | 
				
			||||||
                    {
 | 
					                                vec![],
 | 
				
			||||||
                        Ok(GroupRequestFilter::DisplayName(value.clone()))
 | 
					                            ))))
 | 
				
			||||||
                    } else {
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    _ => {
 | 
				
			||||||
 | 
					                        match map_field(field) {
 | 
				
			||||||
 | 
					                            Ok("display_name") | Ok("user_id") => {
 | 
				
			||||||
 | 
					                                return Ok(GroupRequestFilter::DisplayName(value.clone()));
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                            Ok("uuid") => {
 | 
				
			||||||
 | 
					                                return Ok(GroupRequestFilter::Uuid(Uuid::try_from(
 | 
				
			||||||
 | 
					                                    value.as_str(),
 | 
				
			||||||
 | 
					                                )?));
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                            _ => (),
 | 
				
			||||||
 | 
					                        };
 | 
				
			||||||
                        if !self.ignored_group_attributes.contains(field) {
 | 
					                        if !self.ignored_group_attributes.contains(field) {
 | 
				
			||||||
                            warn!(
 | 
					                            warn!(
 | 
				
			||||||
                                r#"Ignoring unknown group attribute "{:?}" in filter.\n\
 | 
					                                r#"Ignoring unknown group attribute "{:?}" in filter.\n\
 | 
				
			||||||
@ -928,32 +936,37 @@ impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend
 | 
				
			|||||||
            ))),
 | 
					            ))),
 | 
				
			||||||
            LdapFilter::Equality(field, value) => {
 | 
					            LdapFilter::Equality(field, value) => {
 | 
				
			||||||
                let field = &field.to_ascii_lowercase();
 | 
					                let field = &field.to_ascii_lowercase();
 | 
				
			||||||
                if field == "memberof" {
 | 
					                match field.as_str() {
 | 
				
			||||||
                    let group_name = get_group_id_from_distinguished_name(
 | 
					                    "memberof" => {
 | 
				
			||||||
                        &value.to_ascii_lowercase(),
 | 
					                        let group_name = get_group_id_from_distinguished_name(
 | 
				
			||||||
                        &self.base_dn,
 | 
					                            &value.to_ascii_lowercase(),
 | 
				
			||||||
                        &self.base_dn_str,
 | 
					                            &self.base_dn,
 | 
				
			||||||
                    )?;
 | 
					                            &self.base_dn_str,
 | 
				
			||||||
                    Ok(UserRequestFilter::MemberOf(group_name))
 | 
					                        )?;
 | 
				
			||||||
                } else if field == "objectclass" {
 | 
					                        Ok(UserRequestFilter::MemberOf(group_name))
 | 
				
			||||||
                    if value == "person"
 | 
					 | 
				
			||||||
                        || value == "inetOrgPerson"
 | 
					 | 
				
			||||||
                        || value == "posixAccount"
 | 
					 | 
				
			||||||
                        || value == "mailAccount"
 | 
					 | 
				
			||||||
                    {
 | 
					 | 
				
			||||||
                        Ok(UserRequestFilter::And(vec![]))
 | 
					 | 
				
			||||||
                    } else {
 | 
					 | 
				
			||||||
                        Ok(UserRequestFilter::Not(Box::new(UserRequestFilter::And(
 | 
					 | 
				
			||||||
                            vec![],
 | 
					 | 
				
			||||||
                        ))))
 | 
					 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                } else {
 | 
					                    "objectclass" => {
 | 
				
			||||||
                    match map_field(field) {
 | 
					                        if value == "person"
 | 
				
			||||||
 | 
					                            || value == "inetOrgPerson"
 | 
				
			||||||
 | 
					                            || value == "posixAccount"
 | 
				
			||||||
 | 
					                            || value == "mailAccount"
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            Ok(UserRequestFilter::And(vec![]))
 | 
				
			||||||
 | 
					                        } else {
 | 
				
			||||||
 | 
					                            Ok(UserRequestFilter::Not(Box::new(UserRequestFilter::And(
 | 
				
			||||||
 | 
					                                vec![],
 | 
				
			||||||
 | 
					                            ))))
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    _ => match map_field(field) {
 | 
				
			||||||
                        Ok(field) => {
 | 
					                        Ok(field) => {
 | 
				
			||||||
                            if field == "user_id" {
 | 
					                            if field == "user_id" {
 | 
				
			||||||
                                Ok(UserRequestFilter::UserId(UserId::new(value)))
 | 
					                                Ok(UserRequestFilter::UserId(UserId::new(value)))
 | 
				
			||||||
                            } else {
 | 
					                            } else {
 | 
				
			||||||
                                Ok(UserRequestFilter::Equality(field, value.clone()))
 | 
					                                Ok(UserRequestFilter::Equality(
 | 
				
			||||||
 | 
					                                    field.to_string(),
 | 
				
			||||||
 | 
					                                    value.clone(),
 | 
				
			||||||
 | 
					                                ))
 | 
				
			||||||
                            }
 | 
					                            }
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                        Err(_) => {
 | 
					                        Err(_) => {
 | 
				
			||||||
@ -968,7 +981,7 @@ impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend
 | 
				
			|||||||
                                vec![],
 | 
					                                vec![],
 | 
				
			||||||
                            ))))
 | 
					                            ))))
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    },
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            LdapFilter::Present(field) => {
 | 
					            LdapFilter::Present(field) => {
 | 
				
			||||||
@ -990,8 +1003,12 @@ impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend
 | 
				
			|||||||
#[cfg(test)]
 | 
					#[cfg(test)]
 | 
				
			||||||
mod tests {
 | 
					mod tests {
 | 
				
			||||||
    use super::*;
 | 
					    use super::*;
 | 
				
			||||||
    use crate::domain::{error::Result, handler::*, opaque_handler::*};
 | 
					    use crate::{
 | 
				
			||||||
 | 
					        domain::{error::Result, handler::*, opaque_handler::*},
 | 
				
			||||||
 | 
					        uuid,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
    use async_trait::async_trait;
 | 
					    use async_trait::async_trait;
 | 
				
			||||||
 | 
					    use chrono::TimeZone;
 | 
				
			||||||
    use ldap3_server::proto::{LdapDerefAliases, LdapSearchScope};
 | 
					    use ldap3_server::proto::{LdapDerefAliases, LdapSearchScope};
 | 
				
			||||||
    use mockall::predicate::eq;
 | 
					    use mockall::predicate::eq;
 | 
				
			||||||
    use std::collections::HashSet;
 | 
					    use std::collections::HashSet;
 | 
				
			||||||
@ -1011,8 +1028,8 @@ mod tests {
 | 
				
			|||||||
            async fn list_users(&self, filters: Option<UserRequestFilter>, get_groups: bool) -> Result<Vec<UserAndGroups>>;
 | 
					            async fn list_users(&self, filters: Option<UserRequestFilter>, get_groups: bool) -> Result<Vec<UserAndGroups>>;
 | 
				
			||||||
            async fn list_groups(&self, filters: Option<GroupRequestFilter>) -> Result<Vec<Group>>;
 | 
					            async fn list_groups(&self, filters: Option<GroupRequestFilter>) -> Result<Vec<Group>>;
 | 
				
			||||||
            async fn get_user_details(&self, user_id: &UserId) -> Result<User>;
 | 
					            async fn get_user_details(&self, user_id: &UserId) -> Result<User>;
 | 
				
			||||||
            async fn get_group_details(&self, group_id: GroupId) -> Result<GroupIdAndName>;
 | 
					            async fn get_group_details(&self, group_id: GroupId) -> Result<GroupDetails>;
 | 
				
			||||||
            async fn get_user_groups(&self, user: &UserId) -> Result<HashSet<GroupIdAndName>>;
 | 
					            async fn get_user_groups(&self, user: &UserId) -> Result<HashSet<GroupDetails>>;
 | 
				
			||||||
            async fn create_user(&self, request: CreateUserRequest) -> Result<()>;
 | 
					            async fn create_user(&self, request: CreateUserRequest) -> Result<()>;
 | 
				
			||||||
            async fn update_user(&self, request: UpdateUserRequest) -> Result<()>;
 | 
					            async fn update_user(&self, request: UpdateUserRequest) -> Result<()>;
 | 
				
			||||||
            async fn update_group(&self, request: UpdateGroupRequest) -> Result<()>;
 | 
					            async fn update_group(&self, request: UpdateGroupRequest) -> Result<()>;
 | 
				
			||||||
@ -1079,7 +1096,12 @@ mod tests {
 | 
				
			|||||||
            .with(eq(UserId::new("test")))
 | 
					            .with(eq(UserId::new("test")))
 | 
				
			||||||
            .return_once(|_| {
 | 
					            .return_once(|_| {
 | 
				
			||||||
                let mut set = HashSet::new();
 | 
					                let mut set = HashSet::new();
 | 
				
			||||||
                set.insert(GroupIdAndName(GroupId(42), group));
 | 
					                set.insert(GroupDetails {
 | 
				
			||||||
 | 
					                    group_id: GroupId(42),
 | 
				
			||||||
 | 
					                    display_name: group,
 | 
				
			||||||
 | 
					                    creation_date: chrono::Utc.timestamp(42, 42),
 | 
				
			||||||
 | 
					                    uuid: uuid!("a1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d8"),
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
                Ok(set)
 | 
					                Ok(set)
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
        let mut ldap_handler =
 | 
					        let mut ldap_handler =
 | 
				
			||||||
@ -1155,7 +1177,12 @@ mod tests {
 | 
				
			|||||||
            .with(eq(UserId::new("test")))
 | 
					            .with(eq(UserId::new("test")))
 | 
				
			||||||
            .return_once(|_| {
 | 
					            .return_once(|_| {
 | 
				
			||||||
                let mut set = HashSet::new();
 | 
					                let mut set = HashSet::new();
 | 
				
			||||||
                set.insert(GroupIdAndName(GroupId(42), "lldap_admin".to_string()));
 | 
					                set.insert(GroupDetails {
 | 
				
			||||||
 | 
					                    group_id: GroupId(42),
 | 
				
			||||||
 | 
					                    display_name: "lldap_admin".to_string(),
 | 
				
			||||||
 | 
					                    creation_date: chrono::Utc.timestamp(42, 42),
 | 
				
			||||||
 | 
					                    uuid: uuid!("a1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d8"),
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
                Ok(set)
 | 
					                Ok(set)
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
        let mut ldap_handler =
 | 
					        let mut ldap_handler =
 | 
				
			||||||
@ -1237,7 +1264,12 @@ mod tests {
 | 
				
			|||||||
                        user_id: UserId::new("bob"),
 | 
					                        user_id: UserId::new("bob"),
 | 
				
			||||||
                        ..Default::default()
 | 
					                        ..Default::default()
 | 
				
			||||||
                    },
 | 
					                    },
 | 
				
			||||||
                    groups: Some(vec![GroupIdAndName(GroupId(42), "rockstars".to_string())]),
 | 
					                    groups: Some(vec![GroupDetails {
 | 
				
			||||||
 | 
					                        group_id: GroupId(42),
 | 
				
			||||||
 | 
					                        display_name: "rockstars".to_string(),
 | 
				
			||||||
 | 
					                        creation_date: chrono::Utc.timestamp(42, 42),
 | 
				
			||||||
 | 
					                        uuid: uuid!("a1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d8"),
 | 
				
			||||||
 | 
					                    }]),
 | 
				
			||||||
                }])
 | 
					                }])
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
        let mut ldap_handler = setup_bound_readonly_handler(mock).await;
 | 
					        let mut ldap_handler = setup_bound_readonly_handler(mock).await;
 | 
				
			||||||
@ -1386,6 +1418,7 @@ mod tests {
 | 
				
			|||||||
                        display_name: "Bôb Böbberson".to_string(),
 | 
					                        display_name: "Bôb Böbberson".to_string(),
 | 
				
			||||||
                        first_name: "Bôb".to_string(),
 | 
					                        first_name: "Bôb".to_string(),
 | 
				
			||||||
                        last_name: "Böbberson".to_string(),
 | 
					                        last_name: "Böbberson".to_string(),
 | 
				
			||||||
 | 
					                        uuid: uuid!("698e1d5f-7a40-3151-8745-b9b8a37839da"),
 | 
				
			||||||
                        ..Default::default()
 | 
					                        ..Default::default()
 | 
				
			||||||
                    },
 | 
					                    },
 | 
				
			||||||
                    groups: None,
 | 
					                    groups: None,
 | 
				
			||||||
@ -1397,6 +1430,7 @@ mod tests {
 | 
				
			|||||||
                        display_name: "Jimminy Cricket".to_string(),
 | 
					                        display_name: "Jimminy Cricket".to_string(),
 | 
				
			||||||
                        first_name: "Jim".to_string(),
 | 
					                        first_name: "Jim".to_string(),
 | 
				
			||||||
                        last_name: "Cricket".to_string(),
 | 
					                        last_name: "Cricket".to_string(),
 | 
				
			||||||
 | 
					                        uuid: uuid!("04ac75e0-2900-3e21-926c-2f732c26b3fc"),
 | 
				
			||||||
                        creation_date: Utc.ymd(2014, 7, 8).and_hms(9, 10, 11),
 | 
					                        creation_date: Utc.ymd(2014, 7, 8).and_hms(9, 10, 11),
 | 
				
			||||||
                    },
 | 
					                    },
 | 
				
			||||||
                    groups: None,
 | 
					                    groups: None,
 | 
				
			||||||
@ -1415,6 +1449,7 @@ mod tests {
 | 
				
			|||||||
                "sn",
 | 
					                "sn",
 | 
				
			||||||
                "cn",
 | 
					                "cn",
 | 
				
			||||||
                "createTimestamp",
 | 
					                "createTimestamp",
 | 
				
			||||||
 | 
					                "entryUuid",
 | 
				
			||||||
            ],
 | 
					            ],
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
        assert_eq!(
 | 
					        assert_eq!(
 | 
				
			||||||
@ -1459,7 +1494,11 @@ mod tests {
 | 
				
			|||||||
                        LdapPartialAttribute {
 | 
					                        LdapPartialAttribute {
 | 
				
			||||||
                            atype: "createTimestamp".to_string(),
 | 
					                            atype: "createTimestamp".to_string(),
 | 
				
			||||||
                            vals: vec!["1970-01-01T00:00:00+00:00".to_string()]
 | 
					                            vals: vec!["1970-01-01T00:00:00+00:00".to_string()]
 | 
				
			||||||
                        }
 | 
					                        },
 | 
				
			||||||
 | 
					                        LdapPartialAttribute {
 | 
				
			||||||
 | 
					                            atype: "entryUuid".to_string(),
 | 
				
			||||||
 | 
					                            vals: vec!["698e1d5f-7a40-3151-8745-b9b8a37839da".to_string()]
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
                    ],
 | 
					                    ],
 | 
				
			||||||
                }),
 | 
					                }),
 | 
				
			||||||
                LdapOp::SearchResultEntry(LdapSearchResultEntry {
 | 
					                LdapOp::SearchResultEntry(LdapSearchResultEntry {
 | 
				
			||||||
@ -1501,7 +1540,11 @@ mod tests {
 | 
				
			|||||||
                        LdapPartialAttribute {
 | 
					                        LdapPartialAttribute {
 | 
				
			||||||
                            atype: "createTimestamp".to_string(),
 | 
					                            atype: "createTimestamp".to_string(),
 | 
				
			||||||
                            vals: vec!["2014-07-08T09:10:11+00:00".to_string()]
 | 
					                            vals: vec!["2014-07-08T09:10:11+00:00".to_string()]
 | 
				
			||||||
                        }
 | 
					                        },
 | 
				
			||||||
 | 
					                        LdapPartialAttribute {
 | 
				
			||||||
 | 
					                            atype: "entryUuid".to_string(),
 | 
				
			||||||
 | 
					                            vals: vec!["04ac75e0-2900-3e21-926c-2f732c26b3fc".to_string()]
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
                    ],
 | 
					                    ],
 | 
				
			||||||
                }),
 | 
					                }),
 | 
				
			||||||
                make_search_success(),
 | 
					                make_search_success(),
 | 
				
			||||||
@ -1520,12 +1563,16 @@ mod tests {
 | 
				
			|||||||
                    Group {
 | 
					                    Group {
 | 
				
			||||||
                        id: GroupId(1),
 | 
					                        id: GroupId(1),
 | 
				
			||||||
                        display_name: "group_1".to_string(),
 | 
					                        display_name: "group_1".to_string(),
 | 
				
			||||||
 | 
					                        creation_date: chrono::Utc.timestamp(42, 42),
 | 
				
			||||||
                        users: vec![UserId::new("bob"), UserId::new("john")],
 | 
					                        users: vec![UserId::new("bob"), UserId::new("john")],
 | 
				
			||||||
 | 
					                        uuid: uuid!("04ac75e0-2900-3e21-926c-2f732c26b3fc"),
 | 
				
			||||||
                    },
 | 
					                    },
 | 
				
			||||||
                    Group {
 | 
					                    Group {
 | 
				
			||||||
                        id: GroupId(3),
 | 
					                        id: GroupId(3),
 | 
				
			||||||
                        display_name: "BestGroup".to_string(),
 | 
					                        display_name: "BestGroup".to_string(),
 | 
				
			||||||
 | 
					                        creation_date: chrono::Utc.timestamp(42, 42),
 | 
				
			||||||
                        users: vec![UserId::new("john")],
 | 
					                        users: vec![UserId::new("john")],
 | 
				
			||||||
 | 
					                        uuid: uuid!("04ac75e0-2900-3e21-926c-2f732c26b3fc"),
 | 
				
			||||||
                    },
 | 
					                    },
 | 
				
			||||||
                ])
 | 
					                ])
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
@ -1533,7 +1580,7 @@ mod tests {
 | 
				
			|||||||
        let request = make_search_request(
 | 
					        let request = make_search_request(
 | 
				
			||||||
            "ou=groups,dc=example,dc=cOm",
 | 
					            "ou=groups,dc=example,dc=cOm",
 | 
				
			||||||
            LdapFilter::And(vec![]),
 | 
					            LdapFilter::And(vec![]),
 | 
				
			||||||
            vec!["objectClass", "dn", "cn", "uniqueMember"],
 | 
					            vec!["objectClass", "dn", "cn", "uniqueMember", "entryUuid"],
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
        assert_eq!(
 | 
					        assert_eq!(
 | 
				
			||||||
            ldap_handler.do_search_or_dse(&request).await,
 | 
					            ldap_handler.do_search_or_dse(&request).await,
 | 
				
			||||||
@ -1560,6 +1607,10 @@ mod tests {
 | 
				
			|||||||
                                "uid=john,ou=people,dc=example,dc=com".to_string(),
 | 
					                                "uid=john,ou=people,dc=example,dc=com".to_string(),
 | 
				
			||||||
                            ]
 | 
					                            ]
 | 
				
			||||||
                        },
 | 
					                        },
 | 
				
			||||||
 | 
					                        LdapPartialAttribute {
 | 
				
			||||||
 | 
					                            atype: "entryUuid".to_string(),
 | 
				
			||||||
 | 
					                            vals: vec!["04ac75e0-2900-3e21-926c-2f732c26b3fc".to_string()],
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
                    ],
 | 
					                    ],
 | 
				
			||||||
                }),
 | 
					                }),
 | 
				
			||||||
                LdapOp::SearchResultEntry(LdapSearchResultEntry {
 | 
					                LdapOp::SearchResultEntry(LdapSearchResultEntry {
 | 
				
			||||||
@ -1581,6 +1632,10 @@ mod tests {
 | 
				
			|||||||
                            atype: "uniqueMember".to_string(),
 | 
					                            atype: "uniqueMember".to_string(),
 | 
				
			||||||
                            vals: vec!["uid=john,ou=people,dc=example,dc=com".to_string()]
 | 
					                            vals: vec!["uid=john,ou=people,dc=example,dc=com".to_string()]
 | 
				
			||||||
                        },
 | 
					                        },
 | 
				
			||||||
 | 
					                        LdapPartialAttribute {
 | 
				
			||||||
 | 
					                            atype: "entryUuid".to_string(),
 | 
				
			||||||
 | 
					                            vals: vec!["04ac75e0-2900-3e21-926c-2f732c26b3fc".to_string()],
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
                    ],
 | 
					                    ],
 | 
				
			||||||
                }),
 | 
					                }),
 | 
				
			||||||
                make_search_success(),
 | 
					                make_search_success(),
 | 
				
			||||||
@ -1609,7 +1664,9 @@ mod tests {
 | 
				
			|||||||
                Ok(vec![Group {
 | 
					                Ok(vec![Group {
 | 
				
			||||||
                    display_name: "group_1".to_string(),
 | 
					                    display_name: "group_1".to_string(),
 | 
				
			||||||
                    id: GroupId(1),
 | 
					                    id: GroupId(1),
 | 
				
			||||||
 | 
					                    creation_date: chrono::Utc.timestamp(42, 42),
 | 
				
			||||||
                    users: vec![],
 | 
					                    users: vec![],
 | 
				
			||||||
 | 
					                    uuid: uuid!("04ac75e0-2900-3e21-926c-2f732c26b3fc"),
 | 
				
			||||||
                }])
 | 
					                }])
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
        let mut ldap_handler = setup_bound_admin_handler(mock).await;
 | 
					        let mut ldap_handler = setup_bound_admin_handler(mock).await;
 | 
				
			||||||
@ -1658,7 +1715,9 @@ mod tests {
 | 
				
			|||||||
                Ok(vec![Group {
 | 
					                Ok(vec![Group {
 | 
				
			||||||
                    display_name: "group_1".to_string(),
 | 
					                    display_name: "group_1".to_string(),
 | 
				
			||||||
                    id: GroupId(1),
 | 
					                    id: GroupId(1),
 | 
				
			||||||
 | 
					                    creation_date: chrono::Utc.timestamp(42, 42),
 | 
				
			||||||
                    users: vec![],
 | 
					                    users: vec![],
 | 
				
			||||||
 | 
					                    uuid: uuid!("04ac75e0-2900-3e21-926c-2f732c26b3fc"),
 | 
				
			||||||
                }])
 | 
					                }])
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
        let mut ldap_handler = setup_bound_admin_handler(mock).await;
 | 
					        let mut ldap_handler = setup_bound_admin_handler(mock).await;
 | 
				
			||||||
@ -1932,7 +1991,9 @@ mod tests {
 | 
				
			|||||||
                Ok(vec![Group {
 | 
					                Ok(vec![Group {
 | 
				
			||||||
                    id: GroupId(1),
 | 
					                    id: GroupId(1),
 | 
				
			||||||
                    display_name: "group_1".to_string(),
 | 
					                    display_name: "group_1".to_string(),
 | 
				
			||||||
 | 
					                    creation_date: chrono::Utc.timestamp(42, 42),
 | 
				
			||||||
                    users: vec![UserId::new("bob"), UserId::new("john")],
 | 
					                    users: vec![UserId::new("bob"), UserId::new("john")],
 | 
				
			||||||
 | 
					                    uuid: uuid!("04ac75e0-2900-3e21-926c-2f732c26b3fc"),
 | 
				
			||||||
                }])
 | 
					                }])
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
        let mut ldap_handler = setup_bound_admin_handler(mock).await;
 | 
					        let mut ldap_handler = setup_bound_admin_handler(mock).await;
 | 
				
			||||||
@ -1989,7 +2050,6 @@ mod tests {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    #[tokio::test]
 | 
					    #[tokio::test]
 | 
				
			||||||
    async fn test_search_wildcards() {
 | 
					    async fn test_search_wildcards() {
 | 
				
			||||||
        use chrono::TimeZone;
 | 
					 | 
				
			||||||
        let mut mock = MockTestBackendHandler::new();
 | 
					        let mut mock = MockTestBackendHandler::new();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        mock.expect_list_users().returning(|_, _| {
 | 
					        mock.expect_list_users().returning(|_, _| {
 | 
				
			||||||
@ -2011,7 +2071,9 @@ mod tests {
 | 
				
			|||||||
                Ok(vec![Group {
 | 
					                Ok(vec![Group {
 | 
				
			||||||
                    id: GroupId(1),
 | 
					                    id: GroupId(1),
 | 
				
			||||||
                    display_name: "group_1".to_string(),
 | 
					                    display_name: "group_1".to_string(),
 | 
				
			||||||
 | 
					                    creation_date: chrono::Utc.timestamp(42, 42),
 | 
				
			||||||
                    users: vec![UserId::new("bob"), UserId::new("john")],
 | 
					                    users: vec![UserId::new("bob"), UserId::new("john")],
 | 
				
			||||||
 | 
					                    uuid: uuid!("04ac75e0-2900-3e21-926c-2f732c26b3fc"),
 | 
				
			||||||
                }])
 | 
					                }])
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
        let mut ldap_handler = setup_bound_admin_handler(mock).await;
 | 
					        let mut ldap_handler = setup_bound_admin_handler(mock).await;
 | 
				
			||||||
 | 
				
			|||||||
@ -38,8 +38,8 @@ mockall::mock! {
 | 
				
			|||||||
    async fn list_users(&self, filters: Option<UserRequestFilter>, get_groups: bool) -> Result<Vec<UserAndGroups>>;
 | 
					    async fn list_users(&self, filters: Option<UserRequestFilter>, get_groups: bool) -> Result<Vec<UserAndGroups>>;
 | 
				
			||||||
        async fn list_groups(&self, filters: Option<GroupRequestFilter>) -> Result<Vec<Group>>;
 | 
					        async fn list_groups(&self, filters: Option<GroupRequestFilter>) -> Result<Vec<Group>>;
 | 
				
			||||||
        async fn get_user_details(&self, user_id: &UserId) -> Result<User>;
 | 
					        async fn get_user_details(&self, user_id: &UserId) -> Result<User>;
 | 
				
			||||||
        async fn get_group_details(&self, group_id: GroupId) -> Result<GroupIdAndName>;
 | 
					        async fn get_group_details(&self, group_id: GroupId) -> Result<GroupDetails>;
 | 
				
			||||||
        async fn get_user_groups(&self, user: &UserId) -> Result<HashSet<GroupIdAndName>>;
 | 
					        async fn get_user_groups(&self, user: &UserId) -> Result<HashSet<GroupDetails>>;
 | 
				
			||||||
        async fn create_user(&self, request: CreateUserRequest) -> Result<()>;
 | 
					        async fn create_user(&self, request: CreateUserRequest) -> Result<()>;
 | 
				
			||||||
        async fn update_user(&self, request: UpdateUserRequest) -> Result<()>;
 | 
					        async fn update_user(&self, request: UpdateUserRequest) -> Result<()>;
 | 
				
			||||||
        async fn update_group(&self, request: UpdateGroupRequest) -> Result<()>;
 | 
					        async fn update_group(&self, request: UpdateGroupRequest) -> Result<()>;
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user