Verified Commit 917f54b3 authored by Sven-Hendrik Haase's avatar Sven-Hendrik Haase
Browse files

Final foundation

parent 786dbe8a
......@@ -384,17 +384,6 @@ version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
[[package]]
name = "bcrypt"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4d0faafe9e089674fc3efdb311ff5253d445c79d85d1d28bd3ace76d45e7164"
dependencies = [
"base64 0.13.0",
"blowfish",
"getrandom 0.2.0",
]
[[package]]
name = "bitflags"
version = "1.2.1"
......@@ -410,17 +399,6 @@ dependencies = [
"generic-array",
]
[[package]]
name = "blowfish"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32fa6a061124e37baba002e496d203e23ba3d7b73750be82dbfbc92913048a5b"
dependencies = [
"byteorder",
"cipher",
"opaque-debug",
]
[[package]]
name = "brotli-sys"
version = "0.3.2"
......@@ -486,15 +464,6 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "cipher"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12f8e7987cbd042a63249497f41aed09f8e65add917ea6566effbc56578d6801"
dependencies = [
"generic-array",
]
[[package]]
name = "clap"
version = "2.33.3"
......@@ -817,17 +786,6 @@ dependencies = [
"wasi",
]
[[package]]
name = "getrandom"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee8025cf36f917e6a52cce185b7c7177689b838b7ec138364e50cc2277a56cf4"
dependencies = [
"cfg-if 0.1.10",
"libc",
"wasi",
]
[[package]]
name = "gimli"
version = "0.23.0"
......@@ -1116,12 +1074,13 @@ dependencies = [
name = "mail-credential-syncer"
version = "0.1.0"
dependencies = [
"actix-rt",
"actix-web",
"anyhow",
"bcrypt",
"keycloak",
"log",
"pretty_env_logger",
"regex",
"reqwest",
"serde_json",
"structopt",
......@@ -1476,7 +1435,7 @@ version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
dependencies = [
"getrandom 0.1.15",
"getrandom",
"libc",
"rand_chacha",
"rand_core",
......@@ -1499,7 +1458,7 @@ version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
dependencies = [
"getrandom 0.1.15",
"getrandom",
]
[[package]]
......
......@@ -16,11 +16,12 @@ codegen-units = 1
[dependencies]
structopt = "0.3"
keycloak = "11"
bcrypt = "0.9"
regex = "1"
url = "2"
reqwest = { version = "0.10", features = [ "json" ] }
anyhow = "1"
actix-web = "3"
actix-rt = "1"
log = "0.4"
pretty_env_logger = "0.4"
serde_json = "1"
......@@ -6,4 +6,15 @@ Small tool to sync mail passwords from Keycloak to Dovecot
First of all, we need to start a pre-configured Keycloak server to run our tests against:
git clone https://github.com/svenstaro/keycloak-http-webhook-provider.git
cd keycloak-http-webhook-provider
mvn clean install
keycloak/run-keycloak-container.sh
Once Keycloak is running, we can start our webhook server:
cargo run -- --listen 0.0.0.0:5000
Now go to your Keycloak server at http://localhost:8080, login via admin/admin and add a new user
attribute called `mail_password_hash` to a user of your choice. The webhook server should then give
you some output of what's happening.
use actix_web::{middleware::Logger, post, web, App, HttpResponse, HttpServer, Responder};
use anyhow::{Context, Result};
use std::net::SocketAddr;
use log::{debug, trace, warn};
use regex::Regex;
use std::path::PathBuf;
use std::{net::SocketAddr, time::Duration};
use structopt::StructOpt;
use url::Url;
......@@ -16,51 +18,89 @@ struct CliConfig {
/// Local socket address to listen on
#[structopt(long)]
listen: SocketAddr,
// TODO Implement all of the below!
// /// Username for basic auth of HTTP endpoint
// #[structopt(long, env)]
// basic_auth_username: String,
//
// /// Password for basic auth of HTTP endpoint
// #[structopt(long, env)]
// basic_auth_password: String,
//
// /// Path to file containing Keycloak UUID to mail address mappings
// #[structopt(long)]
// mailmap: PathBuf,
//
// /// Path to a post-sync hook executable
// #[structopt(long)]
// post_sync_hook: PathBuf,
//
// /// Keycloak URL for incoming webhook validation and periodic syncs
// #[structopt(long)]
// keycloak_url: Url,
//
// /// Keycloak admin username
// #[structopt(long, env)]
// keycloak_admin_username: String,
//
// /// Keycloak admin password
// #[structopt(long, env)]
// keycloak_admin_password: String,
}
/// Username for basic auth of HTTP endpoint
#[structopt(long, env)]
basic_auth_username: String,
/// Password for basic auth of HTTP endpoint
#[structopt(long, env)]
basic_auth_password: String,
/// Path to file containing Keycloak UUID to mail address mappings
#[structopt(long)]
mailmap: PathBuf,
/// Path to a post-sync hook executable
#[structopt(long)]
post_sync_hook: PathBuf,
#[post("/webhook")]
async fn webhook(data: web::Json<serde_json::Value>) -> impl Responder {
// We're getting a `serde_json::Value` here instead of trying to deserialize into a struct
// because we're only interested in a small subset of the data and we don't care about
// validation errors in case the struct doesn't match the data sent. I think this gives us more
// flexibility in handling and ignoring most validation errors that we see while still ensuring
// we get valid JSON to start with.
/// Keycloak URL for incoming webhook validation and periodic syncs
#[structopt(long)]
keycloak_url: Url,
trace!("Received\n{:#}", data);
/// Keycloak admin username
#[structopt(long, env)]
keycloak_admin_username: String,
if let Some(hash) = data.pointer("/representation/attributes/mail_password_hash/0") {
debug!("Received update with mail_password_hash: {:#}", hash);
/// Keycloak admin password
#[structopt(long, env)]
keycloak_admin_password: String,
}
if let Some(hash_value) = hash.as_str() {
let re = Regex::new(r"^\$2y\$(.*)$").unwrap();
if let Some(hash_last_part) = re
.captures(hash_value)
.and_then(|captured_value| captured_value.get(1))
{
let final_hash_value = hash_last_part.as_str();
// TODO Actually do something interesting here with the value.
} else {
warn!("Received hash but it has wrong format");
debug!("Hash in question is {:#}", hash_value);
return HttpResponse::BadRequest().body("Hash has wrong format");
}
} else {
warn!("Received update with mail_password_hash but ");
return HttpResponse::BadRequest().body("Empty hash value");
}
}
#[post("/webhook")]
async fn webhook(info: web::Json<serde_json::Value>) -> impl Responder {
println!("{:#}", info);
HttpResponse::Ok().body("Hello world!")
}
#[actix_web::main]
async fn main() -> Result<()> {
std::env::set_var("RUST_LOG", "actix_web=info");
std::env::set_var("RUST_LOG", "mail_credential_syncer=debug,actix_web=info");
pretty_env_logger::init();
// let args = CliConfig::from_args();
let args = CliConfig::from_args();
actix_rt::spawn(async move {
let mut interval = actix_rt::time::interval(Duration::from_secs(10));
loop {
interval.tick().await;
// TODO Do some periodic syncing somehow
debug!("syncing");
}
});
HttpServer::new(|| App::new().wrap(Logger::default()).service(webhook))
.bind("0.0.0.0:5000")?
.bind(args.listen)?
.run()
.await
.context("Failed to run actix")
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment