implement ldap support
This commit is contained in:
parent
d98a8e3790
commit
be59462741
|
@ -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"
|
||||
|
|
|
@ -51,3 +51,4 @@ sha2 = "0.10.6"
|
|||
rand = "0.8.5"
|
||||
bcrypt = "0.14.0"
|
||||
bitflags = "2.2.1"
|
||||
ldap3 = "0.11.1"
|
||||
|
|
|
@ -8,5 +8,7 @@ pkgs.mkShell {
|
|||
postgresql
|
||||
sqlite
|
||||
diesel-cli
|
||||
openssl
|
||||
pkg-config
|
||||
];
|
||||
}
|
|
@ -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<AuthBasic>, state: State<Arc<AppS
|
|||
|
||||
if let (Some(account), Some(password)) = (&auth.account, auth.password) {
|
||||
// Ensure that the password is correct
|
||||
let auth_driver = state.auth_checker.lock().await;
|
||||
let mut auth_driver = state.auth_checker.lock().await;
|
||||
if !auth_driver.verify_user_login(account.clone(), password).await.unwrap() {
|
||||
debug!("Authentication failed, incorrect password!");
|
||||
|
||||
|
|
|
@ -8,14 +8,14 @@ use axum::response::{IntoResponse, Response};
|
|||
use tokio_util::io::ReaderStream;
|
||||
|
||||
use crate::app_state::AppState;
|
||||
use crate::auth_storage::{unauthenticated_response, AuthDriver};
|
||||
use crate::auth::{unauthenticated_response, AuthDriver};
|
||||
use crate::database::Database;
|
||||
use crate::dto::RepositoryVisibility;
|
||||
use crate::dto::user::{Permission, RegistryUserType, UserAuth};
|
||||
|
||||
pub async fn digest_exists_head(Path((name, layer_digest)): Path<(String, String)>, state: State<Arc<AppState>>, Extension(auth): Extension<UserAuth>) -> 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<Arc<AppState>>, Extension(auth): Extension<UserAuth>) -> 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);
|
||||
}
|
||||
|
|
|
@ -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<Arc<AppState>>, Extension(auth): Extension<UserAuth>, 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<Arc<AppState>>, Extension(auth): Extension<UserAuth>) -> 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<Arc<AppState>>, Extension(auth): Extension<UserAuth>) -> 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<Arc<AppState>>, Extension(auth): Extension<UserAuth>) -> 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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<UserAuth>, state: State<Arc<AppState>>) -> 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<UserAuth>, state: State<Arc<AppState>>, 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<HashMap<String, String>>, Extension(auth): Extension<UserAuth>, state: State<Arc<AppState>>, 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<Arc<AppState>>, Extension(auth): Extension<UserAuth>) -> 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<Arc<AppState>>, Extension(auth): Extension<UserAuth>) -> 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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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<Sqlite>,
|
||||
}
|
||||
|
||||
impl LdapAuthDriver {
|
||||
pub async fn new(config: LdapConnectionConfig, database: Pool<Sqlite>) -> anyhow::Result<Self> {
|
||||
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<bool> {
|
||||
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<SearchEntry> = 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<RepositoryVisibility>) -> anyhow::Result<bool> {
|
||||
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<SearchEntry> = 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<bool> {
|
||||
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<SearchEntry> = 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<RepositoryVisibility>) -> anyhow::Result<bool>;
|
||||
async fn verify_user_login(&self, username: String, password: String) -> anyhow::Result<bool>;
|
||||
async fn user_has_permission(&mut self, email: String, repository: String, permission: Permission, required_visibility: Option<RepositoryVisibility>) -> anyhow::Result<bool>;
|
||||
async fn verify_user_login(&mut self, email: String, password: String) -> anyhow::Result<bool>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl AuthDriver for Pool<Sqlite> {
|
||||
async fn user_has_permission(&self, username: String, repository: String, permission: Permission, required_visibility: Option<RepositoryVisibility>) -> anyhow::Result<bool> {
|
||||
async fn user_has_permission(&mut self, email: String, repository: String, permission: Permission, required_visibility: Option<RepositoryVisibility>) -> anyhow::Result<bool> {
|
||||
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<bool> {
|
||||
Database::verify_user_login(self, username, password).await
|
||||
async fn verify_user_login(&mut self, email: String, password: String) -> anyhow::Result<bool> {
|
||||
Database::verify_user_login(self, email, password).await
|
||||
}
|
||||
}
|
||||
|
||||
/// Temporary struct for storing auth information in memory.
|
||||
/* pub struct MemoryAuthStorage {
|
||||
pub valid_tokens: HashSet<String>,
|
||||
}
|
||||
|
||||
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<D>(database: &D, email: String, repository: String, permission: Permission, required_visibility: Option<RepositoryVisibility>) -> anyhow::Result<bool>
|
||||
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);
|
|
@ -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<String>,
|
||||
pub ldap: Option<LdapConnectionConfig>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
|
|
|
@ -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<User>;
|
||||
async fn verify_user_login(&self, username: String, password: String) -> anyhow::Result<bool>;
|
||||
async fn get_user_registry_type(&self, username: String) -> anyhow::Result<Option<RegistryUserType>>;
|
||||
async fn get_user_repo_permissions(&self, username: String, repository: String) -> anyhow::Result<Option<RepositoryPermissions>>;
|
||||
async fn get_user_registry_usertype(&self, username: String) -> anyhow::Result<Option<RegistryUserType>>;
|
||||
async fn store_user_token(&self, token: String, username: String, expiry: DateTime<Utc>, created_at: DateTime<Utc>) -> anyhow::Result<()>;
|
||||
async fn does_user_exist(&self, email: String) -> sqlx::Result<bool>;
|
||||
async fn create_user(&self, email: String, username: String, login_source: LoginSource) -> sqlx::Result<User>;
|
||||
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<bool>;
|
||||
async fn get_user_registry_type(&self, email: String) -> anyhow::Result<Option<RegistryUserType>>;
|
||||
async fn get_user_repo_permissions(&self, email: String, repository: String) -> anyhow::Result<Option<RepositoryPermissions>>;
|
||||
async fn get_user_registry_usertype(&self, email: String) -> anyhow::Result<Option<RegistryUserType>>;
|
||||
async fn store_user_token(&self, token: String, email: String, expiry: DateTime<Utc>, created_at: DateTime<Utc>) -> anyhow::Result<()>;
|
||||
async fn verify_user_token(&self, token: String) -> anyhow::Result<Option<UserAuth>>;
|
||||
}
|
||||
|
||||
|
@ -233,8 +235,7 @@ impl Database for Pool<Sqlite> {
|
|||
}
|
||||
|
||||
async fn has_repository(&self, repository: &str) -> sqlx::Result<bool> {
|
||||
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<Sqlite> {
|
|||
Ok(repos)
|
||||
}
|
||||
|
||||
async fn create_user(&self, username: String, email: String, password_hash: String, password_salt: String) -> sqlx::Result<User> {
|
||||
async fn does_user_exist(&self, email: String) -> sqlx::Result<bool> {
|
||||
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<User> {
|
||||
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<bool> {
|
||||
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<bool> {
|
||||
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<Option<RegistryUserType>> {
|
||||
let username = username.to_lowercase();
|
||||
async fn get_user_registry_type(&self, email: String) -> anyhow::Result<Option<RegistryUserType>> {
|
||||
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<Sqlite> {
|
|||
Ok(Some(RegistryUserType::try_from(row.0)?))
|
||||
}
|
||||
|
||||
async fn get_user_repo_permissions(&self, username: String, repository: String) -> anyhow::Result<Option<RepositoryPermissions>> {
|
||||
let username = username.to_lowercase();
|
||||
async fn get_user_repo_permissions(&self, email: String, repository: String) -> anyhow::Result<Option<RepositoryPermissions>> {
|
||||
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<Sqlite> {
|
|||
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<Sqlite> {
|
|||
}
|
||||
}
|
||||
|
||||
async fn get_user_registry_usertype(&self, username: String) -> anyhow::Result<Option<RegistryUserType>> {
|
||||
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<Option<RegistryUserType>> {
|
||||
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<Utc>, created_at: DateTime<Utc>) -> anyhow::Result<()> {
|
||||
let username = username.to_lowercase();
|
||||
async fn store_user_token(&self, token: String, email: String, expiry: DateTime<Utc>, created_at: DateTime<Utc>) -> 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<Sqlite> {
|
|||
}
|
||||
|
||||
async fn verify_user_token(&self, token: String) -> anyhow::Result<Option<UserAuth>> {
|
||||
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);
|
||||
|
||||
|
|
|
@ -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');
|
||||
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');
|
|
@ -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<u32> for LoginSource {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(value: u32) -> Result<Self, Self::Error> {
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
32
src/main.rs
32
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<B>(mut request: Request<B>, next: Next<B>) -> 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<Box<dyn StorageDriver>> = Mutex::new(Box::new(FilesystemDriver::new("registry/blobs")));
|
||||
let auth_driver: Mutex<Box<dyn AuthDriver>> = Mutex::new(Box::new(pool.clone()));
|
||||
|
||||
let config = Config::new().expect("Failure to parse config!");
|
||||
// figure out the auth driver depending on whats specified in the config,
|
||||
// the fallback is a database auth driver.
|
||||
let auth_driver: Mutex<Box<dyn AuthDriver>> = 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 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()
|
||||
|
|
Loading…
Reference in New Issue