kanidm/kanidmd/src/lib/core/mod.rs
Firstyear 8cfa8f3f95
12 totp (#201)
Implements #12, TOTP. This adds support for TOTP to the api and server, with server side token generation, authentication and the correct URI for encoding into QR codes for client token addition. Some extra measures have been taken such as in the stepped auth to always notify on the success or failure of the TOTP first (regardless of order) to prevent PW bruteforce attacks.
2020-04-10 15:50:45 +10:00

1664 lines
57 KiB
Rust

// use actix_files as fs;
use actix::prelude::*;
use actix_session::{CookieSession, Session};
use actix_web::web::{self, Data, HttpResponse, Json, Path};
use actix_web::{cookie, error, middleware, App, HttpServer};
use std::sync::Arc;
use time::Duration;
use crate::config::Configuration;
// SearchResult
use crate::actors::v1_read::QueryServerReadV1;
use crate::actors::v1_read::{
AuthMessage, IdmAccountUnixAuthMessage, InternalRadiusReadMessage,
InternalRadiusTokenReadMessage, InternalSearchMessage, InternalSearchRecycledMessage,
InternalSshKeyReadMessage, InternalSshKeyTagReadMessage, InternalUnixGroupTokenReadMessage,
InternalUnixUserTokenReadMessage, SearchMessage, WhoamiMessage,
};
use crate::actors::v1_write::QueryServerWriteV1;
use crate::actors::v1_write::{
AppendAttributeMessage, CreateMessage, DeleteMessage, IdmAccountPersonExtendMessage,
IdmAccountSetPasswordMessage, IdmAccountUnixExtendMessage, IdmAccountUnixSetCredMessage,
IdmGroupUnixExtendMessage, InternalCredentialSetMessage, InternalDeleteMessage,
InternalRegenerateRadiusMessage, InternalSshKeyCreateMessage, ModifyMessage,
PurgeAttributeMessage, RemoveAttributeValueMessage, ReviveRecycledMessage, SetAttributeMessage,
};
use crate::async_log;
use crate::audit::AuditScope;
use crate::be::{Backend, BackendTransaction};
use crate::crypto::setup_tls;
use crate::filter::{Filter, FilterInvalid};
use crate::idm::server::IdmServer;
use crate::interval::IntervalActor;
use crate::schema::Schema;
use crate::schema::SchemaTransaction;
use crate::server::QueryServer;
use crate::utils::duration_from_epoch_now;
use crate::value::PartialValue;
use kanidm_proto::v1::Entry as ProtoEntry;
use kanidm_proto::v1::OperationError;
use kanidm_proto::v1::{
AccountUnixExtend, AuthRequest, AuthState, CreateRequest, DeleteRequest, GroupUnixExtend,
ModifyRequest, SearchRequest, SetCredentialRequest, SingleStringRequest, UserAuthToken,
};
use uuid::Uuid;
struct AppState {
qe_r: Addr<QueryServerReadV1>,
qe_w: Addr<QueryServerWriteV1>,
}
fn get_current_user(session: &Session) -> Option<UserAuthToken> {
match session.get::<UserAuthToken>("uat") {
Ok(maybe_uat) => maybe_uat,
Err(_) => None,
}
}
fn operation_error_to_response(e: OperationError) -> HttpResponse {
match e {
OperationError::NotAuthenticated => HttpResponse::Unauthorized().json(e),
OperationError::AccessDenied | OperationError::SystemProtectedObject => {
HttpResponse::Forbidden().json(e)
}
OperationError::EmptyRequest
| OperationError::NoMatchingEntries
| OperationError::SchemaViolation(_) => HttpResponse::BadRequest().json(e),
_ => HttpResponse::InternalServerError().json(e),
}
}
macro_rules! json_event_post {
($req:expr, $session:expr, $message_type:ty, $dest:expr) => {{
// Get auth if any?
let uat = get_current_user(&$session);
// Send to the db for handling
// combine request + uat -> message.
let m_obj = <$message_type>::new(uat, $req);
match $dest.send(m_obj).await {
Ok(Ok(r)) => HttpResponse::Ok().json(r),
Ok(Err(e)) => operation_error_to_response(e),
Err(_) => HttpResponse::InternalServerError().json("mailbox failure"),
}
}};
}
macro_rules! json_event_get {
($session:expr, $state:expr, $message_type:ty) => {{
// Get current auth data - remember, the QS checks if the
// none/some is okay, because it's too hard to make it work here
// with all the async parts.
let uat = get_current_user(&$session);
// New event, feed current auth data from the token to it.
let obj = <$message_type>::new(uat);
match $state.qe_r.send(obj).await {
Ok(Ok(r)) => HttpResponse::Ok().json(r),
Ok(Err(e)) => operation_error_to_response(e),
Err(_) => HttpResponse::InternalServerError().json("mailbox failure"),
}
}};
}
// Handle the various end points we need to expose
async fn create(
(req, session, state): (Json<CreateRequest>, Session, Data<AppState>),
) -> HttpResponse {
json_event_post!(req.into_inner(), session, CreateMessage, state.qe_w)
}
async fn modify(
(req, session, state): (Json<ModifyRequest>, Session, Data<AppState>),
) -> HttpResponse {
json_event_post!(req.into_inner(), session, ModifyMessage, state.qe_w)
}
async fn delete(
(req, session, state): (Json<DeleteRequest>, Session, Data<AppState>),
) -> HttpResponse {
json_event_post!(req.into_inner(), session, DeleteMessage, state.qe_w)
}
async fn search(
(req, session, state): (Json<SearchRequest>, Session, Data<AppState>),
) -> HttpResponse {
json_event_post!(req.into_inner(), session, SearchMessage, state.qe_r)
}
async fn whoami((session, state): (Session, Data<AppState>)) -> HttpResponse {
json_event_get!(session, state, WhoamiMessage)
}
// =============== REST generics ========================
async fn json_rest_event_get(
session: Session,
state: Data<AppState>,
filter: Filter<FilterInvalid>,
attrs: Option<Vec<String>>,
) -> HttpResponse {
let uat = get_current_user(&session);
let obj = InternalSearchMessage { uat, filter, attrs };
match state.qe_r.send(obj).await {
Ok(Ok(r)) => HttpResponse::Ok().json(r),
Ok(Err(e)) => operation_error_to_response(e),
Err(_) => HttpResponse::InternalServerError().json("mailbox failure"),
}
}
async fn json_rest_event_get_id(
path: Path<String>,
session: Session,
state: Data<AppState>,
filter: Filter<FilterInvalid>,
attrs: Option<Vec<String>>,
) -> HttpResponse {
let uat = get_current_user(&session);
let filter = Filter::join_parts_and(filter, filter_all!(f_id(path.as_str())));
let obj = InternalSearchMessage { uat, filter, attrs };
match state.qe_r.send(obj).await {
Ok(Ok(mut r)) => HttpResponse::Ok().json(r.pop()),
Ok(Err(e)) => operation_error_to_response(e),
Err(_) => HttpResponse::InternalServerError().json("mailbox failure"),
}
}
async fn json_rest_event_delete_id(
path: Path<String>,
session: Session,
state: Data<AppState>,
filter: Filter<FilterInvalid>,
) -> HttpResponse {
let uat = get_current_user(&session);
let filter = Filter::join_parts_and(filter, filter_all!(f_id(path.as_str())));
let obj = InternalDeleteMessage { uat, filter };
match state.qe_w.send(obj).await {
Ok(Ok(r)) => HttpResponse::Ok().json(r),
Ok(Err(e)) => operation_error_to_response(e),
Err(_) => HttpResponse::InternalServerError().json("mailbox failure"),
}
}
async fn json_rest_event_get_id_attr(
path: Path<(String, String)>,
session: Session,
state: Data<AppState>,
filter: Filter<FilterInvalid>,
) -> HttpResponse {
let (id, attr) = path.into_inner();
let uat = get_current_user(&session);
let filter = Filter::join_parts_and(filter, filter_all!(f_id(id.as_str())));
let obj = InternalSearchMessage {
uat,
filter,
attrs: Some(vec![attr.clone()]),
};
match state.qe_r.send(obj).await {
Ok(Ok(mut event_result)) => {
// TODO: Check this only has len 1, even though that satte should be impossible.
// Only get one result
let r = event_result.pop().and_then(|mut e| {
// Only get the attribute as requested.
e.attrs.remove(&attr)
});
debug!("final json result {:?}", r);
// Only send back the first result, or None
HttpResponse::Ok().json(r)
}
Ok(Err(e)) => operation_error_to_response(e),
Err(_) => HttpResponse::InternalServerError().json("mailbox failure"),
}
}
async fn json_rest_event_post(
mut obj: ProtoEntry,
session: Session,
state: Data<AppState>,
classes: Vec<String>,
) -> HttpResponse {
// Read the json from the wire.
let uat = get_current_user(&session);
obj.attrs.insert("class".to_string(), classes);
let m_obj = CreateMessage::new_entry(uat, obj);
match state.qe_w.send(m_obj).await {
Ok(Ok(r)) => HttpResponse::Ok().json(r),
Ok(Err(e)) => operation_error_to_response(e),
Err(_) => HttpResponse::InternalServerError().json("mailbox failure"),
}
}
async fn json_rest_event_post_id_attr(
path: Path<(String, String)>,
session: Session,
state: Data<AppState>,
filter: Filter<FilterInvalid>,
values: Vec<String>,
) -> HttpResponse {
let uat = get_current_user(&session);
let (id, attr) = path.into_inner();
let m_obj = AppendAttributeMessage {
uat,
uuid_or_name: id,
attr,
values,
filter,
};
// Add a msg here
match state.qe_w.send(m_obj).await {
Ok(Ok(r)) => HttpResponse::Ok().json(r),
Ok(Err(e)) => operation_error_to_response(e),
Err(_) => HttpResponse::InternalServerError().json("mailbox failure"),
}
}
async fn json_rest_event_put_id_attr(
path: Path<(String, String)>,
session: Session,
state: Data<AppState>,
filter: Filter<FilterInvalid>,
values: Vec<String>,
) -> HttpResponse {
let uat = get_current_user(&session);
let (id, attr) = path.into_inner();
let m_obj = SetAttributeMessage {
uat,
uuid_or_name: id,
attr,
values,
filter,
};
match state.qe_w.send(m_obj).await {
Ok(Ok(r)) => HttpResponse::Ok().json(r),
Ok(Err(e)) => operation_error_to_response(e),
Err(_) => HttpResponse::InternalServerError().json("mailbox failure"),
}
}
async fn json_rest_event_delete_id_attr(
path: Path<(String, String)>,
session: Session,
state: Data<AppState>,
filter: Filter<FilterInvalid>,
) -> HttpResponse {
let uat = get_current_user(&session);
let (id, attr) = path.into_inner();
// TODO: Attempt to get an option Vec<String> here?
let obj = PurgeAttributeMessage {
uat,
uuid_or_name: id,
attr,
filter,
};
match state.qe_w.send(obj).await {
Ok(Ok(r)) => HttpResponse::Ok().json(r),
Ok(Err(e)) => operation_error_to_response(e),
Err(_) => HttpResponse::InternalServerError().json("mailbox failure"),
}
}
async fn json_rest_event_credential_put(
id: String,
cred_id: Option<String>,
session: Session,
state: Data<AppState>,
obj: SetCredentialRequest,
) -> HttpResponse {
let uat = get_current_user(&session);
let m_obj = InternalCredentialSetMessage {
uat,
uuid_or_name: id,
appid: cred_id,
sac: obj,
};
match state.qe_w.send(m_obj).await {
Ok(Ok(r)) => HttpResponse::Ok().json(r),
Ok(Err(e)) => operation_error_to_response(e),
Err(_) => HttpResponse::InternalServerError().json("mailbox failure"),
}
}
// Okay, so a put normally needs
// * filter of what we are working on (id + class)
// * a BTreeMap<String, Vec<String>> that we turn into a modlist.
//
// OR
// * filter of what we are working on (id + class)
// * a Vec<String> that we are changing
// * the attr name (as a param to this in path)
//
// json_rest_event_put_id(path, req, state
async fn schema_get((session, state): (Session, Data<AppState>)) -> HttpResponse {
// NOTE: This is filter_all, because from_internal_message will still do the alterations
// needed to make it safe. This is needed because there may be aci's that block access
// to the recycle/ts types in the filter, and we need the aci to only eval on this
// part of the filter!
let filter = filter_all!(f_or!([
f_eq("class", PartialValue::new_class("attributetype")),
f_eq("class", PartialValue::new_class("classtype"))
]));
json_rest_event_get(session, state, filter, None).await
}
async fn schema_attributetype_get((session, state): (Session, Data<AppState>)) -> HttpResponse {
let filter = filter_all!(f_eq("class", PartialValue::new_class("attributetype")));
json_rest_event_get(session, state, filter, None).await
}
async fn schema_attributetype_get_id(
(path, session, state): (Path<String>, Session, Data<AppState>),
) -> HttpResponse {
// These can't use get_id because they attribute name and class name aren't ... well name.
let uat = get_current_user(&session);
let filter = filter_all!(f_and!([
f_eq("class", PartialValue::new_class("attributetype")),
f_eq("attributename", PartialValue::new_iutf8s(path.as_str()))
]));
let obj = InternalSearchMessage {
uat,
filter,
attrs: None,
};
match state.qe_r.send(obj).await {
Ok(Ok(mut r)) => HttpResponse::Ok().json(r.pop()),
Ok(Err(e)) => operation_error_to_response(e),
Err(_) => HttpResponse::InternalServerError().json("mailbox failure"),
}
}
async fn schema_classtype_get((session, state): (Session, Data<AppState>)) -> HttpResponse {
let filter = filter_all!(f_eq("class", PartialValue::new_class("classtype")));
json_rest_event_get(session, state, filter, None).await
}
async fn schema_classtype_get_id(
(path, session, state): (Path<String>, Session, Data<AppState>),
) -> HttpResponse {
// These can't use get_id because they attribute name and class name aren't ... well name.
let uat = get_current_user(&session);
let filter = filter_all!(f_and!([
f_eq("class", PartialValue::new_class("classtype")),
f_eq("classname", PartialValue::new_iutf8s(path.as_str()))
]));
let obj = InternalSearchMessage {
uat,
filter,
attrs: None,
};
match state.qe_r.send(obj).await {
Ok(Ok(mut r)) => HttpResponse::Ok().json(r.pop()),
Ok(Err(e)) => operation_error_to_response(e),
Err(_) => HttpResponse::InternalServerError().json("mailbox failure"),
}
}
// == person ==
async fn person_get((session, state): (Session, Data<AppState>)) -> HttpResponse {
let filter = filter_all!(f_eq("class", PartialValue::new_class("person")));
json_rest_event_get(session, state, filter, None).await
}
async fn person_post(
(obj, session, state): (Json<ProtoEntry>, Session, Data<AppState>),
) -> HttpResponse {
let classes = vec!["account".to_string(), "object".to_string()];
json_rest_event_post(obj.into_inner(), session, state, classes).await
}
async fn person_id_get(
(path, session, state): (Path<String>, Session, Data<AppState>),
) -> HttpResponse {
let filter = filter_all!(f_eq("class", PartialValue::new_class("person")));
json_rest_event_get_id(path, session, state, filter, None).await
}
// == account ==
async fn account_get((session, state): (Session, Data<AppState>)) -> HttpResponse {
let filter = filter_all!(f_eq("class", PartialValue::new_class("account")));
json_rest_event_get(session, state, filter, None).await
}
async fn account_post(
(obj, session, state): (Json<ProtoEntry>, Session, Data<AppState>),
) -> HttpResponse {
let classes = vec!["account".to_string(), "object".to_string()];
json_rest_event_post(obj.into_inner(), session, state, classes).await
}
async fn account_id_get(
(path, session, state): (Path<String>, Session, Data<AppState>),
) -> HttpResponse {
let filter = filter_all!(f_eq("class", PartialValue::new_class("account")));
json_rest_event_get_id(path, session, state, filter, None).await
}
async fn account_id_get_attr(
(path, session, state): (Path<(String, String)>, Session, Data<AppState>),
) -> HttpResponse {
let filter = filter_all!(f_eq("class", PartialValue::new_class("account")));
json_rest_event_get_id_attr(path, session, state, filter).await
}
async fn account_id_post_attr(
(values, path, session, state): (
Json<Vec<String>>,
Path<(String, String)>,
Session,
Data<AppState>,
),
) -> HttpResponse {
let filter = filter_all!(f_eq("class", PartialValue::new_class("account")));
json_rest_event_post_id_attr(path, session, state, filter, values.into_inner()).await
}
async fn account_id_delete_attr(
(path, session, state): (Path<(String, String)>, Session, Data<AppState>),
) -> HttpResponse {
let filter = filter_all!(f_eq("class", PartialValue::new_class("account")));
json_rest_event_delete_id_attr(path, session, state, filter).await
}
async fn account_id_put_attr(
(values, path, session, state): (
Json<Vec<String>>,
Path<(String, String)>,
Session,
Data<AppState>,
),
) -> HttpResponse {
let filter = filter_all!(f_eq("class", PartialValue::new_class("account")));
json_rest_event_put_id_attr(path, session, state, filter, values.into_inner()).await
}
async fn account_id_delete(
(path, session, state): (Path<String>, Session, Data<AppState>),
) -> HttpResponse {
let filter = filter_all!(f_eq("class", PartialValue::new_class("account")));
json_rest_event_delete_id(path, session, state, filter).await
}
async fn account_put_id_credential_primary(
(obj, path, session, state): (
Json<SetCredentialRequest>,
Path<String>,
Session,
Data<AppState>,
),
) -> HttpResponse {
let id = path.into_inner();
json_rest_event_credential_put(id, None, session, state, obj.into_inner()).await
}
// Return a vec of str
async fn account_get_id_ssh_pubkeys(
(path, session, state): (Path<String>, Session, Data<AppState>),
) -> HttpResponse {
let uat = get_current_user(&session);
let id = path.into_inner();
let obj = InternalSshKeyReadMessage {
uat,
uuid_or_name: id,
};
match state.qe_r.send(obj).await {
Ok(Ok(r)) => HttpResponse::Ok().json(r),
Ok(Err(e)) => operation_error_to_response(e),
Err(_) => HttpResponse::InternalServerError().json("mailbox failure"),
}
}
async fn account_post_id_ssh_pubkey(
(obj, path, session, state): (
Json<(String, String)>,
Path<String>,
Session,
Data<AppState>,
),
) -> HttpResponse {
let uat = get_current_user(&session);
let id = path.into_inner();
let (tag, key) = obj.into_inner();
let m_obj = InternalSshKeyCreateMessage {
uat,
uuid_or_name: id,
tag,
key,
filter: filter_all!(f_eq("class", PartialValue::new_class("account"))),
};
// Add a msg here
match state.qe_w.send(m_obj).await {
Ok(Ok(r)) => HttpResponse::Ok().json(r),
Ok(Err(e)) => operation_error_to_response(e),
Err(_) => HttpResponse::InternalServerError().json("mailbox failure"),
}
}
async fn account_get_id_ssh_pubkey_tag(
(path, session, state): (Path<(String, String)>, Session, Data<AppState>),
) -> HttpResponse {
let uat = get_current_user(&session);
let (id, tag) = path.into_inner();
let obj = InternalSshKeyTagReadMessage {
uat,
uuid_or_name: id,
tag,
};
match state.qe_r.send(obj).await {
Ok(Ok(r)) => HttpResponse::Ok().json(r),
Ok(Err(e)) => operation_error_to_response(e),
Err(_) => HttpResponse::InternalServerError().json("mailbox failure"),
}
}
async fn account_delete_id_ssh_pubkey_tag(
(path, session, state): (Path<(String, String)>, Session, Data<AppState>),
) -> HttpResponse {
let uat = get_current_user(&session);
let (id, tag) = path.into_inner();
let obj = RemoveAttributeValueMessage {
uat,
uuid_or_name: id,
attr: "ssh_publickey".to_string(),
value: tag,
filter: filter_all!(f_eq("class", PartialValue::new_class("account"))),
};
match state.qe_w.send(obj).await {
Ok(Ok(r)) => HttpResponse::Ok().json(r),
Ok(Err(e)) => operation_error_to_response(e),
Err(_) => HttpResponse::InternalServerError().json("mailbox failure"),
}
}
// Get and return a single str
async fn account_get_id_radius(
(path, session, state): (Path<String>, Session, Data<AppState>),
) -> HttpResponse {
let uat = get_current_user(&session);
let id = path.into_inner();
let obj = InternalRadiusReadMessage {
uat,
uuid_or_name: id,
};
match state.qe_r.send(obj).await {
Ok(Ok(r)) => HttpResponse::Ok().json(r),
Ok(Err(e)) => operation_error_to_response(e),
Err(_) => HttpResponse::InternalServerError().json("mailbox failure"),
}
}
async fn account_post_id_radius_regenerate(
(path, session, state): (Path<String>, Session, Data<AppState>),
) -> HttpResponse {
// Need to to send the regen msg
let uat = get_current_user(&session);
let id = path.into_inner();
let obj = InternalRegenerateRadiusMessage::new(uat, id);
match state.qe_w.send(obj).await {
Ok(Ok(r)) => HttpResponse::Ok().json(r),
Ok(Err(e)) => operation_error_to_response(e),
Err(_) => HttpResponse::InternalServerError().json("mailbox failure"),
}
}
async fn account_delete_id_radius(
(path, session, state): (Path<String>, Session, Data<AppState>),
) -> HttpResponse {
// We reconstruct path here to keep json_rest_event_delete_id_attr generic.
let p = Path::from((path.into_inner(), "radius_secret".to_string()));
let filter = filter_all!(f_eq("class", PartialValue::new_class("account")));
json_rest_event_delete_id_attr(p, session, state, filter).await
}
async fn account_get_id_radius_token(
(path, session, state): (Path<String>, Session, Data<AppState>),
) -> HttpResponse {
let uat = get_current_user(&session);
let id = path.into_inner();
let obj = InternalRadiusTokenReadMessage {
uat,
uuid_or_name: id,
};
match state.qe_r.send(obj).await {
Ok(Ok(r)) => HttpResponse::Ok().json(r),
Ok(Err(e)) => operation_error_to_response(e),
Err(_) => HttpResponse::InternalServerError().json("mailbox failure"),
}
}
async fn account_post_id_person_extend(
(path, session, state): (Path<String>, Session, Data<AppState>),
) -> HttpResponse {
let uat = get_current_user(&session);
let uuid_or_name = path.into_inner();
let m_obj = IdmAccountPersonExtendMessage { uat, uuid_or_name };
match state.qe_w.send(m_obj).await {
Ok(Ok(r)) => HttpResponse::Ok().json(r),
Ok(Err(e)) => operation_error_to_response(e),
Err(_) => HttpResponse::InternalServerError().json("mailbox failure"),
}
}
async fn account_post_id_unix(
(obj, path, session, state): (
Json<AccountUnixExtend>,
Path<String>,
Session,
Data<AppState>,
),
) -> HttpResponse {
let uat = get_current_user(&session);
let id = path.into_inner();
let m_obj = IdmAccountUnixExtendMessage::new(uat, id, obj.into_inner());
match state.qe_w.send(m_obj).await {
Ok(Ok(r)) => HttpResponse::Ok().json(r),
Ok(Err(e)) => operation_error_to_response(e),
Err(_) => HttpResponse::InternalServerError().json("mailbox failure"),
}
}
async fn account_get_id_unix_token(
(path, session, state): (Path<String>, Session, Data<AppState>),
) -> HttpResponse {
let uat = get_current_user(&session);
let id = path.into_inner();
let obj = InternalUnixUserTokenReadMessage {
uat,
uuid_or_name: id,
};
match state.qe_r.send(obj).await {
Ok(Ok(r)) => HttpResponse::Ok().json(r),
Ok(Err(e)) => operation_error_to_response(e),
Err(_) => HttpResponse::InternalServerError().json("mailbox failure"),
}
}
async fn account_post_id_unix_auth(
(obj, path, session, state): (
Json<SingleStringRequest>,
Path<String>,
Session,
Data<AppState>,
),
) -> HttpResponse {
let uat = get_current_user(&session);
let id = path.into_inner();
let m_obj = IdmAccountUnixAuthMessage {
uat: uat,
uuid_or_name: id,
cred: obj.into_inner().value,
};
match state.qe_r.send(m_obj).await {
Ok(Ok(r)) => HttpResponse::Ok().json(r),
Ok(Err(e)) => operation_error_to_response(e),
Err(_) => HttpResponse::InternalServerError().json("mailbox failure"),
}
}
async fn account_put_id_unix_credential(
(obj, path, session, state): (
Json<SingleStringRequest>,
Path<String>,
Session,
Data<AppState>,
),
) -> HttpResponse {
let uat = get_current_user(&session);
let id = path.into_inner();
let m_obj = IdmAccountUnixSetCredMessage {
uat,
uuid_or_name: id,
cred: obj.into_inner().value,
};
match state.qe_w.send(m_obj).await {
Ok(Ok(r)) => HttpResponse::Ok().json(r),
Ok(Err(e)) => operation_error_to_response(e),
Err(_) => HttpResponse::InternalServerError().json("mailbox failure"),
}
}
async fn account_delete_id_unix_credential(
(path, session, state): (Path<String>, Session, Data<AppState>),
) -> HttpResponse {
let uat = get_current_user(&session);
let id = path.into_inner();
let obj = PurgeAttributeMessage {
uat,
uuid_or_name: id,
attr: "unix_password".to_string(),
filter: filter_all!(f_eq("class", PartialValue::new_class("posixaccount"))),
};
match state.qe_w.send(obj).await {
Ok(Ok(r)) => HttpResponse::Ok().json(r),
Ok(Err(e)) => operation_error_to_response(e),
Err(_) => HttpResponse::InternalServerError().json("mailbox failure"),
}
}
async fn group_get((session, state): (Session, Data<AppState>)) -> HttpResponse {
let filter = filter_all!(f_eq("class", PartialValue::new_class("group")));
json_rest_event_get(session, state, filter, None).await
}
async fn group_post(
(obj, session, state): (Json<ProtoEntry>, Session, Data<AppState>),
) -> HttpResponse {
let classes = vec!["group".to_string(), "object".to_string()];
json_rest_event_post(obj.into_inner(), session, state, classes).await
}
async fn group_id_get(
(path, session, state): (Path<String>, Session, Data<AppState>),
) -> HttpResponse {
let filter = filter_all!(f_eq("class", PartialValue::new_class("group")));
json_rest_event_get_id(path, session, state, filter, None).await
}
async fn group_id_get_attr(
(path, session, state): (Path<(String, String)>, Session, Data<AppState>),
) -> HttpResponse {
let filter = filter_all!(f_eq("class", PartialValue::new_class("group")));
json_rest_event_get_id_attr(path, session, state, filter).await
}
async fn group_id_post_attr(
(values, path, session, state): (
Json<Vec<String>>,
Path<(String, String)>,
Session,
Data<AppState>,
),
) -> HttpResponse {
let filter = filter_all!(f_eq("class", PartialValue::new_class("group")));
json_rest_event_post_id_attr(path, session, state, filter, values.into_inner()).await
}
async fn group_id_delete_attr(
(path, session, state): (Path<(String, String)>, Session, Data<AppState>),
) -> HttpResponse {
let filter = filter_all!(f_eq("class", PartialValue::new_class("group")));
json_rest_event_delete_id_attr(path, session, state, filter).await
}
async fn group_id_put_attr(
(values, path, session, state): (
Json<Vec<String>>,
Path<(String, String)>,
Session,
Data<AppState>,
),
) -> HttpResponse {
let filter = filter_all!(f_eq("class", PartialValue::new_class("group")));
json_rest_event_put_id_attr(path, session, state, filter, values.into_inner()).await
}
async fn group_id_delete(
(path, session, state): (Path<String>, Session, Data<AppState>),
) -> HttpResponse {
let filter = filter_all!(f_eq("class", PartialValue::new_class("group")));
json_rest_event_delete_id(path, session, state, filter).await
}
async fn group_post_id_unix(
(obj, path, session, state): (Json<GroupUnixExtend>, Path<String>, Session, Data<AppState>),
) -> HttpResponse {
let uat = get_current_user(&session);
let id = path.into_inner();
let m_obj = IdmGroupUnixExtendMessage::new(uat, id, obj.into_inner());
match state.qe_w.send(m_obj).await {
Ok(Ok(r)) => HttpResponse::Ok().json(r),
Ok(Err(e)) => operation_error_to_response(e),
Err(_) => HttpResponse::InternalServerError().json("mailbox failure"),
}
}
async fn group_get_id_unix_token(
(path, session, state): (Path<String>, Session, Data<AppState>),
) -> HttpResponse {
let uat = get_current_user(&session);
let id = path.into_inner();
let obj = InternalUnixGroupTokenReadMessage {
uat,
uuid_or_name: id,
};
match state.qe_r.send(obj).await {
Ok(Ok(r)) => HttpResponse::Ok().json(r),
Ok(Err(e)) => operation_error_to_response(e),
Err(_) => HttpResponse::InternalServerError().json("mailbox failure"),
}
}
async fn domain_get((session, state): (Session, Data<AppState>)) -> HttpResponse {
let filter = filter_all!(f_eq("class", PartialValue::new_class("domain_info")));
json_rest_event_get(session, state, filter, None).await
}
async fn domain_id_get(
(path, session, state): (Path<String>, Session, Data<AppState>),
) -> HttpResponse {
let filter = filter_all!(f_eq("class", PartialValue::new_class("domain_info")));
json_rest_event_get_id(path, session, state, filter, None).await
}
async fn domain_id_get_attr(
(path, session, state): (Path<(String, String)>, Session, Data<AppState>),
) -> HttpResponse {
let filter = filter_all!(f_eq("class", PartialValue::new_class("domain_info")));
json_rest_event_get_id_attr(path, session, state, filter).await
}
async fn domain_id_put_attr(
(values, path, session, state): (
Json<Vec<String>>,
Path<(String, String)>,
Session,
Data<AppState>,
),
) -> HttpResponse {
let filter = filter_all!(f_eq("class", PartialValue::new_class("domain_info")));
json_rest_event_put_id_attr(path, session, state, filter, values.into_inner()).await
}
async fn recycle_bin_get((session, state): (Session, Data<AppState>)) -> HttpResponse {
let filter = filter_all!(f_pres("class"));
let uat = get_current_user(&session);
let attrs = None;
let obj = InternalSearchRecycledMessage { uat, filter, attrs };
match state.qe_r.send(obj).await {
Ok(Ok(r)) => HttpResponse::Ok().json(r),
Ok(Err(e)) => operation_error_to_response(e),
Err(_) => HttpResponse::InternalServerError().json("mailbox failure"),
}
}
async fn recycle_bin_id_get(
(path, session, state): (Path<String>, Session, Data<AppState>),
) -> HttpResponse {
let uat = get_current_user(&session);
let filter = filter_all!(f_id(path.as_str()));
let attrs = None;
let obj = InternalSearchRecycledMessage { uat, filter, attrs };
match state.qe_r.send(obj).await {
Ok(Ok(mut r)) => HttpResponse::Ok().json(r.pop()),
Ok(Err(e)) => operation_error_to_response(e),
Err(_) => HttpResponse::InternalServerError().json("mailbox failure"),
}
}
async fn recycle_bin_revive_id_post(
(path, session, state): (Path<String>, Session, Data<AppState>),
) -> HttpResponse {
let uat = get_current_user(&session);
let filter = filter_all!(f_id(path.as_str()));
let m_obj = ReviveRecycledMessage { uat, filter };
match state.qe_w.send(m_obj).await {
Ok(Ok(r)) => HttpResponse::Ok().json(r),
Ok(Err(e)) => operation_error_to_response(e),
Err(_) => HttpResponse::InternalServerError().json("mailbox failure"),
}
}
async fn do_nothing(_session: Session) -> String {
"did nothing".to_string()
}
// We probably need an extract auth or similar to handle the different
// types (cookie, bearer), and to generic this over get/post.
async fn auth((obj, session, state): (Json<AuthRequest>, Session, Data<AppState>)) -> HttpResponse {
// First, deal with some state management.
// Do anything here first that's needed like getting the session details
// out of the req cookie.
// From the actix source errors here
// seems to be related to the serde_json deserialise of the cookie
// content, and because we control it's get/set it SHOULD be fine
// provided we use secure cookies. But we can't always trust that ...
let maybe_sessionid = match session.get::<Uuid>("auth-session-id") {
Ok(c) => c,
Err(_e) => return HttpResponse::InternalServerError().json(()),
};
let auth_msg = AuthMessage::new(obj.into_inner(), maybe_sessionid);
// We probably need to know if we allocate the cookie, that this is a
// new session, and in that case, anything *except* authrequest init is
// invalid.
match state
// This may change in the future ...
.qe_r
.send(auth_msg)
.await
{
Ok(Ok(ar)) => {
match &ar.state {
AuthState::Success(uat) => {
// Remove the auth-session-id
session.remove("auth-session-id");
// Set the uat into the cookie
match session.set("uat", uat) {
Ok(_) => HttpResponse::Ok().json(ar),
Err(_) => HttpResponse::InternalServerError().json(()),
}
}
AuthState::Denied(_) => {
// Remove the auth-session-id
session.remove("auth-session-id");
HttpResponse::Unauthorized().json(ar)
}
AuthState::Continue(_) => {
// Ensure the auth-session-id is set
match session.set("auth-session-id", ar.sessionid) {
Ok(_) => HttpResponse::Ok().json(ar),
Err(_) => HttpResponse::InternalServerError().json(()),
}
}
}
}
Ok(Err(e)) => operation_error_to_response(e),
Err(_) => HttpResponse::InternalServerError().json("mailbox failure"),
}
}
async fn idm_account_set_password(
(obj, session, state): (Json<SingleStringRequest>, Session, Data<AppState>),
) -> HttpResponse {
json_event_post!(
obj.into_inner(),
session,
IdmAccountSetPasswordMessage,
state.qe_w
)
}
// === internal setup helpers
fn setup_backend(config: &Configuration) -> Result<Backend, OperationError> {
let mut audit_be = AuditScope::new("backend_setup");
let pool_size: u32 = config.threads as u32;
let be = Backend::new(&mut audit_be, config.db_path.as_str(), pool_size);
// debug!
debug!("{}", audit_be);
be
}
// TODO #54: We could move most of the be/schema/qs setup and startup
// outside of this call, then pass in "what we need" in a cloneable
// form, this way we could have seperate Idm vs Qs threads, and dedicated
// threads for write vs read
fn setup_qs_idms(
audit: &mut AuditScope,
be: Backend,
) -> Result<(QueryServer, IdmServer), OperationError> {
// Create "just enough" schema for us to be able to load from
// disk ... Schema loading is one time where we validate the
// entries as we read them, so we need this here.
let schema = match Schema::new(audit) {
Ok(s) => s,
Err(e) => {
error!("Failed to setup in memory schema: {:?}", e);
return Err(e);
}
};
// Create a query_server implementation
let query_server = QueryServer::new(be, schema);
// TODO #62: Should the IDM parts be broken out to the IdmServer?
// What's important about this initial setup here is that it also triggers
// the schema and acp reload, so they are now configured correctly!
// Initialise the schema core.
//
// Now search for the schema itself, and validate that the system
// in memory matches the BE on disk, and that it's syntactically correct.
// Write it out if changes are needed.
query_server.initialise_helper(audit, duration_from_epoch_now())?;
// We generate a SINGLE idms only!
let idms = IdmServer::new(query_server.clone());
Ok((query_server, idms))
}
pub fn backup_server_core(config: Configuration, dst_path: &str) {
let be = match setup_backend(&config) {
Ok(be) => be,
Err(e) => {
error!("Failed to setup BE: {:?}", e);
return;
}
};
let mut audit = AuditScope::new("backend_backup");
let be_ro_txn = be.read();
let r = be_ro_txn.backup(&mut audit, dst_path);
debug!("{}", audit);
match r {
Ok(_) => info!("Backup success!"),
Err(e) => {
error!("Backup failed: {:?}", e);
std::process::exit(1);
}
};
// Let the txn abort, even on success.
}
pub fn restore_server_core(config: Configuration, dst_path: &str) {
let be = match setup_backend(&config) {
Ok(be) => be,
Err(e) => {
error!("Failed to setup BE: {:?}", e);
return;
}
};
let mut audit = AuditScope::new("backend_restore");
// First, we provide the in-memory schema so that core attrs are indexed correctly.
let schema = match Schema::new(&mut audit) {
Ok(s) => s,
Err(e) => {
error!("Failed to setup in memory schema: {:?}", e);
std::process::exit(1);
}
};
// Limit the scope of the schema txn.
let idxmeta = { schema.write().get_idxmeta_set() };
let mut be_wr_txn = be.write(idxmeta);
let r = be_wr_txn
.restore(&mut audit, dst_path)
.and_then(|_| be_wr_txn.commit(&mut audit));
if r.is_err() {
debug!("{}", audit);
error!("Failed to restore database: {:?}", r);
std::process::exit(1);
}
info!("Restore Success!");
info!("Attempting to init query server ...");
let (qs, _idms) = match setup_qs_idms(&mut audit, be) {
Ok(t) => t,
Err(e) => {
debug!("{}", audit);
error!("Unable to setup query server or idm server -> {:?}", e);
return;
}
};
info!("Success!");
info!("Start reindex phase ...");
let qs_write = qs.write(duration_from_epoch_now());
let r = qs_write
.reindex(&mut audit)
.and_then(|_| qs_write.commit(&mut audit));
match r {
Ok(_) => info!("Reindex Success!"),
Err(e) => {
error!("Restore failed: {:?}", e);
std::process::exit(1);
}
};
}
pub fn reindex_server_core(config: Configuration) {
let be = match setup_backend(&config) {
Ok(be) => be,
Err(e) => {
error!("Failed to setup BE: {:?}", e);
return;
}
};
let mut audit = AuditScope::new("server_reindex");
// First, we provide the in-memory schema so that core attrs are indexed correctly.
let schema = match Schema::new(&mut audit) {
Ok(s) => s,
Err(e) => {
error!("Failed to setup in memory schema: {:?}", e);
std::process::exit(1);
}
};
info!("Start Index Phase 1 ...");
// Limit the scope of the schema txn.
let idxmeta = { schema.write().get_idxmeta_set() };
// Reindex only the core schema attributes to bootstrap the process.
let be_wr_txn = be.write(idxmeta);
let r = be_wr_txn
.reindex(&mut audit)
.and_then(|_| be_wr_txn.commit(&mut audit));
// Now that's done, setup a minimal qs and reindex from that.
if r.is_err() {
debug!("{}", audit);
error!("Failed to reindex database: {:?}", r);
std::process::exit(1);
}
info!("Index Phase 1 Success!");
info!("Attempting to init query server ...");
let (qs, _idms) = match setup_qs_idms(&mut audit, be) {
Ok(t) => t,
Err(e) => {
debug!("{}", audit);
error!("Unable to setup query server or idm server -> {:?}", e);
return;
}
};
info!("Init Query Server Success!");
info!("Start Index Phase 2 ...");
let qs_write = qs.write(duration_from_epoch_now());
let r = qs_write
.reindex(&mut audit)
.and_then(|_| qs_write.commit(&mut audit));
match r {
Ok(_) => info!("Index Phase 2 Success!"),
Err(e) => {
error!("Reindex failed: {:?}", e);
std::process::exit(1);
}
};
}
pub fn domain_rename_core(config: Configuration, new_domain_name: String) {
let mut audit = AuditScope::new("domain_rename");
// Start the backend.
let be = match setup_backend(&config) {
Ok(be) => be,
Err(e) => {
error!("Failed to setup BE: {:?}", e);
return;
}
};
// setup the qs - *with* init of the migrations and schema.
let (qs, _idms) = match setup_qs_idms(&mut audit, be) {
Ok(t) => t,
Err(e) => {
debug!("{}", audit);
error!("Unable to setup query server or idm server -> {:?}", e);
return;
}
};
let mut qs_write = qs.write(duration_from_epoch_now());
let r = qs_write
.domain_rename(&mut audit, new_domain_name.as_str())
.and_then(|_| qs_write.commit(&mut audit));
match r {
Ok(_) => info!("Domain Rename Success!"),
Err(e) => {
error!("Domain Rename Failed - Rollback has occured: {:?}", e);
std::process::exit(1);
}
};
}
pub fn reset_sid_core(config: Configuration) {
let mut audit = AuditScope::new("reset_sid_core");
// Setup the be
let be = match setup_backend(&config) {
Ok(be) => be,
Err(e) => {
error!("Failed to setup BE: {:?}", e);
return;
}
};
let nsid = be.reset_db_s_uuid(&mut audit);
debug!("{}", audit);
info!("New Server ID: {:?}", nsid);
}
pub fn verify_server_core(config: Configuration) {
let mut audit = AuditScope::new("server_verify");
// Setup the be
let be = match setup_backend(&config) {
Ok(be) => be,
Err(e) => {
error!("Failed to setup BE: {:?}", e);
return;
}
};
// setup the qs - without initialise!
let schema_mem = match Schema::new(&mut audit) {
Ok(sc) => sc,
Err(e) => {
error!("Failed to setup in memory schema: {:?}", e);
return;
}
};
let server = QueryServer::new(be, schema_mem);
// Run verifications.
let r = server.verify(&mut audit);
debug!("{}", audit);
if r.is_empty() {
info!("Verification passed!");
std::process::exit(0);
} else {
for er in r {
error!("{:?}", er);
}
std::process::exit(1);
}
// Now add IDM server verifications?
}
pub fn recover_account_core(config: Configuration, name: String, password: String) {
let mut audit = AuditScope::new("recover_account");
// Start the backend.
let be = match setup_backend(&config) {
Ok(be) => be,
Err(e) => {
error!("Failed to setup BE: {:?}", e);
return;
}
};
// setup the qs - *with* init of the migrations and schema.
let (_qs, idms) = match setup_qs_idms(&mut audit, be) {
Ok(t) => t,
Err(e) => {
debug!("{}", audit);
error!("Unable to setup query server or idm server -> {:?}", e);
return;
}
};
// Run the password change.
let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now());
match idms_prox_write.recover_account(&mut audit, name, password) {
Ok(_) => {
idms_prox_write
.commit(&mut audit)
.expect("A critical error during commit occured.");
debug!("{}", audit);
info!("Password reset!");
}
Err(e) => {
error!("Error during password reset -> {:?}", e);
debug!("{}", audit);
// abort the txn
std::mem::drop(idms_prox_write);
std::process::exit(1);
}
};
}
pub fn create_server_core(config: Configuration) {
// Until this point, we probably want to write to the log macro fns.
if config.integration_test_config.is_some() {
warn!("RUNNING IN INTEGRATION TEST MODE.");
warn!("IF YOU SEE THIS IN PRODUCTION YOU MUST CONTACT SUPPORT IMMEDIATELY.");
}
info!("Starting kanidm with configuration: {}", config);
// The log server is started on it's own thread, and is contacted
// asynchronously.
let log_addr = async_log::start();
// Setup TLS (if any)
let opt_tls_params = match setup_tls(&config) {
Ok(opt_tls_params) => opt_tls_params,
Err(e) => {
error!("Failed to configure TLS parameters -> {:?}", e);
return;
}
};
// Similar, create a stats thread which aggregates statistics from the
// server as they come in.
// Setup the be for the qs.
let be = match setup_backend(&config) {
Ok(be) => be,
Err(e) => {
error!("Failed to setup BE -> {:?}", e);
return;
}
};
let mut audit = AuditScope::new("setup_qs_idms");
// Start the IDM server.
let (qs, idms) = match setup_qs_idms(&mut audit, be) {
Ok(t) => t,
Err(e) => {
debug!("{}", audit);
error!("Unable to setup query server or idm server -> {:?}", e);
return;
}
};
// Any pre-start tasks here.
match &config.integration_test_config {
Some(itc) => {
let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now());
match idms_prox_write.recover_account(
&mut audit,
"admin".to_string(),
itc.admin_password.clone(),
) {
Ok(_) => {}
Err(e) => {
debug!("{}", audit);
error!(
"Unable to configure INTERGATION TEST admin account -> {:?}",
e
);
return;
}
};
match idms_prox_write.commit(&mut audit) {
Ok(_) => {}
Err(e) => {
debug!("{}", audit);
error!("Unable to commit INTERGATION TEST setup -> {:?}", e);
return;
}
}
}
None => {}
}
log_addr.do_send(audit);
// Arc the idms.
let idms_arc = Arc::new(idms);
// Pass it to the actor for threading.
// Start the read query server with the given be path: future config
let server_read_addr = QueryServerReadV1::start(
log_addr.clone(),
qs.clone(),
idms_arc.clone(),
config.threads,
);
// Start the write thread
let server_write_addr = QueryServerWriteV1::start(log_addr, qs, idms_arc);
// Setup timed events associated to the write thread
let _int_addr = IntervalActor::new(server_write_addr.clone()).start();
// Copy the max size
let secure_cookies = config.secure_cookies;
// domain will come from the qs now!
let cookie_key: [u8; 32] = config.cookie_key;
// start the web server
let server = HttpServer::new(move || {
App::new()
.data(AppState {
qe_r: server_read_addr.clone(),
qe_w: server_write_addr.clone(),
})
.wrap(middleware::Logger::default())
.wrap(
// Signed prevents tampering. this 32 byte key MUST
// be generated (probably a cli option, and it's up to the
// server process to coordinate these on hosts). IE an RODC
// could have a different key than our write servers to prevent
// disclosure of a writeable token in case of compromise. It does
// mean that you can't load balance between the rodc and the write
// though, but that's tottaly reasonable.
CookieSession::signed(&cookie_key)
// .path(prefix.as_str())
// .domain(domain.as_str())
.same_site(cookie::SameSite::Strict)
.name("kanidm-session")
// if true, only allow to https
.secure(secure_cookies)
// TODO #63: make this configurable!
.max_age_time(Duration::hours(1)),
)
// .service(fs::Files::new("/static", "./static"))
// Even though this says "CreateRequest", it's actually configuring *ALL* json requests.
// .app_data(web::Json::<CreateRequest>::configure(|cfg| { cfg
.app_data(
web::JsonConfig::default()
.limit(4096)
.error_handler(|err, _req| {
let s = format!("{}", err);
error::InternalError::from_response(err, HttpResponse::BadRequest().json(s))
.into()
}),
)
.service(
web::scope("/v1/raw")
.route("/create", web::post().to(create))
.route("/modify", web::post().to(modify))
.route("/delete", web::post().to(delete))
.route("/search", web::post().to(search)),
)
.service(web::scope("/v1/auth").route("", web::post().to(auth)))
.service(
web::scope("/v1/schema")
.route("", web::get().to(schema_get))
.route("/attributetype", web::get().to(schema_attributetype_get))
.route("/attributetype", web::post().to(do_nothing))
.route(
"/attributetype/{id}",
web::get().to(schema_attributetype_get_id),
)
.route("/attributetype/{id}", web::put().to(do_nothing))
.route("/attributetype/{id}", web::patch().to(do_nothing))
.route("/classtype", web::get().to(schema_classtype_get))
.route("/classtype", web::post().to(do_nothing))
.route("/classtype/{id}", web::get().to(schema_classtype_get_id))
.route("/classtype/{id}", web::put().to(do_nothing))
.route("/classtype/{id}", web::patch().to(do_nothing)),
)
.service(
web::scope("/v1/self")
.route("", web::get().to(whoami))
.route("/_attr/{attr}", web::get().to(do_nothing))
.route("/_credential", web::get().to(do_nothing))
.route(
"/_credential/primary/set_password",
web::post().to(idm_account_set_password),
)
.route("/_credential/{cid}/_lock", web::get().to(do_nothing))
.route("/_radius", web::get().to(do_nothing))
.route("/_radius", web::delete().to(do_nothing))
.route("/_radius", web::post().to(do_nothing))
.route("/_radius/_config", web::post().to(do_nothing))
.route("/_radius/_config/{secret_otp}", web::get().to(do_nothing))
.route(
"/_radius/_config/{secret_otp}/apple",
web::get().to(do_nothing),
),
)
.service(
web::scope("/v1/person")
.route("", web::get().to(person_get))
.route("", web::post().to(person_post))
.route("/{id}", web::get().to(person_id_get)), /*
.route("/{id}", web::delete().to(account_id_delete))
.route("/{id}/_attr/{attr}", web::get().to(account_id_get_attr))
.route("/{id}/_attr/{attr}", web::post().to(account_id_post_attr))
.route("/{id}/_attr/{attr}", web::put().to(account_id_put_attr))
.route(
"/{id}/_attr/{attr}",
web::delete().to(account_id_delete_attr),
)
*/
)
.service(
web::scope("/v1/account")
.route("", web::get().to(account_get))
.route("", web::post().to(account_post))
.route("/{id}", web::get().to(account_id_get))
.route("/{id}", web::delete().to(account_id_delete))
.route("/{id}/_attr/{attr}", web::get().to(account_id_get_attr))
.route("/{id}/_attr/{attr}", web::post().to(account_id_post_attr))
.route("/{id}/_attr/{attr}", web::put().to(account_id_put_attr))
.route(
"/{id}/_attr/{attr}",
web::delete().to(account_id_delete_attr),
)
.route(
"/{id}/_person/_extend",
web::post().to(account_post_id_person_extend),
)
.route("/{id}/_lock", web::get().to(do_nothing))
.route("/{id}/_credential", web::get().to(do_nothing))
.route(
"/{id}/_credential/primary",
web::put().to(account_put_id_credential_primary),
)
.route("/{id}/_credential/{cid}/_lock", web::get().to(do_nothing))
.route(
"/{id}/_ssh_pubkeys",
web::get().to(account_get_id_ssh_pubkeys),
)
.route(
"/{id}/_ssh_pubkeys",
web::post().to(account_post_id_ssh_pubkey),
)
.route(
"/{id}/_ssh_pubkeys/{tag}",
web::get().to(account_get_id_ssh_pubkey_tag),
)
.route(
"/{id}/_ssh_pubkeys/{tag}",
web::delete().to(account_delete_id_ssh_pubkey_tag),
)
.route("/{id}/_radius", web::get().to(account_get_id_radius))
.route(
"/{id}/_radius",
web::post().to(account_post_id_radius_regenerate),
)
.route("/{id}/_radius", web::delete().to(account_delete_id_radius))
.route(
"/{id}/_radius/_token",
web::get().to(account_get_id_radius_token),
)
.route("/{id}/_unix", web::post().to(account_post_id_unix))
.route(
"/{id}/_unix/_token",
web::get().to(account_get_id_unix_token),
)
.route(
"/{id}/_unix/_auth",
web::post().to(account_post_id_unix_auth),
)
.route(
"/{id}/_unix/_credential",
web::put().to(account_put_id_unix_credential),
)
.route(
"/{id}/_unix/_credential",
web::delete().to(account_delete_id_unix_credential),
),
)
.service(
web::scope("/v1/group")
.route("", web::get().to(group_get))
.route("", web::post().to(group_post))
.route("/{id}", web::get().to(group_id_get))
.route("/{id}", web::delete().to(group_id_delete))
.route("/{id}/_attr/{attr}", web::get().to(group_id_get_attr))
.route("/{id}/_attr/{attr}", web::post().to(group_id_post_attr))
.route("/{id}/_attr/{attr}", web::put().to(group_id_put_attr))
.route("/{id}/_attr/{attr}", web::delete().to(group_id_delete_attr))
.route("/{id}/_unix", web::post().to(group_post_id_unix))
.route("/{id}/_unix/_token", web::get().to(group_get_id_unix_token)),
)
.service(
web::scope("/v1/domain")
.route("", web::get().to(domain_get))
.route("/{id}", web::get().to(domain_id_get))
.route("/{id}/_attr/{attr}", web::get().to(domain_id_get_attr))
.route("/{id}/_attr/{attr}", web::put().to(domain_id_put_attr)),
)
.service(
web::scope("/v1/recycle_bin")
.route("", web::get().to(recycle_bin_get))
.route("/{id}", web::get().to(recycle_bin_id_get))
.route("/{id}/_revive", web::post().to(recycle_bin_revive_id_post)),
)
.service(
web::scope("/v1/access_profile")
.route("", web::get().to(do_nothing))
.route("/{id}", web::get().to(do_nothing))
.route("/{id}/_attr/{attr}", web::get().to(do_nothing)),
)
});
let server = match opt_tls_params {
Some(tls_params) => server.bind_openssl(config.address, tls_params),
None => {
warn!("Starting WITHOUT TLS parameters. This may cause authentication to fail!");
server.bind(config.address)
}
};
server.expect("Failed to initialise server!").run();
}