Browse Source

Changing password now works

clement 1 year ago
parent
commit
918621f786
6 changed files with 160 additions and 13 deletions
  1. 37 0
      Cargo.lock
  2. 4 0
      Cargo.toml
  3. 50 3
      src/handler.rs
  4. 61 4
      src/ldap/lib.rs
  5. 2 0
      src/main.rs
  6. 6 6
      templates/formpassword.html

+ 37 - 0
Cargo.lock

@@ -357,18 +357,28 @@ version = "0.21.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
 
+[[package]]
+name = "base64"
+version = "0.22.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
+
 [[package]]
 name = "bim-ng"
 version = "0.1.0"
 dependencies = [
  "actix-session",
  "actix-web",
+ "base64 0.22.1",
  "config",
  "deadpool",
+ "digest",
  "ldap3",
  "regex",
+ "ring",
  "serde",
  "serde_derive",
+ "sha2",
  "tera",
 ]
 
@@ -1732,6 +1742,21 @@ version = "0.8.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56"
 
+[[package]]
+name = "ring"
+version = "0.17.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d"
+dependencies = [
+ "cc",
+ "cfg-if",
+ "getrandom",
+ "libc",
+ "spin",
+ "untrusted",
+ "windows-sys 0.52.0",
+]
+
 [[package]]
 name = "ron"
 version = "0.8.1"
@@ -1965,6 +1990,12 @@ dependencies = [
  "windows-sys 0.52.0",
 ]
 
+[[package]]
+name = "spin"
+version = "0.9.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
+
 [[package]]
 name = "subtle"
 version = "2.5.0"
@@ -2329,6 +2360,12 @@ dependencies = [
  "subtle",
 ]
 
+[[package]]
+name = "untrusted"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
+
 [[package]]
 name = "url"
 version = "2.5.0"

+ 4 - 0
Cargo.toml

@@ -15,6 +15,10 @@ serde_derive = "1.0.199"
 config = "0.14.0"
 deadpool = "0.12.1"
 regex = "1.10.6"
+ring = "0.17.8"
+base64 = "0.22.1"
+digest = "0.10.7"
+sha2 = "0.10.8"
 
 [lib]
 name = "ldap"

+ 50 - 3
src/handler.rs

@@ -1,7 +1,7 @@
 use actix_session::Session;
-use actix_web::{ body, get, http::{header, StatusCode}, post, web, HttpResponse, Responder};
+use actix_web::{http::{header, StatusCode}, web, HttpResponse, Responder};
 use ldap::LdapWrapper;
-use serde::{ser::Impossible, Deserialize};
+use serde::Deserialize;
 use tera::Tera;
 
 #[derive(Deserialize)]
@@ -11,7 +11,7 @@ pub struct FormLoginData {
 }
 
 #[derive(Deserialize)]
