auth-server/src/main.rs
Alex Wright 9eaf121454 Adding route for login and login_form
The EmptyContext is probably dumb or at least goofy.. but I can't work
out a better way of making render() happy right now.
2020-02-29 20:23:50 +01:00

213 lines
5.3 KiB
Rust

#![deny(warnings)]
#![feature(proc_macro_hygiene)]
#[macro_use] extern crate rocket;
use log::info;
use serde_derive::Serialize;
use std::env;
use std::fs;
use std::path::Path;
use biscuit::{
Empty,
};
use biscuit::jwa::{
SignatureAlgorithm,
Algorithm,
};
use biscuit::jwk::{
RSAKeyParameters,
CommonParameters,
AlgorithmParameters,
JWK,
JWKSet,
};
use num::BigUint;
use openssl::rsa::Rsa;
use ldap3::{ LdapConn, Scope, SearchEntry };
use rocket::request::Form;
use rocket_contrib::json::Json;
use rocket_contrib::templates::Template;
#[derive(Debug)]
struct BasicAuthentication {
pub username: String,
pub password: String,
}
#[derive(Debug)]
pub enum AuthError {
Parse,
Decode,
LdapBind,
LdapConfig,
LdapConnection,
LdapSearch,
}
#[derive(Debug)]
struct LdapUser {
pub dn: String,
pub groups: Vec<String>,
pub mail: Vec<String>,
pub services: Vec<String>,
}
fn auth_user(auth: &BasicAuthentication) -> Result<LdapUser, AuthError> {
let ldap_server_addr = match env::var("LDAP_SERVER_ADDR") {
Ok(addr) => addr,
_ => return Err(AuthError::LdapConfig),
};
let ldap = match LdapConn::new(&ldap_server_addr) {
Ok(conn) => conn,
Err(_err) => return Err(AuthError::LdapConnection),
};
let base = format!("uid={},ou=people,dc=xeentech,dc=com", auth.username);
match ldap.simple_bind(&base, &auth.password).unwrap().success() {
Ok(_ldap) => println!("Connected and authenticated"),
Err(_err) => return Err(AuthError::LdapBind),
};
let filter = format!("(uid={})", auth.username);
let s = match ldap.search(&base, Scope::Subtree, &filter, vec!["mail", "enabledService", "memberOf"]) {
Ok(result) => {
let (rs, _) = result.success().unwrap();
rs
},
Err(_err) => return Err(AuthError::LdapSearch),
};
// Grab the first, if any, result and discard the rest
let se = SearchEntry::construct(s.first().unwrap().to_owned());
let services = match se.attrs.get("enabledService") {
Some(services) => services.to_vec(),
None => [].to_vec(),
};
let mail = match se.attrs.get("mail") {
Some(mail) => mail.to_vec(),
None => [].to_vec(),
};
let groups = match se.attrs.get("memberOf") {
Some(groups) => groups.to_vec(),
None => [].to_vec(),
};
info!("Authentication success for {:?}", base);
Ok(LdapUser {
dn: base,
groups: groups,
mail: mail,
services: services,
})
}
#[derive(FromForm)]
struct LoginData {
username: String,
password: String,
}
#[derive(Serialize)]
struct EmptyContext {
}
#[get("/login")]
fn login_form() -> Template {
let context = EmptyContext {};
Template::render("login_form", &context)
}
#[post("/login", data = "<form_data>")]
fn login(form_data: Form<LoginData>) -> String {
let auth = BasicAuthentication {
username: form_data.username.to_owned(),
password: form_data.password.to_owned(),
};
match auth_user(&auth) {
Ok(ldap_user) => format!("OK! {:?}", ldap_user),
_ => format!("Bad :("),
}
}
fn jwk_from_pem(file_path: &Path) -> Result<JWK<Empty>, Box<dyn std::error::Error + 'static>> {
let key_bytes = fs::read(file_path)?;
let rsa = Rsa::private_key_from_pem(key_bytes.as_slice())?;
Ok(JWK {
common: CommonParameters {
algorithm: Some(Algorithm::Signature(SignatureAlgorithm::RS256)),
key_id: Some(file_path.file_name().unwrap().to_str().unwrap().to_string()),
..Default::default()
},
algorithm: AlgorithmParameters::RSA(RSAKeyParameters {
n: BigUint::from_bytes_be(&rsa.n().to_vec()),
e: BigUint::from_bytes_be(&rsa.e().to_vec()),
..Default::default()
}),
additional: Default::default(),
})
}
#[get("/oauth2/keys")]
fn get_keys() -> Json<JWKSet<Empty>> {
let jwks: Vec<JWK<Empty>> = fs::read_dir("./").unwrap()
.filter_map(|dir_entry| {
let path = dir_entry.unwrap().path();
let ext = match path.extension() {
Some(ext) => ext.to_str().unwrap().to_owned(),
None => return None,
};
match ext.as_ref() {
"pem" => match jwk_from_pem(path.as_path()) {
Ok(jwk) => Some(jwk),
_ => None,
},
_ => None,
}
})
.collect();
let jwks = JWKSet { keys: jwks };
Json(jwks)
}
#[derive(Debug, Serialize)]
struct OidcConfig {
pub jwks_uri: String,
}
#[get("/.well-known/openid-configuration")]
fn oidc_config() -> Json<OidcConfig> {
let config = OidcConfig {
jwks_uri: "https://auth.xeen.dev/oauth2/keys".to_string(),
};
Json(config)
}
#[get("/")]
fn hello() -> Template {
let config = OidcConfig {
jwks_uri: "https://auth.xeen.dev/oauth2/keys".to_string(),
};
Template::render("hello", &config)
}
fn routes() -> Vec<rocket::Route> {
routes![
hello,
oidc_config,
get_keys,
login,
login_form,
]
}
fn main() {
env_logger::init();
rocket::ignite()
.attach(Template::fairing())
.mount("/", routes())
.launch();
}