auth-server/src/main.rs

202 lines
5.1 KiB
Rust
Raw Normal View History

2019-08-03 18:29:10 +00:00
#![deny(warnings)]
2020-02-29 15:48:07 +00:00
#![feature(proc_macro_hygiene)]
#[macro_use] extern crate rocket;
2020-02-23 19:37:30 +00:00
use log::info;
use serde_derive::Serialize;
2019-08-11 10:37:54 +00:00
use std::env;
use std::fs;
use std::path::Path;
2019-08-03 18:15:52 +00:00
use biscuit::{
Empty,
};
use biscuit::jwa::{
SignatureAlgorithm,
Algorithm,
};
use biscuit::jwk::{
RSAKeyParameters,
CommonParameters,
AlgorithmParameters,
JWK,
JWKSet,
};
use num::BigUint;
use openssl::rsa::Rsa;
2019-08-03 18:15:52 +00:00
use ldap3::{ LdapConn, Scope, SearchEntry };
2020-02-29 15:48:07 +00:00
use rocket::request::Form;
use rocket_contrib::json::Json;
2020-02-29 18:45:32 +00:00
use rocket_contrib::templates::Template;
2019-08-03 18:15:52 +00:00
#[derive(Debug)]
struct BasicAuthentication {
pub username: String,
pub password: String,
}
#[derive(Debug)]
pub enum AuthError {
Parse,
Decode,
2019-08-03 18:29:10 +00:00
LdapBind,
2019-08-11 10:37:54 +00:00
LdapConfig,
2019-08-03 18:29:10 +00:00
LdapConnection,
LdapSearch,
2019-08-03 18:15:52 +00:00
}
#[derive(Debug)]
struct LdapUser {
pub dn: String,
pub groups: Vec<String>,
2019-08-03 18:29:10 +00:00
pub mail: Vec<String>,
2019-08-03 18:15:52 +00:00
pub services: Vec<String>,
}
fn auth_user(auth: &BasicAuthentication) -> Result<LdapUser, AuthError> {
2019-08-11 10:37:54 +00:00
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) {
2019-08-03 18:15:52 +00:00
Ok(conn) => conn,
2019-08-03 18:29:10 +00:00
Err(_err) => return Err(AuthError::LdapConnection),
2019-08-03 18:15:52 +00:00
};
let base = format!("uid={},ou=people,dc=xeentech,dc=com", auth.username);
match ldap.simple_bind(&base, &auth.password).unwrap().success() {
2019-08-03 18:29:10 +00:00
Ok(_ldap) => println!("Connected and authenticated"),
Err(_err) => return Err(AuthError::LdapBind),
2019-08-03 18:15:52 +00:00
};
let filter = format!("(uid={})", auth.username);
let s = match ldap.search(&base, Scope::Subtree, &filter, vec!["mail", "enabledService", "memberOf"]) {
2019-08-03 18:15:52 +00:00
Ok(result) => {
let (rs, _) = result.success().unwrap();
rs
},
2019-08-03 18:29:10 +00:00
Err(_err) => return Err(AuthError::LdapSearch),
2019-08-03 18:15:52 +00:00
};
// 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(),
2019-08-03 18:29:10 +00:00
None => [].to_vec(),
2019-08-03 18:15:52 +00:00
};
let groups = match se.attrs.get("memberOf") {
Some(groups) => groups.to_vec(),
None => [].to_vec(),
};
2019-08-03 18:15:52 +00:00
2020-02-29 15:48:07 +00:00
info!("Authentication success for {:?}", base);
2019-08-03 18:15:52 +00:00
Ok(LdapUser {
dn: base,
groups: groups,
2019-08-03 18:29:10 +00:00
mail: mail,
2019-08-03 18:15:52 +00:00
services: services,
})
}
2020-02-29 15:48:07 +00:00
#[derive(FromForm)]
struct LoginData {
username: String,
password: String,
}
2019-08-03 18:15:52 +00:00
2020-02-29 15:48:07 +00:00
#[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(),
2019-08-03 18:29:10 +00:00
};
2020-02-29 15:48:07 +00:00
match auth_user(&auth) {
Ok(ldap_user) => format!("OK! {:?}", ldap_user),
_ => format!("Bad :("),
}
2019-08-03 18:15:52 +00:00
}
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(),
})
}
2020-02-29 15:48:07 +00:00
#[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 };
2020-02-29 15:48:07 +00:00
Json(jwks)
}
#[derive(Debug, Serialize)]
struct OidcConfig {
pub jwks_uri: String,
}
2020-02-29 15:48:07 +00:00
#[get("/.well-known/openid-configuration")]
fn oidc_config() -> Json<OidcConfig> {
let config = OidcConfig {
jwks_uri: "https://auth.xeen.dev/oauth2/keys".to_string(),
};
2020-02-29 15:48:07 +00:00
Json(config)
}
2020-02-29 15:48:07 +00:00
#[get("/")]
2020-02-29 18:45:32 +00:00
fn hello() -> Template {
let config = OidcConfig {
jwks_uri: "https://auth.xeen.dev/oauth2/keys".to_string(),
};
Template::render("hello", &config)
2019-08-03 18:15:52 +00:00
}
2020-02-29 15:48:07 +00:00
fn routes() -> Vec<rocket::Route> {
routes![
hello,
oidc_config,
get_keys,
login,
]
2019-08-03 18:15:52 +00:00
}
fn main() {
2020-02-23 19:04:07 +00:00
env_logger::init();
2020-02-29 18:45:32 +00:00
rocket::ignite()
.attach(Template::fairing())
.mount("/", routes())
.launch();
2019-08-03 18:15:52 +00:00
}