kanidm/kanidmd/score/src/https/mod.rs
James Hodgkinson 403016b0f8
WebUI Cleanup, icons, Profile Wireframing ()
* account person extend was showing failure when succeeding
* first run on a user profile page, did some other CSS tweaks to the UI
* UI neatening, profile wireframing, robotstxt, PWA manifest
* adding domain_display_name to webmanifest
2022-07-07 18:57:28 +10:00

805 lines
27 KiB
Rust

mod manifest;
mod middleware;
mod oauth2;
mod v1;
use self::manifest::manifest;
use self::middleware::*;
use self::oauth2::*;
use self::v1::*;
use compact_jwt::{Jws, JwsSigner, JwsUnverified, JwsValidator};
use kanidm::actors::v1_read::QueryServerReadV1;
use kanidm::actors::v1_write::QueryServerWriteV1;
use kanidm::config::{ServerRole, TlsConfiguration};
use kanidm::prelude::*;
use kanidm::status::StatusActor;
use kanidm::tracing_tree::TreeMiddleware;
use regex::Regex;
use serde::Serialize;
use std::fs::canonicalize;
use std::path::PathBuf;
use std::str::FromStr;
use tide_compress::CompressMiddleware;
use tide_openssl::TlsListener;
use tracing::{error, info};
use uuid::Uuid;
#[derive(Clone)]
pub struct AppState {
pub status_ref: &'static StatusActor,
pub qe_w_ref: &'static QueryServerWriteV1,
pub qe_r_ref: &'static QueryServerReadV1,
// Store the token management parts.
pub jws_signer: std::sync::Arc<JwsSigner>,
pub jws_validator: std::sync::Arc<JwsValidator>,
}
/// This is for the tide_compression middleware so that we only compress certain content types.
///
/// ```
/// use score::https::compression_content_type_checker;
/// let these_should_match = vec![
/// "application/wasm",
/// "text/json",
/// "text/javascript"
/// ];
/// for test_value in these_should_match {
/// eprintln!("checking {:?}", test_value);
/// assert!(compression_content_type_checker().is_match(test_value));
/// }
/// assert!(compression_content_type_checker().is_match("application/wasm"));
/// let these_should_fail = vec![
/// "image/jpeg",
/// "image/wasm",
/// "text/html",
/// ];
/// for test_value in these_should_fail {
/// eprintln!("checking {:?}", test_value);
/// assert!(!compression_content_type_checker().is_match(test_value));
/// }
/// ```
pub fn compression_content_type_checker() -> Regex {
Regex::new(r"^(?:(image/svg\+xml)|(?:application|text)/(?:css|javascript|json|text|xml|wasm))$")
.expect("regex matcher for tide_compress content-type check failed to compile")
}
pub trait RequestExtensions {
fn get_current_uat(&self) -> Option<String>;
fn get_current_auth_session_id(&self) -> Option<Uuid>;
fn get_url_param(&self, param: &str) -> Result<String, tide::Error>;
fn new_eventid(&self) -> (Uuid, String);
}
impl RequestExtensions for tide::Request<AppState> {
fn get_current_uat(&self) -> Option<String> {
// Contact the QS to get it to validate wtf is up.
// let kref = &self.state().bundy_handle;
// self.session().get::<UserAuthToken>("uat")
self.header(tide::http::headers::AUTHORIZATION)
.and_then(|hv| {
// Get the first header value.
hv.get(0)
})
.and_then(|h| {
// Turn it to a &str, and then check the prefix
h.as_str().strip_prefix("Bearer ")
})
.map(|s| s.to_string())
.or_else(|| self.session().get::<String>("bearer"))
/*
.and_then(|ts| {
// Take the token str and attempt to decrypt
// Attempt to re-inflate a UAT from bytes.
//
// NOTE: UAT expiry validation is performed in event.rs!
let uat: Option<UserAuthToken> = kref.verify(ts).ok();
uat
})
*/
}
fn get_current_auth_session_id(&self) -> Option<Uuid> {
// We see if there is a signed header copy first.
let kref = &self.state().jws_validator;
self.header("X-KANIDM-AUTH-SESSION-ID")
.and_then(|hv| {
// Get the first header value.
hv.get(0)
})
.and_then(|h| {
// Take the token str and attempt to decrypt
// Attempt to re-inflate a uuid from bytes.
JwsUnverified::from_str(h.as_str()).ok()
})
.and_then(|jwsu| {
jwsu.validate(kref)
.map(|jws: Jws<SessionId>| jws.inner.sessionid)
.ok()
})
// If not there, get from the cookie instead.
.or_else(|| self.session().get::<Uuid>("auth-session-id"))
}
fn get_url_param(&self, param: &str) -> Result<String, tide::Error> {
self.param(param).map(str::to_string).map_err(|e| {
error!(?e);
tide::Error::from_str(tide::StatusCode::ImATeapot, "teapot")
})
}
fn new_eventid(&self) -> (Uuid, String) {
let eventid = kanidm::tracing_tree::operation_id().unwrap();
let hv = eventid.as_hyphenated().to_string();
(eventid, hv)
}
}
pub fn to_tide_response<T: Serialize>(
v: Result<T, OperationError>,
hvalue: String,
) -> tide::Result {
match v {
Ok(iv) => {
let mut res = tide::Response::new(200);
tide::Body::from_json(&iv).map(|b| {
res.set_body(b);
res
})
}
Err(e) => {
let mut res = match &e {
OperationError::NotAuthenticated | OperationError::SessionExpired => {
// https://datatracker.ietf.org/doc/html/rfc7235#section-4.1
let mut res = tide::Response::new(tide::StatusCode::Unauthorized);
res.insert_header("WWW-Authenticate", "Bearer");
res
}
OperationError::SystemProtectedObject | OperationError::AccessDenied => {
tide::Response::new(tide::StatusCode::Forbidden)
}
OperationError::NoMatchingEntries => {
tide::Response::new(tide::StatusCode::NotFound)
}
OperationError::PasswordQuality(_)
| OperationError::EmptyRequest
| OperationError::SchemaViolation(_) => {
tide::Response::new(tide::StatusCode::BadRequest)
}
_ => tide::Response::new(tide::StatusCode::InternalServerError),
};
tide::Body::from_json(&e).map(|b| {
res.set_body(b);
res
})
}
}
.map(|mut res| {
res.insert_header("X-KANIDM-OPID", hvalue);
res
})
}
/// Returns a generic robots.txt
async fn robots_txt(_req: tide::Request<AppState>) -> tide::Result {
let mut res = tide::Response::new(200);
res.set_content_type("text/plain");
res.set_body(
r#"
User-agent: *
Disallow: /
"#,
);
Ok(res)
}
/// The web UI at / for Kanidm
async fn index_view(req: tide::Request<AppState>) -> tide::Result {
let mut res = tide::Response::new(200);
let (eventid, hvalue) = req.new_eventid();
let domain_display_name = req.state().qe_r_ref.get_domain_display_name(eventid).await;
res.insert_header("X-KANIDM-OPID", hvalue);
res.set_content_type("text/html;charset=utf-8");
res.set_body(format!(r#"
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="theme-color" content="white" />
<meta name="viewport" content="width=device-width" />
<title>{}</title>
<link rel="icon" href="/pkg/img/favicon.png" />
<link rel="manifest" href="/manifest.webmanifest" />
<link rel="apple-touch-icon" href="/pkg/img/logo-256.png" />
<link rel="apple-touch-icon" sizes="180x180" href="/pkg/img/logo-180.png" />
<link rel="apple-touch-icon" sizes="192x192" href="/pkg/img/logo-192.png" />
<link rel="apple-touch-icon" sizes="512x512" href="/pkg/img/logo-square.svg" />
<link rel="stylesheet" href="/pkg/external/bootstrap.min.css" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC"/>
<link rel="stylesheet" href="/pkg/style.css"/>
<script src="/pkg/external/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM"></script>
<script type="module" type="text/javascript" src="/pkg/wasmloader.js" integrity="sha384-==WASMHASH=="></script>
</head>
<body class="flex-column d-flex h-100">
<main class="flex-shrink-0 form-signin">
<center>
<img src="/pkg/img/logo-square.svg" alt="Kanidm" class="kanidm_logo"/>
<h3>Kanidm is loading, please wait... </h3>
</center>
</main>
<footer class="footer mt-auto py-3 bg-light text-end">
<div class="container">
<span class="text-muted">Powered by <a href="https://kanidm.com">Kanidm</a></span>
</div>
</footer>
</body>
</html>"#, domain_display_name.as_str()
)
);
Ok(res)
}
#[derive(Default)]
struct NoCacheMiddleware;
#[async_trait::async_trait]
impl<State: Clone + Send + Sync + 'static> tide::Middleware<State> for NoCacheMiddleware {
async fn handle(
&self,
request: tide::Request<State>,
next: tide::Next<'_, State>,
) -> tide::Result {
let mut response = next.run(request).await;
response.insert_header("Cache-Control", "no-store, max-age=0");
response.insert_header("Pragma", "no-cache");
Ok(response)
}
}
#[derive(Default)]
struct CacheableMiddleware;
#[async_trait::async_trait]
impl<State: Clone + Send + Sync + 'static> tide::Middleware<State> for CacheableMiddleware {
async fn handle(
&self,
request: tide::Request<State>,
next: tide::Next<'_, State>,
) -> tide::Result {
let mut response = next.run(request).await;
response.insert_header("Cache-Control", "max-age=60,must-revalidate,private");
Ok(response)
}
}
#[derive(Default)]
/// Sets Cache-Control headers on static content endpoints
struct StaticContentMiddleware;
#[async_trait::async_trait]
impl<State: Clone + Send + Sync + 'static> tide::Middleware<State> for StaticContentMiddleware {
async fn handle(
&self,
request: tide::Request<State>,
next: tide::Next<'_, State>,
) -> tide::Result {
let mut response = next.run(request).await;
response.insert_header("Cache-Control", "max-age=3600,private");
Ok(response)
}
}
#[derive(Default)]
/// Adds the folloing headers to responses
/// - x-frame-options
/// - x-content-type-options
/// - cross-origin-resource-policy
/// - cross-origin-embedder-policy
/// - cross-origin-opener-policy
struct StrictResponseMiddleware;
#[async_trait::async_trait]
impl<State: Clone + Send + Sync + 'static> tide::Middleware<State> for StrictResponseMiddleware {
async fn handle(
&self,
request: tide::Request<State>,
next: tide::Next<'_, State>,
) -> tide::Result {
let mut response = next.run(request).await;
response.insert_header("cross-origin-embedder-policy", "require-corp");
response.insert_header("cross-origin-opener-policy", "same-origin");
response.insert_header("cross-origin-resource-policy", "same-origin");
response.insert_header("x-content-type-options", "nosniff");
response.insert_header("x-frame-options", "deny");
Ok(response)
}
}
struct StrictRequestMiddleware;
impl Default for StrictRequestMiddleware {
fn default() -> Self {
StrictRequestMiddleware {}
}
}
#[async_trait::async_trait]
impl<State: Clone + Send + Sync + 'static> tide::Middleware<State> for StrictRequestMiddleware {
async fn handle(
&self,
request: tide::Request<State>,
next: tide::Next<'_, State>,
) -> tide::Result {
let proceed = request
.header("sec-fetch-site")
.map(|hv| {
matches!(hv.as_str(), "same-origin" | "same-site" | "none")
|| (request.header("sec-fetch-mode").map(|v| v.as_str()) == Some("navigate")
&& request.method() == tide::http::Method::Get
&& request.header("sec-fetch-dest").map(|v| v.as_str()) != Some("object")
&& request.header("sec-fetch-dest").map(|v| v.as_str()) != Some("embed"))
})
.unwrap_or(true);
if proceed {
Ok(next.run(request).await)
} else {
Err(tide::Error::from_str(
tide::StatusCode::MethodNotAllowed,
"StrictRequestViolation",
))
}
}
}
pub fn generate_integrity_hash(filename: String) -> Result<String, String> {
let wasm_filepath = PathBuf::from(filename);
match wasm_filepath.exists() {
false => {
return Err(format!(
"Can't find {:?} to generate file hash",
&wasm_filepath
));
}
true => {
let filecontents = match std::fs::read(&wasm_filepath) {
Ok(value) => value,
Err(error) => {
return Err(format!(
"Failed to read {:?}, skipping: {:?}",
wasm_filepath, error
));
}
};
let shasum =
openssl::hash::hash(openssl::hash::MessageDigest::sha384(), &filecontents).unwrap();
Ok(format!("{}", openssl::base64::encode_block(&shasum)))
}
}
}
// TODO: Add request limits.
pub fn create_https_server(
address: String,
// opt_tls_params: Option<SslAcceptorBuilder>,
opt_tls_params: Option<&TlsConfiguration>,
role: ServerRole,
cookie_key: &[u8; 32],
jws_signer: JwsSigner,
status_ref: &'static StatusActor,
qe_w_ref: &'static QueryServerWriteV1,
qe_r_ref: &'static QueryServerReadV1,
) -> Result<(), ()> {
let jws_validator = jws_signer.get_validator().map_err(|e| {
error!(?e, "Failed to get jws validator");
})?;
let jws_validator = std::sync::Arc::new(jws_validator);
let jws_signer = std::sync::Arc::new(jws_signer);
let mut tserver = tide::Server::with_state(AppState {
status_ref,
qe_w_ref,
qe_r_ref,
jws_signer,
jws_validator,
});
// tide::log::with_level(tide::log::LevelFilter::Debug);
// Add middleware?
tserver.with(TreeMiddleware::with_stdout());
// tserver.with(tide::log::LogMiddleware::new());
// We do not force a session ttl, because we validate this elsewhere in usage.
tserver.with(
// We do not force a session ttl, because we validate this elsewhere in usage.
tide::sessions::SessionMiddleware::new(tide::sessions::MemoryStore::new(), cookie_key)
.with_cookie_name("kanidm-session")
.with_same_site_policy(tide::http::cookies::SameSite::Strict),
);
tserver.with(StrictResponseMiddleware::default());
// Add routes
// ==== static content routes that have a longer cache policy.
// If we are no-ui, we remove this.
if !matches!(role, ServerRole::WriteReplicaNoUI) {
let pkg_path = PathBuf::from(env!("KANIDM_WEB_UI_PKG_PATH"));
if !pkg_path.exists() {
eprintln!(
"Couldn't find Web UI package path: ({}), quitting.",
env!("KANIDM_WEB_UI_PKG_PATH")
);
std::process::exit(1);
}
info!("Web UI package path: {:?}", canonicalize(pkg_path).unwrap());
/*
Let's build a compression middleware!
The threat of the TLS BREACH attack [1] was considered as part of adding
the CompressMiddleware configuration.
The attack targets secrets compressed and encrypted in flight with the intent
to infer their content.
This is not a concern for the paths covered by this configuration
( /, /ui/<and all sub-paths>, /pkg/<and all sub-paths> ),
as they're all static content with no secrets in transit - all that data should
come from Kanidm's REST API, which is on a different path and not covered by
the compression middleware.
[1] - https://resources.infosecinstitute.com/topic/the-breach-attack/
*/
let compress_middleware = CompressMiddleware::builder()
.threshold(1024)
.content_type_check(Some(compression_content_type_checker()))
.build();
let mut static_tserver = tserver.at("");
static_tserver.with(StaticContentMiddleware::default());
static_tserver.with(UIContentSecurityPolicyResponseMiddleware::new(
generate_integrity_hash(env!("KANIDM_WEB_UI_PKG_PATH").to_owned() + "/wasmloader.js")
.unwrap(),
));
// The compression middleware needs to be the last one added before routes
static_tserver.with(compress_middleware.clone());
static_tserver.at("/").get(index_view);
static_tserver.at("/robots.txt").get(robots_txt);
static_tserver.at("/manifest.webmanifest").get(manifest);
static_tserver.at("/ui/").get(index_view);
static_tserver.at("/ui/*").get(index_view);
let mut static_dir_tserver = tserver.at("");
static_dir_tserver.with(StaticContentMiddleware::default());
// The compression middleware needs to be the last one added before routes
static_dir_tserver.with(compress_middleware);
static_dir_tserver
.at("/pkg")
.serve_dir(env!("KANIDM_WEB_UI_PKG_PATH"))
.map_err(|e| {
error!(
"Failed to serve pkg dir {} -> {:?}",
env!("KANIDM_WEB_UI_PKG_PATH"),
e
);
})?;
};
// ==== Some routes can be cached - these are here:
let mut tserver_cacheable = tserver.at("");
tserver_cacheable.with(CacheableMiddleware::default());
let mut account_route_cacheable = tserver_cacheable.at("/v1/account");
// We allow caching of the radius token.
account_route_cacheable
.at("/:id/_radius/_token")
.get(account_get_id_radius_token);
// We allow clients to cache the unix token.
account_route_cacheable
.at("/:id/_unix/_token")
.get(account_get_id_unix_token);
// ==== These routes can not be cached
let mut appserver = tserver.at("");
appserver.with(NoCacheMiddleware::default());
// let mut well_known = appserver.at("/.well-known");
appserver.at("/status").get(self::status);
// == oauth endpoints.
let mut oauth2_process = appserver.at("/oauth2");
// ⚠️ ⚠️ WARNING ⚠️ ⚠️
// IF YOU CHANGE THESE VALUES YOU MUST UPDATE OIDC DISCOVERY URLS
oauth2_process
.at("/authorise")
.post(oauth2_authorise_post)
.get(oauth2_authorise_get);
// ⚠️ ⚠️ WARNING ⚠️ ⚠️
// IF YOU CHANGE THESE VALUES YOU MUST UPDATE OIDC DISCOVERY URLS
oauth2_process
.at("/authorise/permit")
.post(oauth2_authorise_permit_post)
.get(oauth2_authorise_permit_get);
// ⚠️ ⚠️ WARNING ⚠️ ⚠️
// IF YOU CHANGE THESE VALUES YOU MUST UPDATE OIDC DISCOVERY URLS
oauth2_process
.at("/authorise/reject")
.post(oauth2_authorise_reject_post)
.get(oauth2_authorise_reject_get);
// ⚠️ ⚠️ WARNING ⚠️ ⚠️
// IF YOU CHANGE THESE VALUES YOU MUST UPDATE OIDC DISCOVERY URLS
oauth2_process.at("/token").post(oauth2_token_post);
// ⚠️ ⚠️ WARNING ⚠️ ⚠️
// IF YOU CHANGE THESE VALUES YOU MUST UPDATE OIDC DISCOVERY URLS
oauth2_process
.at("/token/introspect")
.post(oauth2_token_introspect_post);
let mut openid_process = appserver.at("/oauth2/openid");
// ⚠️ ⚠️ WARNING ⚠️ ⚠️
// IF YOU CHANGE THESE VALUES YOU MUST UPDATE OIDC DISCOVERY URLS
openid_process
.at("/:client_id/.well-known/openid-configuration")
.get(oauth2_openid_discovery_get);
// ⚠️ ⚠️ WARNING ⚠️ ⚠️
// IF YOU CHANGE THESE VALUES YOU MUST UPDATE OIDC DISCOVERY URLS
openid_process
.at("/:client_id/userinfo")
.get(oauth2_openid_userinfo_get);
// ⚠️ ⚠️ WARNING ⚠️ ⚠️
// IF YOU CHANGE THESE VALUES YOU MUST UPDATE OIDC DISCOVERY URLS
openid_process
.at("/:client_id/public_key.jwk")
.get(oauth2_openid_publickey_get);
let mut raw_route = appserver.at("/v1/raw");
raw_route.at("/create").post(create);
raw_route.at("/modify").post(modify);
raw_route.at("/delete").post(delete);
raw_route.at("/search").post(search);
appserver.at("/v1/auth").post(auth);
appserver.at("/v1/auth/valid").get(auth_valid);
let mut schema_route = appserver.at("/v1/schema");
schema_route.at("/").get(schema_get);
schema_route
.at("/attributetype")
.get(schema_attributetype_get)
.post(do_nothing);
schema_route
.at("/attributetype/:id")
.get(schema_attributetype_get_id)
.put(do_nothing)
.patch(do_nothing);
schema_route
.at("/classtype")
.get(schema_classtype_get)
.post(do_nothing);
schema_route
.at("/classtype/:id")
.get(schema_classtype_get_id)
.put(do_nothing)
.patch(do_nothing);
let mut oauth2_route = appserver.at("/v1/oauth2");
oauth2_route.at("/").get(oauth2_get);
oauth2_route.at("/_basic").post(oauth2_basic_post);
oauth2_route
.at("/:id")
.get(oauth2_id_get)
// It's not really possible to replace this wholesale.
// .put(oauth2_id_put)
.patch(oauth2_id_patch)
.delete(oauth2_id_delete);
oauth2_route
.at("/:id/_scopemap/:group")
.post(oauth2_id_scopemap_post)
.delete(oauth2_id_scopemap_delete);
let mut self_route = appserver.at("/v1/self");
self_route.at("/").get(whoami);
self_route.at("/_attr/:attr").get(do_nothing);
self_route.at("/_credential").get(do_nothing);
self_route
.at("/_credential/primary/set_password")
.post(idm_account_set_password);
self_route.at("/_credential/:cid/_lock").get(do_nothing);
self_route
.at("/_radius")
.get(do_nothing)
.delete(do_nothing)
.post(do_nothing);
self_route.at("/_radius/_config").post(do_nothing);
self_route.at("/_radius/_config/:token").get(do_nothing);
self_route
.at("/_radius/_config/:token/apple")
.get(do_nothing);
let mut person_route = appserver.at("/v1/person");
person_route.at("/").get(person_get).post(person_post);
person_route.at("/:id").get(person_id_get);
let mut account_route = appserver.at("/v1/account");
account_route.at("/").get(account_get).post(account_post);
account_route
.at("/:id")
.get(account_id_get)
.delete(account_id_delete);
account_route
.at("/:id/_attr/:attr")
.get(account_id_get_attr)
.put(account_id_put_attr)
.post(account_id_post_attr)
.delete(account_id_delete_attr);
account_route
.at("/:id/_person/_extend")
.post(account_post_id_person_extend);
account_route
.at("/:id/_person/_set")
.post(account_post_id_person_set);
account_route.at("/:id/_lock").get(do_nothing);
account_route.at("/:id/_credential").get(do_nothing);
account_route
.at("/:id/_credential/_status")
.get(account_get_id_credential_status);
account_route
.at("/:id/_credential/primary")
.put(account_put_id_credential_primary);
account_route
.at("/:id/_credential/:cid/_lock")
.get(do_nothing);
account_route
.at("/:id/_credential/:cid/backup_code")
.get(account_get_backup_code);
// .post(account_post_backup_code_regenerate) // use "/:id/_credential/primary" instead
// .delete(account_delete_backup_code); // same as above
account_route
.at("/:id/_credential/_update")
.get(account_get_id_credential_update);
account_route
.at("/:id/_credential/_update_intent")
.get(account_get_id_credential_update_intent);
account_route
.at("/:id/_credential/_update_intent/:ttl")
.get(account_get_id_credential_update_intent);
account_route
.at("/:id/_ssh_pubkeys")
.get(account_get_id_ssh_pubkeys)
.post(account_post_id_ssh_pubkey);
account_route
.at("/:id/_ssh_pubkeys/:tag")
.get(account_get_id_ssh_pubkey_tag)
.delete(account_delete_id_ssh_pubkey_tag);
account_route
.at("/:id/_radius")
.get(account_get_id_radius)
.post(account_post_id_radius_regenerate)
.delete(account_delete_id_radius);
account_route.at("/:id/_unix").post(account_post_id_unix);
account_route
.at("/:id/_unix/_auth")
.post(account_post_id_unix_auth);
account_route
.at("/:id/_unix/_credential")
.put(account_put_id_unix_credential)
.delete(account_delete_id_unix_credential);
let mut cred_route = appserver.at("/v1/credential");
cred_route
.at("/_exchange_intent")
.post(credential_update_exchange_intent);
cred_route.at("/_status").post(credential_update_status);
cred_route.at("/_update").post(credential_update_update);
cred_route.at("/_commit").post(credential_update_commit);
let mut group_route = appserver.at("/v1/group");
group_route.at("/").get(group_get).post(group_post);
group_route
.at("/:id")
.get(group_id_get)
.delete(group_id_delete);
group_route
.at("/:id/_attr/:attr")
.delete(group_id_delete_attr)
.get(group_id_get_attr)
.put(group_id_put_attr)
.post(group_id_post_attr);
group_route.at("/:id/_unix").post(group_post_id_unix);
group_route
.at("/:id/_unix/_token")
.get(group_get_id_unix_token);
let mut domain_route = appserver.at("/v1/domain");
domain_route.at("/").get(domain_get);
domain_route
.at("/_attr/:attr")
.get(domain_get_attr)
.put(domain_put_attr)
.delete(domain_delete_attr);
let mut recycle_route = appserver.at("/v1/recycle_bin");
recycle_route.at("/").get(recycle_bin_get);
recycle_route.at("/:id").get(recycle_bin_id_get);
recycle_route
.at("/:id/_revive")
.post(recycle_bin_revive_id_post);
let mut accessprof_route = appserver.at("/v1/access_profile");
accessprof_route.at("/").get(do_nothing);
accessprof_route.at("/:id").get(do_nothing);
accessprof_route.at("/:id/_attr/:attr").get(do_nothing);
// === End routes
// Create listener?
match opt_tls_params {
Some(tls_param) => {
let tlsl = TlsListener::build()
.addrs(&address)
.cert(&tls_param.chain)
.key(&tls_param.key)
.finish()
.map_err(|e| {
error!("Failed to build TLS Listener -> {:?}", e);
})?;
/*
let x = Box::new(tls_param.build());
let x_ref = Box::leak(x);
let tlsl = TlsListener::new(address, x_ref);
*/
tokio::spawn(async move {
if let Err(e) = tserver.listen(tlsl).await {
error!(
"Failed to start server listener on address {:?} -> {:?}",
&address, e
);
}
});
}
None => {
// Create without https
tokio::spawn(async move {
if let Err(e) = tserver.listen(&address).await {
error!(
"Failed to start server listener on address {:?} -> {:?}",
&address, e,
);
}
});
}
};
Ok(())
}