From be594627410263713d2784df15e969035a0f3ebc Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Sun, 28 May 2023 23:47:22 -0400 Subject: [PATCH] implement ldap support --- Cargo.lock | 339 +++++++++++++++++++++++++-- Cargo.toml | 1 + shell.nix | 2 + src/api/auth.rs | 4 +- src/api/blobs.rs | 6 +- src/api/manifests.rs | 10 +- src/api/mod.rs | 2 +- src/api/uploads.rs | 12 +- src/app_state.rs | 2 +- src/auth/ldap_driver.rs | 145 ++++++++++++ src/{auth_storage.rs => auth/mod.rs} | 63 +++-- src/config.rs | 28 ++- src/database/mod.rs | 133 +++++++---- src/database/schemas/schema.sql | 19 +- src/dto/user.rs | 22 +- src/main.rs | 34 ++- 16 files changed, 697 insertions(+), 125 deletions(-) create mode 100644 src/auth/ldap_driver.rs rename src/{auth_storage.rs => auth/mod.rs} (65%) diff --git a/Cargo.lock b/Cargo.lock index 0e42bdb..8e639cc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -100,7 +100,7 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ - "hermit-abi", + "hermit-abi 0.1.19", "libc", "winapi", ] @@ -357,6 +357,16 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.3" @@ -503,12 +513,42 @@ version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" +[[package]] +name = "errno" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +dependencies = [ + "errno-dragonfly", + "libc", + "winapi", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "event-listener" version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + [[package]] name = "figment" version = "0.10.8" @@ -551,6 +591,21 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.1.0" @@ -723,6 +778,12 @@ dependencies = [ "libc", ] +[[package]] +name = "hermit-abi" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + [[package]] name = "hex" version = "0.4.3" @@ -869,6 +930,17 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi 0.3.1", + "libc", + "windows-sys 0.48.0", +] + [[package]] name = "itertools" version = "0.10.5" @@ -930,10 +1002,44 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] -name = "libc" -version = "0.2.137" +name = "lber" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" +checksum = "b5d85f5e00e12cb50c70c3b1c1f0daff6546eb4c608b44d0a990e38a539e0446" +dependencies = [ + "bytes", + "nom", +] + +[[package]] +name = "ldap3" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5cfbd3c59ca16d6671b002b8b3dd013cd825d9c77a1664a3135194d3270511e" +dependencies = [ + "async-trait", + "bytes", + "futures", + "futures-util", + "lazy_static", + "lber", + "log", + "native-tls", + "nom", + "percent-encoding", + "thiserror", + "tokio", + "tokio-native-tls", + "tokio-stream", + "tokio-util", + "url", +] + +[[package]] +name = "libc" +version = "0.2.144" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" [[package]] name = "libsqlite3-sys" @@ -955,6 +1061,12 @@ dependencies = [ "cc", ] +[[package]] +name = "linux-raw-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" + [[package]] name = "lock_api" version = "0.4.9" @@ -1007,7 +1119,25 @@ dependencies = [ "libc", "log", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys", + "windows-sys 0.42.0", +] + +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", ] [[package]] @@ -1055,7 +1185,7 @@ version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5" dependencies = [ - "hermit-abi", + "hermit-abi 0.1.19", "libc", ] @@ -1071,6 +1201,50 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "openssl" +version = "0.10.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01b8574602df80f7b85fdfc5392fa884a4e3b3f4f35402c070ab34c3d3f78d56" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote 1.0.26", + "syn 2.0.15", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e17f59264b2809d77ae94f0e1ebabc434773f370d6ca667bd223ea10e06cc7e" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "orca-registry" version = "0.1.0" @@ -1093,6 +1267,7 @@ dependencies = [ "hmac", "jws", "jwt", + "ldap3", "pin-project-lite", "qstring", "rand", @@ -1371,6 +1546,20 @@ dependencies = [ "winapi", ] +[[package]] +name = "rustix" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fdebc4b395b7fbb9ab11e462e20ed9051e7b16e42d24042c776eca0ac81b03" +dependencies = [ + "bitflags 1.3.2", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys 0.42.0", +] + [[package]] name = "rustls" version = "0.20.8" @@ -1404,6 +1593,15 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" +[[package]] +name = "schannel" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" +dependencies = [ + "windows-sys 0.42.0", +] + [[package]] name = "scopeguard" version = "1.1.0" @@ -1426,6 +1624,29 @@ dependencies = [ "untrusted", ] +[[package]] +name = "security-framework" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc758eb7bffce5b308734e9b0c1468893cae9ff70ebf13e7090be8dcbcc83a8" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f51d0c0d83bec45f16480d0ce0058397a69e48fcdc52d1dc8855fb68acbd31a7" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "serde" version = "1.0.147" @@ -1739,6 +1960,19 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "tempfile" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af18f7ae1acd354b992402e9ec5864359d693cd8a79dcbef59f76891701c1e95" +dependencies = [ + "cfg-if", + "fastrand", + "redox_syscall", + "rustix", + "windows-sys 0.42.0", +] + [[package]] name = "termcolor" version = "1.1.3" @@ -1819,7 +2053,7 @@ dependencies = [ "pin-project-lite", "socket2", "tokio-macros", - "windows-sys", + "windows-sys 0.42.0", ] [[package]] @@ -1833,6 +2067,16 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.23.4" @@ -1866,6 +2110,7 @@ dependencies = [ "futures-sink", "pin-project-lite", "tokio", + "tracing", ] [[package]] @@ -2245,13 +2490,37 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.42.0", + "windows_aarch64_msvc 0.42.0", + "windows_i686_gnu 0.42.0", + "windows_i686_msvc 0.42.0", + "windows_x86_64_gnu 0.42.0", + "windows_x86_64_gnullvm 0.42.0", + "windows_x86_64_msvc 0.42.0", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +dependencies = [ + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", ] [[package]] @@ -2260,42 +2529,84 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + [[package]] name = "windows_aarch64_msvc" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + [[package]] name = "windows_i686_gnu" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + [[package]] name = "windows_i686_msvc" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + [[package]] name = "windows_x86_64_gnu" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + [[package]] name = "windows_x86_64_msvc" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" + [[package]] name = "yansi" version = "0.5.1" diff --git a/Cargo.toml b/Cargo.toml index 0d524d3..ee6a0ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,3 +51,4 @@ sha2 = "0.10.6" rand = "0.8.5" bcrypt = "0.14.0" bitflags = "2.2.1" +ldap3 = "0.11.1" diff --git a/shell.nix b/shell.nix index 5b53fc4..b79db4a 100644 --- a/shell.nix +++ b/shell.nix @@ -8,5 +8,7 @@ pkgs.mkShell { postgresql sqlite diesel-cli + openssl + pkg-config ]; } \ No newline at end of file diff --git a/src/api/auth.rs b/src/api/auth.rs index 9ac7847..8c1fe88 100644 --- a/src/api/auth.rs +++ b/src/api/auth.rs @@ -15,7 +15,7 @@ use rand::Rng; use crate::{dto::{scope::Scope, user::{UserAuth, TokenInfo}}, app_state::AppState}; use crate::database::Database; -use crate::auth_storage::{unauthenticated_response, AuthDriver}; +use crate::auth::{unauthenticated_response, AuthDriver}; #[derive(Deserialize, Debug)] pub struct TokenAuthRequest { @@ -167,7 +167,7 @@ pub async fn auth_basic_get(basic_auth: Option, state: State, state: State>, Extension(auth): Extension) -> Response { // Check if the user has permission to pull, or that the repository is public - let auth_driver = state.auth_checker.lock().await; + let mut auth_driver = state.auth_checker.lock().await; if !auth_driver.user_has_permission(auth.user.username, name.clone(), Permission::PULL, Some(RepositoryVisibility::Public)).await.unwrap() { return unauthenticated_response(&state.config); } @@ -40,7 +40,7 @@ pub async fn digest_exists_head(Path((name, layer_digest)): Path<(String, String pub async fn pull_digest_get(Path((name, layer_digest)): Path<(String, String)>, state: State>, Extension(auth): Extension) -> Response { // Check if the user has permission to pull, or that the repository is public - let auth_driver = state.auth_checker.lock().await; + let mut auth_driver = state.auth_checker.lock().await; if !auth_driver.user_has_permission(auth.user.username, name.clone(), Permission::PULL, Some(RepositoryVisibility::Public)).await.unwrap() { return unauthenticated_response(&state.config); } diff --git a/src/api/manifests.rs b/src/api/manifests.rs index ab716c8..edb3aa6 100644 --- a/src/api/manifests.rs +++ b/src/api/manifests.rs @@ -7,7 +7,7 @@ use axum::http::{StatusCode, HeaderMap, HeaderName, header}; use tracing::log::warn; use tracing::{debug, info}; -use crate::auth_storage::{unauthenticated_response, AuthDriver}; +use crate::auth::{unauthenticated_response, AuthDriver}; use crate::app_state::AppState; use crate::database::Database; use crate::dto::RepositoryVisibility; @@ -16,7 +16,7 @@ use crate::dto::manifest::Manifest; use crate::dto::user::{UserAuth, Permission}; pub async fn upload_manifest_put(Path((name, reference)): Path<(String, String)>, state: State>, Extension(auth): Extension, body: String) -> Response { - let auth_driver = state.auth_checker.lock().await; + let mut auth_driver = state.auth_checker.lock().await; if !auth_driver.user_has_permission(auth.user.username, name.clone(), Permission::PUSH, None).await.unwrap() { return unauthenticated_response(&state.config); } @@ -65,7 +65,7 @@ pub async fn upload_manifest_put(Path((name, reference)): Path<(String, String)> pub async fn pull_manifest_get(Path((name, reference)): Path<(String, String)>, state: State>, Extension(auth): Extension) -> Response { // Check if the user has permission to pull, or that the repository is public - let auth_driver = state.auth_checker.lock().await; + let mut auth_driver = state.auth_checker.lock().await; if !auth_driver.user_has_permission(auth.user.username, name.clone(), Permission::PULL, Some(RepositoryVisibility::Public)).await.unwrap() { return unauthenticated_response(&state.config); } @@ -108,7 +108,7 @@ pub async fn pull_manifest_get(Path((name, reference)): Path<(String, String)>, pub async fn manifest_exists_head(Path((name, reference)): Path<(String, String)>, state: State>, Extension(auth): Extension) -> Response { // Check if the user has permission to pull, or that the repository is public - let auth_driver = state.auth_checker.lock().await; + let mut auth_driver = state.auth_checker.lock().await; if !auth_driver.user_has_permission(auth.user.username, name.clone(), Permission::PULL, Some(RepositoryVisibility::Public)).await.unwrap() { return unauthenticated_response(&state.config); } @@ -148,7 +148,7 @@ pub async fn manifest_exists_head(Path((name, reference)): Path<(String, String) } pub async fn delete_manifest(Path((name, reference)): Path<(String, String)>, headers: HeaderMap, state: State>, Extension(auth): Extension) -> Response { - let auth_driver = state.auth_checker.lock().await; + let mut auth_driver = state.auth_checker.lock().await; if !auth_driver.user_has_permission(auth.user.username, name.clone(), Permission::PUSH, None).await.unwrap() { return unauthenticated_response(&state.config); } diff --git a/src/api/mod.rs b/src/api/mod.rs index 85c80f7..ddf2574 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -14,7 +14,7 @@ pub mod tags; pub mod catalog; pub mod auth; -use crate::auth_storage::AuthToken; +use crate::auth::AuthToken; use crate::dto::user::UserAuth; /// https://docs.docker.com/registry/spec/api/#api-version-check diff --git a/src/api/uploads.rs b/src/api/uploads.rs index 30e1a04..82aea14 100644 --- a/src/api/uploads.rs +++ b/src/api/uploads.rs @@ -12,14 +12,14 @@ use futures::StreamExt; use tracing::{debug, warn}; use crate::app_state::AppState; -use crate::auth_storage::{unauthenticated_response, AuthDriver}; +use crate::auth::{unauthenticated_response, AuthDriver}; use crate::byte_stream::ByteStream; use crate::database::Database; use crate::dto::user::{UserAuth, Permission, RegistryUser, RegistryUserType}; /// Starting an upload pub async fn start_upload_post(Path((name, )): Path<(String, )>, Extension(auth): Extension, state: State>) -> Response { - let auth_driver = state.auth_checker.lock().await; + let mut auth_driver = state.auth_checker.lock().await; if auth_driver.user_has_permission(auth.user.username, name.clone(), Permission::PUSH, None).await.unwrap() { debug!("Upload requested"); let uuid = uuid::Uuid::new_v4(); @@ -39,7 +39,7 @@ pub async fn start_upload_post(Path((name, )): Path<(String, )>, Extension(auth) } pub async fn chunked_upload_layer_patch(Path((name, layer_uuid)): Path<(String, String)>, Extension(auth): Extension, state: State>, mut body: BodyStream) -> Response { - let auth_driver = state.auth_checker.lock().await; + let mut auth_driver = state.auth_checker.lock().await; if !auth_driver.user_has_permission(auth.user.username, name.clone(), Permission::PUSH, None).await.unwrap() { return unauthenticated_response(&state.config); } @@ -98,7 +98,7 @@ pub async fn chunked_upload_layer_patch(Path((name, layer_uuid)): Path<(String, } pub async fn finish_chunked_upload_put(Path((name, layer_uuid)): Path<(String, String)>, Query(query): Query>, Extension(auth): Extension, state: State>, body: Bytes) -> Response { - let auth_driver = state.auth_checker.lock().await; + let mut auth_driver = state.auth_checker.lock().await; if !auth_driver.user_has_permission(auth.user.username, name.clone(), Permission::PUSH, None).await.unwrap() { return unauthenticated_response(&state.config); } @@ -127,7 +127,7 @@ pub async fn finish_chunked_upload_put(Path((name, layer_uuid)): Path<(String, S } pub async fn cancel_upload_delete(Path((name, layer_uuid)): Path<(String, String)>, state: State>, Extension(auth): Extension) -> Response { - let auth_driver = state.auth_checker.lock().await; + let mut auth_driver = state.auth_checker.lock().await; if !auth_driver.user_has_permission(auth.user.username, name.clone(), Permission::PUSH, None).await.unwrap() { return unauthenticated_response(&state.config); } @@ -141,7 +141,7 @@ pub async fn cancel_upload_delete(Path((name, layer_uuid)): Path<(String, String } pub async fn check_upload_status_get(Path((name, layer_uuid)): Path<(String, String)>, state: State>, Extension(auth): Extension) -> Response { - let auth_driver = state.auth_checker.lock().await; + let mut auth_driver = state.auth_checker.lock().await; if !auth_driver.user_has_permission(auth.user.username, name.clone(), Permission::PUSH, None).await.unwrap() { return unauthenticated_response(&state.config); } diff --git a/src/app_state.rs b/src/app_state.rs index 82577e2..a645076 100644 --- a/src/app_state.rs +++ b/src/app_state.rs @@ -1,6 +1,6 @@ use sqlx::{Sqlite, Pool}; -use crate::auth_storage::AuthDriver; +use crate::auth::AuthDriver; use crate::storage::StorageDriver; use crate::config::Config; diff --git a/src/auth/ldap_driver.rs b/src/auth/ldap_driver.rs new file mode 100644 index 0000000..d276055 --- /dev/null +++ b/src/auth/ldap_driver.rs @@ -0,0 +1,145 @@ +use std::{slice::Iter, iter::Peekable}; + +use async_trait::async_trait; +use ldap3::{LdapConnAsync, Ldap, Scope, asn1::PL, ResultEntry, SearchEntry}; +use sqlx::{Pool, Sqlite}; +use tracing::{debug, warn}; + +use crate::{config::LdapConnectionConfig, dto::{user::{Permission, LoginSource}, RepositoryVisibility}, database::Database}; + +use super::AuthDriver; + +pub struct LdapAuthDriver { + ldap_config: LdapConnectionConfig, + ldap: Ldap, + database: Pool, +} + +impl LdapAuthDriver { + pub async fn new(config: LdapConnectionConfig, database: Pool) -> anyhow::Result { + debug!("connecting to ldap"); + let (conn, ldap) = LdapConnAsync::new(&config.connection_url).await?; + ldap3::drive!(conn); + + debug!("Created ldap connection!"); + + Ok(Self { + ldap_config: config, + ldap, + database, + }) + } + + async fn bind(&mut self) -> anyhow::Result<()> { + let res = self.ldap.simple_bind(&self.ldap_config.bind_dn, &self.ldap_config.bind_password).await?; + res.success()?; + + Ok(()) + } + + /* pub async fn verify_login(&mut self, username: &str, password: &str) -> anyhow::Result { + self.bind().await?; + + let filter = self.ldap_config.user_search_filter.replace("%s", &username); + let res = self.ldap.search(&self.ldap_config.user_base_dn, Scope::Subtree, &filter, + vec!["userPassword", "uid", "cn", "mail", "displayName"]).await?; + let (entries, _res) = res.success()?; + + let entries: Vec = entries + .into_iter() + .map(|e| SearchEntry::construct(e)) + .collect(); + + if entries.is_empty() { + Ok(false) + } else if entries.len() > 1 { + warn!("Got multiple DNs for user ({}), unsure which one to use!!", username); + Ok(false) + } else { + let entry = entries.first().unwrap(); + + let res = self.ldap.simple_bind(&entry.dn, password).await?; + if res.rc == 0 { + Ok(true) + } else if res.rc == 49 { + warn!("User failed to auth (invalidCredentials, rc=49)!"); + Ok(false) + } else { + // this would fail, its just here to propagate the error down + res.success()?; + + Ok(false) + } + } + } */ +} + +#[async_trait] +impl AuthDriver for LdapAuthDriver { + async fn user_has_permission(&mut self, email: String, repository: String, permission: Permission, required_visibility: Option) -> anyhow::Result { + self.bind().await?; + + // Send a request to LDAP to check if the user is an admin + + let filter = format!("(&({}={}){})", &self.ldap_config.login_attribute, email, &self.ldap_config.admin_filter); + let res = self.ldap.search(&self.ldap_config.group_base_dn, Scope::Subtree, &filter, vec!["*"]).await?; + let (entries, _res) = res.success()?; + + let entries: Vec = entries + .into_iter() + .map(|e| SearchEntry::construct(e)) + .collect(); + + if entries.len() > 0 { + Ok(true) + } else { + debug!("LDAP is falling back to database"); + // fall back to database auth since this user might be local + self.database.user_has_permission(email, repository, permission, required_visibility).await + } + } + + async fn verify_user_login(&mut self, email: String, password: String) -> anyhow::Result { + self.bind().await?; + + let filter = self.ldap_config.user_search_filter.replace("%s", &email); + let res = self.ldap.search(&self.ldap_config.user_base_dn, Scope::Subtree, &filter, + vec!["userPassword", "uid", "cn", "mail", "displayName"]).await?; + let (entries, _res) = res.success()?; + + let entries: Vec = entries + .into_iter() + .map(|e| SearchEntry::construct(e)) + .collect(); + + if entries.is_empty() { + Ok(false) + } else if entries.len() > 1 { + warn!("Got multiple DNs for user ({}), unsure which one to use!!", email); + Ok(false) + } else { + let entry = entries.first().unwrap(); + + let res = self.ldap.simple_bind(&entry.dn, &password).await?; + if res.rc == 0 { + // The user was authenticated through ldap! + // Check if the user is stored in the database, if not, add it. + let database = &self.database; + if !database.does_user_exist(email.clone()).await? { + let display_name = entry.attrs.get(&self.ldap_config.display_name_attribute).unwrap().first().unwrap().clone(); + database.create_user(email, display_name, LoginSource::LDAP).await?; + } + + Ok(true) + } else if res.rc == 49 { + warn!("User failed to auth (invalidCredentials, rc=49)!"); + Ok(false) + } else { + // this would fail, its just here to propagate the error down + res.success()?; + + Ok(false) + } + } + } +} \ No newline at end of file diff --git a/src/auth_storage.rs b/src/auth/mod.rs similarity index 65% rename from src/auth_storage.rs rename to src/auth/mod.rs index d8efd77..ab5c886 100644 --- a/src/auth_storage.rs +++ b/src/auth/mod.rs @@ -1,3 +1,5 @@ +pub mod ldap_driver; + use std::{collections::HashSet, ops::Deref, sync::Arc}; use axum::{extract::{State, Path}, http::{StatusCode, HeaderMap, header, HeaderName, Request}, middleware::Next, response::{Response, IntoResponse}}; @@ -18,60 +20,51 @@ pub trait AuthDriver: Send + Sync { /// * `repository`: Name of the repository. /// * `permissions`: Permission to check for. /// * `required_visibility`: Specified if there is a specific visibility of the repository that will give the user permission. - async fn user_has_permission(&self, username: String, repository: String, permission: Permission, required_visibility: Option) -> anyhow::Result; - async fn verify_user_login(&self, username: String, password: String) -> anyhow::Result; + async fn user_has_permission(&mut self, email: String, repository: String, permission: Permission, required_visibility: Option) -> anyhow::Result; + async fn verify_user_login(&mut self, email: String, password: String) -> anyhow::Result; } #[async_trait] impl AuthDriver for Pool { - async fn user_has_permission(&self, username: String, repository: String, permission: Permission, required_visibility: Option) -> anyhow::Result { + async fn user_has_permission(&mut self, email: String, repository: String, permission: Permission, required_visibility: Option) -> anyhow::Result { let allowed_to = { - match self.get_user_registry_type(username.clone()).await? { + match self.get_user_registry_type(email.clone()).await? { Some(RegistryUserType::Admin) => true, _ => { - if let Some(perms) = self.get_user_repo_permissions(username, repository.clone()).await? { - if perms.has_permission(permission) { - return Ok(true); - } - } - - if let Some(vis) = required_visibility { - if let Some(repo_vis) = self.get_repository_visibility(&repository).await? { - if vis == repo_vis { - return Ok(true); - } - } - } - - false + check_user_permissions(self, email, repository, permission, required_visibility).await? } - /* match database.get_user_repo_permissions(username, repository).await.unwrap() { - Some(perms) => if perms.has_permission(permission) { true } else { false }, - _ => false, - } */ } }; Ok(allowed_to) } - async fn verify_user_login(&self, username: String, password: String) -> anyhow::Result { - Database::verify_user_login(self, username, password).await + async fn verify_user_login(&mut self, email: String, password: String) -> anyhow::Result { + Database::verify_user_login(self, email, password).await } } -/// Temporary struct for storing auth information in memory. -/* pub struct MemoryAuthStorage { - pub valid_tokens: HashSet, -} - -impl MemoryAuthStorage { - pub fn new() -> Self { - Self { - valid_tokens: HashSet::new(), +// This ONLY checks permissions, does not check user type +pub async fn check_user_permissions(database: &D, email: String, repository: String, permission: Permission, required_visibility: Option) -> anyhow::Result +where + D: Database +{ + if let Some(perms) = database.get_user_repo_permissions(email, repository.clone()).await? { + if perms.has_permission(permission) { + return Ok(true); } } -} */ + + if let Some(vis) = required_visibility { + if let Some(repo_vis) = database.get_repository_visibility(&repository).await? { + if vis == repo_vis { + return Ok(true); + } + } + } + + Ok(false) +} #[derive(Clone)] pub struct AuthToken(pub String); diff --git a/src/config.rs b/src/config.rs index c85e4b0..57c5029 100644 --- a/src/config.rs +++ b/src/config.rs @@ -4,11 +4,37 @@ use serde::Deserialize; use std::env; -#[derive(Deserialize)] +#[derive(Deserialize, Clone)] +pub struct LdapConnectionConfig { + pub connection_url: String, + pub bind_dn: String, + pub bind_password: String, + pub user_base_dn: String, + pub group_base_dn: String, + pub user_search_filter: String, + pub group_search_filter: String, + pub admin_filter: String, + + #[serde(default = "default_login_attribute")] + pub login_attribute: String, + #[serde(default = "default_display_name_attribute")] + pub display_name_attribute: String, +} + +fn default_login_attribute() -> String { + "mail".to_string() +} + +fn default_display_name_attribute() -> String { + "displayName".to_string() +} + +#[derive(Deserialize, Clone)] pub struct Config { pub listen_address: String, pub listen_port: String, pub url: Option, + pub ldap: Option, } #[allow(dead_code)] diff --git a/src/database/mod.rs b/src/database/mod.rs index b758e77..bf83aa7 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -4,7 +4,7 @@ use tracing::debug; use chrono::{DateTime, Utc, NaiveDateTime, TimeZone}; -use crate::dto::{Tag, user::{User, RepositoryPermissions, RegistryUserType, Permission, UserAuth, TokenInfo}, RepositoryVisibility}; +use crate::dto::{Tag, user::{User, RepositoryPermissions, RegistryUserType, Permission, UserAuth, TokenInfo, LoginSource}, RepositoryVisibility}; #[async_trait] pub trait Database { @@ -50,12 +50,14 @@ pub trait Database { /// User stuff - async fn create_user(&self, username: String, email: String, password_hash: String, password_salt: String) -> sqlx::Result; - async fn verify_user_login(&self, username: String, password: String) -> anyhow::Result; - async fn get_user_registry_type(&self, username: String) -> anyhow::Result>; - async fn get_user_repo_permissions(&self, username: String, repository: String) -> anyhow::Result>; - async fn get_user_registry_usertype(&self, username: String) -> anyhow::Result>; - async fn store_user_token(&self, token: String, username: String, expiry: DateTime, created_at: DateTime) -> anyhow::Result<()>; + async fn does_user_exist(&self, email: String) -> sqlx::Result; + async fn create_user(&self, email: String, username: String, login_source: LoginSource) -> sqlx::Result; + async fn add_user_auth(&self, email: String, password_hash: String, password_salt: String) -> sqlx::Result<()>; + async fn verify_user_login(&self, email: String, password: String) -> anyhow::Result; + async fn get_user_registry_type(&self, email: String) -> anyhow::Result>; + async fn get_user_repo_permissions(&self, email: String, repository: String) -> anyhow::Result>; + async fn get_user_registry_usertype(&self, email: String) -> anyhow::Result>; + async fn store_user_token(&self, token: String, email: String, expiry: DateTime, created_at: DateTime) -> anyhow::Result<()>; async fn verify_user_token(&self, token: String) -> anyhow::Result>; } @@ -233,8 +235,7 @@ impl Database for Pool { } async fn has_repository(&self, repository: &str) -> sqlx::Result { - debug!("before query ig"); - let row: (u32, ) = match sqlx::query_as("SELECT COUNT(1) repositories WHERE 'name' = ?") + let row: (u32, ) = match sqlx::query_as("SELECT COUNT(1) FROM repositories WHERE \"name\" = ?") .bind(repository) .fetch_one(self).await { Ok(row) => row, @@ -321,33 +322,61 @@ impl Database for Pool { Ok(repos) } - async fn create_user(&self, username: String, email: String, password_hash: String, password_salt: String) -> sqlx::Result { + async fn does_user_exist(&self, email: String) -> sqlx::Result { + let row: (u32, ) = match sqlx::query_as("SELECT COUNT(1) FROM users WHERE \"email\" = ?") + .bind(email) + .fetch_one(self).await { + Ok(row) => row, + Err(e) => match e { + sqlx::Error::RowNotFound => { + return Ok(false) + }, + _ => { + return Err(e); + } + } + }; + + Ok(row.0 > 0) + } + + async fn create_user(&self, email: String, username: String, login_source: LoginSource) -> sqlx::Result { let username = username.to_lowercase(); let email = email.to_lowercase(); - sqlx::query("INSERT INTO users (username, email, password_hash, password_salt) VALUES (?, ?, ?, ?)") + sqlx::query("INSERT INTO users (username, email, login_source) VALUES (?, ?, ?)") .bind(username.clone()) + .bind(email.clone()) + .bind(login_source as u32) + .execute(self).await?; + + Ok(User::new(username, email, login_source)) + } + + async fn add_user_auth(&self, email: String, password_hash: String, password_salt: String) -> sqlx::Result<()> { + let email = email.to_lowercase(); + sqlx::query("INSERT INTO user_logins (email, password_hash, password_salt) VALUES (?, ?, ?)") .bind(email.clone()) .bind(password_hash) .bind(password_salt) .execute(self).await?; - Ok(User::new(username, email)) + Ok(()) } - async fn verify_user_login(&self, username: String, password: String) -> anyhow::Result { - let username = username.to_lowercase(); - let row: (String, ) = sqlx::query_as("SELECT password_hash FROM users WHERE username = ?") - .bind(username) + async fn verify_user_login(&self, email: String, password: String) -> anyhow::Result { + let email = email.to_lowercase(); + let row: (String, ) = sqlx::query_as("SELECT password_hash FROM users WHERE email = ?") + .bind(email) .fetch_one(self).await?; Ok(bcrypt::verify(password, &row.0)?) } - async fn get_user_registry_type(&self, username: String) -> anyhow::Result> { - let username = username.to_lowercase(); + async fn get_user_registry_type(&self, email: String) -> anyhow::Result> { + let email = email.to_lowercase(); - let row: (u32, ) = match sqlx::query_as("SELECT user_type FROM user_registry_permissions WHERE username = ?") - .bind(username) + let row: (u32, ) = match sqlx::query_as("SELECT user_type FROM user_registry_permissions WHERE email = ?") + .bind(email) .fetch_one(self).await { Ok(row) => row, Err(e) => match e { @@ -363,11 +392,11 @@ impl Database for Pool { Ok(Some(RegistryUserType::try_from(row.0)?)) } - async fn get_user_repo_permissions(&self, username: String, repository: String) -> anyhow::Result> { - let username = username.to_lowercase(); + async fn get_user_repo_permissions(&self, email: String, repository: String) -> anyhow::Result> { + let email = email.to_lowercase(); - let row: (u32, ) = match sqlx::query_as("SELECT repository_permissions FROM user_repo_permissions WHERE username = ? AND repository_name = ?") - .bind(username.clone()) + let row: (u32, ) = match sqlx::query_as("SELECT repository_permissions FROM user_repo_permissions WHERE email = ? AND repository_name = ?") + .bind(email.clone()) .bind(repository.clone()) .fetch_one(self).await { Ok(row) => row, @@ -384,7 +413,7 @@ impl Database for Pool { let vis = self.get_repository_visibility(&repository).await?.unwrap(); // Also get the user type for the registry, if its admin return admin repository permissions - let utype = self.get_user_registry_usertype(username).await?.unwrap(); // unwrap should be safe + let utype = self.get_user_registry_usertype(email).await?.unwrap(); // unwrap should be safe if utype == RegistryUserType::Admin { Ok(Some(RepositoryPermissions::new(Permission::ADMIN.bits(), vis))) } else { @@ -392,22 +421,22 @@ impl Database for Pool { } } - async fn get_user_registry_usertype(&self, username: String) -> anyhow::Result> { - let username = username.to_lowercase(); - let row: (u32, ) = sqlx::query_as("SELECT user_type FROM user_registry_permissions WHERE username = ?") - .bind(username) + async fn get_user_registry_usertype(&self, email: String) -> anyhow::Result> { + let email = email.to_lowercase(); + let row: (u32, ) = sqlx::query_as("SELECT user_type FROM user_registry_permissions WHERE email = ?") + .bind(email) .fetch_one(self).await?; Ok(Some(RegistryUserType::try_from(row.0)?)) } - async fn store_user_token(&self, token: String, username: String, expiry: DateTime, created_at: DateTime) -> anyhow::Result<()> { - let username = username.to_lowercase(); + async fn store_user_token(&self, token: String, email: String, expiry: DateTime, created_at: DateTime) -> anyhow::Result<()> { + let email = email.to_lowercase(); let expiry = expiry.timestamp(); let created_at = created_at.timestamp(); - sqlx::query("INSERT INTO user_tokens (token, username, expiry, created_at) VALUES (?, ?, ?, ?)") + sqlx::query("INSERT INTO user_tokens (token, email, expiry, created_at) VALUES (?, ?, ?, ?)") .bind(token) - .bind(username) + .bind(email) .bind(expiry) .bind(created_at) .execute(self).await?; @@ -416,18 +445,42 @@ impl Database for Pool { } async fn verify_user_token(&self, token: String) -> anyhow::Result> { - let token_row: (String, i64, i64, ) = sqlx::query_as("SELECT username, expiry, created_at FROM user_tokens WHERE token = ?") + let token_row: (String, i64, i64,) = match sqlx::query_as("SELECT email, expiry, created_at FROM user_tokens WHERE token = ?") .bind(token.clone()) - .fetch_one(self).await?; + .fetch_one(self).await { + Ok(row) => row, + Err(e) => match e { + sqlx::Error::RowNotFound => { + return Ok(None) + }, + _ => { + return Err(anyhow::Error::new(e)); + } + } + }; - let (username, expiry, created_at) = (token_row.0, token_row.1, token_row.2); + let (email, expiry, created_at) = (token_row.0, token_row.1, token_row.2); - let user_row: (String, ) = sqlx::query_as("SELECT email FROM users WHERE username = ?") - .bind(username.clone()) - .fetch_one(self).await?; + let user_row: (String, u32) = match sqlx::query_as("SELECT username, login_source FROM users WHERE email = ?") + .bind(email.clone()) + .fetch_one(self).await { + Ok(row) => row, + Err(e) => match e { + sqlx::Error::RowNotFound => { + return Ok(None) + }, + _ => { + return Err(anyhow::Error::new(e)); + } + } + }; + + /* let user_row: (String, u32) = sqlx::query_as("SELECT email, login_source FROM users WHERE email = ?") + .bind(email.clone()) + .fetch_one(self).await?; */ let (expiry, created_at) = (Utc.timestamp_millis_opt(expiry).unwrap(), Utc.timestamp_millis_opt(created_at).unwrap()); - let user = User::new(username, user_row.0); + let user = User::new(email, user_row.0, LoginSource::try_from(user_row.1)?); let token = TokenInfo::new(token, expiry, created_at); let auth = UserAuth::new(user, token); diff --git a/src/database/schemas/schema.sql b/src/database/schemas/schema.sql index 84b10c9..7af27d4 100644 --- a/src/database/schemas/schema.sql +++ b/src/database/schemas/schema.sql @@ -35,8 +35,14 @@ CREATE TABLE IF NOT EXISTS manifest_layers ( ); CREATE TABLE IF NOT EXISTS users ( - username TEXT NOT NULL UNIQUE PRIMARY KEY, - email TEXT NOT NULL, + email TEXT NOT NULL UNIQUE PRIMARY KEY, + username TEXT NOT NULL, + -- 0 = local, 1 = ldap + login_source BIGINT NOT NULL +); + +CREATE TABLE IF NOT EXISTS user_logins ( + email TEXT NOT NULL UNIQUE PRIMARY KEY, -- bcrypt hashed password password_hash TEXT NOT NULL, -- the salt generated along side the password hash @@ -44,13 +50,13 @@ CREATE TABLE IF NOT EXISTS users ( ); CREATE TABLE IF NOT EXISTS user_registry_permissions ( - username TEXT NOT NULL UNIQUE PRIMARY KEY, + email TEXT NOT NULL UNIQUE PRIMARY KEY, -- 0 = regular user, 1 = admin user_type INTEGER NOT NULL ); CREATE TABLE IF NOT EXISTS user_repo_permissions ( - username TEXT NOT NULL UNIQUE PRIMARY KEY, + email TEXT NOT NULL UNIQUE PRIMARY KEY, -- name of repository that this user has these permissions in repository_name TEXT NOT NULL, -- bitwised integer storing permissions @@ -59,10 +65,11 @@ CREATE TABLE IF NOT EXISTS user_repo_permissions ( CREATE TABLE IF NOT EXISTS user_tokens ( token TEXT NOT NULL UNIQUE PRIMARY KEY, - username TEXT NOT NULL, + email TEXT NOT NULL, expiry BIGINT NOT NULL, created_at BIGINT NOT NULL ); -- create admin user -INSERT OR IGNORE INTO users (username, email, password_hash, password_salt) VALUES ('admin', 'admin@example.com', '$2b$12$x5ECk0jUmOSfBWxW52wsyOmFxNZkwc2J9FH225if4eBnQYUvYLYYq', 'x5ECk0jUmOSfBWxW52wsyO'); \ No newline at end of file +INSERT OR IGNORE INTO users (username, email, login_source) VALUES ('admin', 'admin@example.com', 0); +INSERT OR IGNORE INTO user_logins (email, password_hash, password_salt) VALUES ('admin@example.com', '$2b$12$x5ECk0jUmOSfBWxW52wsyOmFxNZkwc2J9FH225if4eBnQYUvYLYYq', 'x5ECk0jUmOSfBWxW52wsyO'); \ No newline at end of file diff --git a/src/dto/user.rs b/src/dto/user.rs index 50e3b70..ad50dcc 100644 --- a/src/dto/user.rs +++ b/src/dto/user.rs @@ -5,17 +5,37 @@ use chrono::{DateTime, Utc}; use super::RepositoryVisibility; +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum LoginSource { + Database = 0, + LDAP = 1 +} + +impl TryFrom for LoginSource { + type Error = anyhow::Error; + + fn try_from(value: u32) -> Result { + match value { + 0 => Ok(Self::Database), + 1 => Ok(Self::LDAP), + _ => Err(anyhow::anyhow!("Invalid value for LoginSource: `{}`", value)), + } + } +} + #[derive(Clone, Debug, PartialEq)] pub struct User { pub username: String, pub email: String, + pub source: LoginSource, } impl User { - pub fn new(username: String, email: String) -> Self { + pub fn new(username: String, email: String, source: LoginSource) -> Self { Self { username, email, + source, } } } diff --git a/src/main.rs b/src/main.rs index bf317fa..e2f9c6d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,13 +6,13 @@ mod storage; mod byte_stream; mod config; mod query; -mod auth_storage; +mod auth; use std::net::SocketAddr; use std::str::FromStr; use std::sync::Arc; -use auth_storage::AuthDriver; +use auth::{AuthDriver, ldap_driver::LdapAuthDriver}; use axum::http::{Request, StatusCode, header, HeaderName}; use axum::middleware::Next; use axum::response::{Response, IntoResponse}; @@ -29,10 +29,11 @@ use tracing::{debug, Level}; use app_state::AppState; use database::Database; +use crate::dto::user::Permission; use crate::storage::StorageDriver; use crate::storage::filesystem::FilesystemDriver; -use crate::config::Config; +use crate::config::{Config, LdapConnectionConfig}; use tower_http::trace::TraceLayer; @@ -60,6 +61,12 @@ async fn change_request_paths(mut request: Request, next: Next) -> Resp #[tokio::main] async fn main() -> std::io::Result<()> { + tracing_subscriber::fmt() + .with_max_level(Level::DEBUG) + .init(); + + let config = Config::new().expect("Failure to parse config!"); + let pool = SqlitePoolOptions::new() .max_connections(15) .connect("test.db").await.unwrap(); @@ -67,18 +74,25 @@ async fn main() -> std::io::Result<()> { pool.create_schema().await.unwrap(); let storage_driver: Mutex> = Mutex::new(Box::new(FilesystemDriver::new("registry/blobs"))); - let auth_driver: Mutex> = Mutex::new(Box::new(pool.clone())); + + // figure out the auth driver depending on whats specified in the config, + // the fallback is a database auth driver. + let auth_driver: Mutex> = match config.ldap.clone() { + Some(ldap) => { + let ldap_driver = LdapAuthDriver::new(ldap, pool.clone()).await.unwrap(); + Mutex::new(Box::new(ldap_driver)) + }, + None => { + Mutex::new(Box::new(pool.clone())) + } + }; - let config = Config::new().expect("Failure to parse config!"); let app_addr = SocketAddr::from_str(&format!("{}:{}", config.listen_address, config.listen_port)).unwrap(); let state = Arc::new(AppState::new(pool, storage_driver, config, auth_driver)); + - tracing_subscriber::fmt() - .with_max_level(Level::DEBUG) - .init(); - - let auth_middleware = axum::middleware::from_fn_with_state(state.clone(), auth_storage::require_auth); + let auth_middleware = axum::middleware::from_fn_with_state(state.clone(), auth::require_auth); let path_middleware = axum::middleware::from_fn(change_request_paths); let app = Router::new()