-struct FormChangePasswd {
+pub struct FormChangePasswd {
     current_password: String,
     new_password: String,
     new_password_conf: String,
@@ -79,6 +79,53 @@ pub async fn home(session: Session) -> impl Responder {
     .body(body)
 }
 
+pub async fn form_password(session: Session) -> impl Responder {
+    if !validate_session(&session) {
+        return HttpResponse::Ok()
+        .status(StatusCode::FOUND)
+        .append_header((header::LOCATION, "/"))
+        .finish();
+    }
+    let body = get_template("formpassword.html".to_string()).await;
+    HttpResponse::Ok().content_type("text/html")
+    .body(body)
+}
+
+pub async fn change_password(ldap_wrapper: web::Data<LdapWrapper>, form: web::Form<FormChangePasswd>, session: Session) -> impl Responder {
+    if !validate_session(&session) {
+        return HttpResponse::Ok()
+        .status(StatusCode::FOUND)
+        .append_header((header::LOCATION, "/"))
+        .finish();
+    }
+
+    if form.new_password != form.new_password_conf {
+        return HttpResponse::Ok()
+        .status(StatusCode::FOUND)
+        .append_header((header::LOCATION, "/changepassword"))
+        .finish();
+    }
+
+    if form.new_password.len() == 0 || form.new_password_conf.len() == 0 {
+        return HttpResponse::Ok()
+        .status(StatusCode::FOUND)
+        .append_header((header::LOCATION, "/changepassword"))
+        .finish();
+    }
+
+    let uid = session.get("user_id").unwrap().unwrap();
+
+    match ldap_wrapper.change_password(uid, form.current_password.clone(), form.new_password.clone()).await {
+        Ok(()) => {
+            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(),
+    }
+}
+
 pub async fn signout(session: Session) -> impl Responder {
     session.purge();
     HttpResponse::Ok()

+ 61 - 4
src/ldap/lib.rs

@@ -1,13 +1,18 @@
+use std::collections::HashSet;
+use base64::{engine::general_purpose::STANDARD, Engine as _};
 use deadpool::managed::Pool;
 use ldap3::SearchEntry;
 use regex::Regex;
+use ring::rand::{self, SecureRandom};
+use sha2::{Sha512, Digest};
 
 pub mod pool;
 
 use pool::{get_ldap_pool, LdapConfig, LdapManager};
 
 pub enum Error {
-    Authfailed(String)
+    Authfailed(String),
+    LdapServerError(String)
 }
 #[derive(Clone)]
 pub struct LdapWrapper {
@@ -38,11 +43,11 @@ impl LdapWrapper {
             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"))));
+                return Err(Error::Authfailed(format!("No record found")));
             }
 
             if result_entry.len() > 1 {
-                return Err(Error::Authfailed((format!("Multiple records found"))));
+                return Err(Error::Authfailed(format!("Multiple records found")));
             }
 
             let records = SearchEntry::construct(result_entry.first().unwrap().to_owned());
@@ -61,6 +66,58 @@ impl LdapWrapper {
                 Error::Authfailed(format!("Wrong username or password"))
             })
         })
-        .and(Ok(User{uid: uid, groups: vec!("coucou".to_string())}))
+        .and(Ok(User{uid: uid, groups: vec!("coucou".to_string())})) //TODO: add groups
+    }
+
+    pub async fn change_password(&self, username: String, password: String, new_password: String) -> Result<(), Error> {
+        let mut salt = [0u8; 4];
+        let salt_generator = rand::SystemRandom::new();
+        let _ = salt_generator.fill(&mut salt);
+
+        let mut password_bytes = new_password.into_bytes();
+        //adding salt to the end of the password
+        salt.map(|elem| {
+            password_bytes.push(elem);
+        });
+
+        //Getting sha512 sum
+        let mut hasher = Sha512::new();
+        hasher.update(password_bytes);
+        let mut hash = hasher.finalize().as_slice().to_vec();
+
+        //Adding salt to the end of hash
+        salt.map(|elem| {
+            hash.push(elem);
+        });
+
+        let b64_new_password = format!("{{SSHA512}}{}", STANDARD.encode(hash));
+
+        let mut ldap = self.ldap_pool.get().await.unwrap();
+
+        let _ = ldap.simple_bind(format!("uid={},{}", username, self.config.basedn).as_str(), &password)
+        .await
+        .map_err(|_| {
+            Error::Authfailed(format!("Wrong current password"))
+        }).and_then(|r| {
+            r.success().map_err(|_| {
+                Error::Authfailed(format!("Wrong current password"))
+            })
+        });
+
+        let hashset= HashSet::from([b64_new_password.as_str()]);
+
+        let data = vec![ldap3::Mod::Replace("userPassword", hashset)];
+
+        ldap.modify(format!("uid={},{}", username, self.config.basedn).as_str(), data)
+        .await
+        .map_err(|e| {
+            Error::LdapServerError(format!("An error occured, contact admins: {}", e))
+        })
+        .and_then(|r| {
+            r.success().map_err(|e| {
+                Error::LdapServerError(format!("An error occured, contact admins: {}", e))
+            })
+        })
+        .and(Ok(()))
     }
 }

+ 2 - 0
src/main.rs

@@ -49,6 +49,8 @@ async fn main() -> std::io::Result<()> {
         .route("/auth", web::post().to(handler::auth))
         .route("/home", web::get().to(handler::home))
         .route("/signout", web::get().to(handler::signout))
+        .route("/changepassword", web::get().to(handler::form_password))
+        .route("/changepassword", web::post().to(handler::change_password))
     })
     .bind(("127.0.0.1", 8080))?
     .run()

+ 6 - 6
templates/formpassword.html

@@ -9,23 +9,23 @@
             <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>
+                        <label for="current_password" class="label">Current password</label>
                         <div class="control">
-                            <input id="password_current" type="password" name="password_current" class="input is-rounded is-primary">
+                            <input id="current_password" type="password" name="current_password" class="input is-rounded is-primary">
                         </div>
                     </div>
 
                     <div class="field">
-                        <label for="password" class="label">New password</label>
+                        <label for="new_password" class="label">New password</label>
                         <div class="control">
-                            <input id="password" type="password" name="password" class="input is-rounded is-primary">
+                            <input id="new_password" type="password" name="new_password" class="input is-rounded is-primary">
                         </div>
                     </div>
 
                     <div class="field">
-                        <label for="password_conf" class="label">Confirm new password</label>
+                        <label for="new_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">
+                            <input id="new_password_conf" type="password" name="new_password_conf" class="input is-rounded is-primary">
                         </div>
                     </div>