Преглед изворни кода

codebase simplification + login via email or uid works

clement пре 1 година
родитељ
комит
9c71294563
9 измењених фајлова са 123 додато и 59 уклоњено
  1. 3 2
      Cargo.lock
  2. 1 0
      Cargo.toml
  3. 32 33
      src/handler.rs
  4. 31 2
      src/ldap/lib.rs
  5. 3 2
      src/ldap/pool.rs
  6. 16 7
      src/main.rs
  7. 0 11
      src/users/controller.rs
  8. 0 2
      src/users/mod.rs
  9. 37 0
      templates/formpassword.html

+ 3 - 2
Cargo.lock

@@ -366,6 +366,7 @@ dependencies = [
  "config",
  "deadpool",
  "ldap3",
+ "regex",
  "serde",
  "serde_derive",
  "tera",
@@ -1704,9 +1705,9 @@ dependencies = [
 
 [[package]]
 name = "regex"
-version = "1.10.4"
+version = "1.10.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c"
+checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619"
 dependencies = [
  "aho-corasick",
  "memchr",

+ 1 - 0
Cargo.toml

@@ -14,6 +14,7 @@ serde = "1.0.199"
 serde_derive = "1.0.199"
 config = "0.14.0"
 deadpool = "0.12.1"
+regex = "1.10.6"
 
 [lib]
 name = "ldap"

+ 32 - 33
src/users/routes.rs → src/handler.rs

@@ -1,16 +1,30 @@
 use actix_session::Session;
-use actix_web::{ get, http::{header, StatusCode}, post, web, HttpResponse, Responder};
+use actix_web::{ body, get, http::{header, StatusCode}, post, web, HttpResponse, Responder};
 use ldap::LdapWrapper;
-use serde::Deserialize;
+use serde::{ser::Impossible, Deserialize};
 use tera::Tera;
-use crate::users::controller;
 
 #[derive(Deserialize)]
-struct FormLoginData {
+pub struct FormLoginData {
     id: String,
     password: String,
 }
 
+#[derive(Deserialize)]
+struct FormChangePasswd {
+    current_password: String,
+    new_password: String,
+    new_password_conf: String,
+}
+
+async fn get_template(template_name: String) -> String {
+    let tera = Tera::new("templates/*.html")
+    .expect("Failed to parse template files");
+    let ctx = tera::Context::new();
+    tera.render(&template_name, &ctx)
+    .expect(format!("Faile to render template {}", template_name).as_str())
+}
+
 fn validate_session(session: &Session) -> bool {
     let user_id: Option<String> = session
     .get("user_id")
@@ -25,16 +39,7 @@ fn validate_session(session: &Session) -> bool {
     }
 }
 
-async fn get_template(template_name: String) -> String {
-    let tera = Tera::new("templates/*.html")
-    .expect("Failed to parse template files");
-    let ctx = tera::Context::new();
-    tera.render(&template_name, &ctx)
-    .expect(format!("Faile to render template {}", template_name).as_str())
-}
-
-#[get("/")]
-async fn index(session: Session) -> impl Responder {
+pub async fn index(session: Session) -> impl Responder {
     if validate_session(&session) {
         return HttpResponse::Ok()
         .status(StatusCode::FOUND)
@@ -47,27 +52,22 @@ async fn index(session: Session) -> impl Responder {
     .body(body)
 }
 
-#[post("/auth")]
-async fn auth(ldap_wrapper: web::Data<LdapWrapper>, form: web::Form<FormLoginData>, session: Session) -> impl Responder {
-    let user_authed = controller::login(&ldap_wrapper, &form.id, &form.password)
-    .await;
+pub async fn auth(ldap_wrapper: web::Data<LdapWrapper>, form: web::Form<FormLoginData>, session: Session) -> impl Responder {
 
-    if !user_authed {
-       return HttpResponse::Ok()
-        .status(StatusCode::FOUND)
-        .append_header((header::LOCATION, "/"))
-        .finish();
+    match ldap_wrapper.auth(form.id.clone(), form.password.clone())
+    .await {
+        Ok(user) => {
+            session.insert("user_id", user.uid).unwrap();
+            return HttpResponse::Ok()
+            .status(StatusCode::FOUND)
+            .append_header((header::LOCATION, "/home"))
+            .finish();
+        },
+        Err(_e) => return HttpResponse::Ok().status(StatusCode::FOUND).append_header((header::LOCATION, "/")).finish(),
     }
-
-    session.insert("user_id", &form.id).unwrap();
-    HttpResponse::Ok()
-    .status(StatusCode::FOUND)
-    .append_header((header::LOCATION, "/home"))
-    .finish()
 }
 
-#[get("/home")]
-async fn home(session: Session) -> impl Responder {
+pub async fn home(session: Session) -> impl Responder {
     if !validate_session(&session) {
         return HttpResponse::Ok()
         .status(StatusCode::FOUND)
@@ -79,8 +79,7 @@ async fn home(session: Session) -> impl Responder {
     .body(body)
 }
 
-#[get("/signout")]
-async fn signout(session: Session) -> impl Responder {
+pub async fn signout(session: Session) -> impl Responder {
     session.purge();
     HttpResponse::Ok()
     .status(StatusCode::FOUND)

+ 31 - 2
src/ldap/lib.rs

@@ -1,4 +1,6 @@
 use deadpool::managed::Pool;
+use ldap3::SearchEntry;
+use regex::Regex;
 
 pub mod pool;
 
@@ -13,14 +15,41 @@ pub struct LdapWrapper {
     config: LdapConfig,
 }
 
+pub struct User {
+    pub uid: String,
+    pub groups: Vec<String>,
+}
+
 impl LdapWrapper {
     pub fn new(config: LdapConfig) -> LdapWrapper {
         let ldap_pool = get_ldap_pool(config.clone());
         LdapWrapper{ldap_pool, config}
     }
 
-    pub async fn auth(&self, uid: String, passwd: String) -> Result<(), Error> {
+    pub async fn auth(&self, username: String, passwd: String) -> Result<User, Error> {
         let mut ldap = self.ldap_pool.get().await.unwrap();
+        let mut uid = username.clone();
+
+        let email = Regex::new(r"^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,6}*").unwrap();
+        if email.is_match(&username) {
+            let search_res = ldap.search(&self.config.basedn, ldap3::Scope::Subtree, format!("(mail={})", username).as_str(), vec!["uid"])
+            .await.map_err(|_| {Error::Authfailed(format!("Wrong username or password"))})?;
+
+            let (result_entry, _ldap_res) = search_res.success().map_err(|_| {Error::Authfailed(format!("Wrong username or password"))})?;
+
+            if result_entry.is_empty() {
+                return Err(Error::Authfailed((format!("No record found"))));
+            }
+
+            if result_entry.len() > 1 {
+                return Err(Error::Authfailed((format!("Multiple records found"))));
+            }
+
+            let records = SearchEntry::construct(result_entry.first().unwrap().to_owned());
+
+            uid = records.attrs.get("uid").unwrap().first().unwrap().to_owned();
+        }
+
         ldap
         .simple_bind(format!("uid={},{}", uid, self.config.basedn).as_str(), &passwd)
         .await
@@ -32,6 +61,6 @@ impl LdapWrapper {
                 Error::Authfailed(format!("Wrong username or password"))
             })
         })
-        .and(Ok(()))
+        .and(Ok(User{uid: uid, groups: vec!("coucou".to_string())}))
     }
 }

+ 3 - 2
src/ldap/pool.rs

@@ -27,14 +27,15 @@ impl managed::Manager for LdapManager {
         let ldap_settings = LdapConnSettings::new()
         .set_starttls(self.config.starttls);
         let ldap_url = format!("ldap://{}:{}", self.config.hostname, self.config.port);
-        let (conn, ldap) = LdapConnAsync::with_settings(ldap_settings, &ldap_url)
+        let (conn, mut ldap) = LdapConnAsync::with_settings(ldap_settings, &ldap_url)
         .await?;
         ldap3::drive!(conn);
+        ldap.simple_bind(&self.config.binddn, &self.config.bindpw).await?;
         Ok(ldap)
     }
 
     async fn recycle(&self, conn: &mut Self::Type, _: &managed::Metrics) -> managed::RecycleResult<LdapError> {
-        conn.simple_bind("", "").await?;
+        conn.simple_bind(&self.config.binddn, &self.config.bindpw).await?;
         Ok(())
     }
 }

+ 16 - 7
src/main.rs

@@ -3,18 +3,27 @@ use actix_session::{storage::RedisSessionStore, SessionMiddleware};
 use config::Config;
 use ldap::{pool::LdapConfig, LdapWrapper};
 use std::env;
-mod users;
+mod handler;
 
 #[actix_web::main]
 async fn main() -> std::io::Result<()> {
     let settings_path = env::var("BIMNG_SETTINGS_PATH")
-    .unwrap();
+    .expect("BIMNG_SETTINGS_PATH env var must be set");
     let settings = Config::builder()
     .add_source(config::File::with_name(settings_path.as_str()))
     .build()
     .unwrap();
 
-    let redis_store = RedisSessionStore::new("redis://127.0.0.1:6379").await.unwrap();
+    let redis_store = match RedisSessionStore::new("redis://127.0.0.1:6379").await {
+        Ok(redis_store) => {
+            redis_store
+        }
+        Err(_) => {
+            println!("Failed to connect to redis session store");
+            std::process::exit(1);
+        }
+    };
+
     let signing_key = Key::generate();
 
     let config = LdapConfig {
@@ -36,10 +45,10 @@ async fn main() -> std::io::Result<()> {
             .build()
         )
         .app_data(web::Data::new(ldap_wrapper.clone()))
-        .service(users::routes::index)
-        .service(users::routes::auth)
-        .service(users::routes::home)
-        .service(users::routes::signout)
+        .route("/", web::get().to(handler::index))
+        .route("/auth", web::post().to(handler::auth))
+        .route("/home", web::get().to(handler::home))
+        .route("/signout", web::get().to(handler::signout))
     })
     .bind(("127.0.0.1", 8080))?
     .run()

+ 0 - 11
src/users/controller.rs

@@ -1,11 +0,0 @@
-use ldap::LdapWrapper;
-
-pub(crate) async fn login(ldap_wrapper: &LdapWrapper, id: &String, password: &String) -> bool {
-    let res = ldap_wrapper
-    .auth(id.to_string(), password.to_string()).await;
-
-    if res.is_ok() {
-        return true;
-    }
-    return false;
-}

+ 0 - 2
src/users/mod.rs

@@ -1,2 +0,0 @@
-pub mod routes;
-mod controller;

+ 37 - 0
templates/formpassword.html

@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>BIM</title>
+        <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css">
+    </head>
+    <body class="layout-default">
+       <div class="section">
+            <div class="columns is-centered">
+                <form method="post" accept-charset="UTF-8" action="changepassword" class="box">
+                    <div class="field">
+                        <label for="password_current" class="label">Current password</label>
+                        <div class="control">
+                            <input id="password_current" type="password" name="password_current" class="input is-rounded is-primary">
+                        </div>
+                    </div>
+
+                    <div class="field">
+                        <label for="password" class="label">New password</label>
+                        <div class="control">
+                            <input id="password" type="password" name="password" class="input is-rounded is-primary">
+                        </div>
+                    </div>
+
+                    <div class="field">
+                        <label for="password_conf" class="label">Confirm new password</label>
+                        <div class="control">
+                            <input id="password_conf" type="password" name="password_conf" class="input is-rounded is-primary">
+                        </div>
+                    </div>
+                
+                    <button class="button is-primary">Confirm</button>
+                </form>
+            </div>
+        </div>
+    </body>
+</html>