mirror of
https://github.com/kanidm/kanidm.git
synced 2025-02-24 21:17:01 +01:00
1568 lines
53 KiB
Rust
1568 lines
53 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, InternalSshKeyReadMessage,
|
||
|
InternalSshKeyTagReadMessage, InternalUnixGroupTokenReadMessage,
|
||
|
InternalUnixUserTokenReadMessage, SearchMessage, WhoamiMessage,
|
||
|
};
|
||
|
use crate::actors::v1_write::QueryServerWriteV1;
|
||
|
use crate::actors::v1_write::{
|
||
|
AppendAttributeMessage, CreateMessage, DeleteMessage, IdmAccountSetPasswordMessage,
|
||
|
IdmAccountUnixExtendMessage, IdmAccountUnixSetCredMessage, IdmGroupUnixExtendMessage,
|
||
|
InternalCredentialSetMessage, InternalDeleteMessage, InternalRegenerateRadiusMessage,
|
||
|
InternalSshKeyCreateMessage, ModifyMessage, PurgeAttributeMessage, RemoveAttributeValueMessage,
|
||
|
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::SID;
|
||
|
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, SetAuthCredential, 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: SetAuthCredential,
|
||
|
) -> HttpResponse {
|
||
|
let uat = get_current_user(&session);
|
||
|
|
||
|
let m_obj = InternalCredentialSetMessage::new(uat, id, cred_id, 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"),
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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<SetAuthCredential>,
|
||
|
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_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 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,
|
||
|
sid: SID,
|
||
|
) -> 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)?;
|
||
|
|
||
|
// We generate a SINGLE idms only!
|
||
|
|
||
|
let idms = IdmServer::new(query_server.clone(), sid);
|
||
|
|
||
|
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 server_id = be.get_db_sid();
|
||
|
|
||
|
let (qs, _idms) = match setup_qs_idms(&mut audit, be, server_id) {
|
||
|
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();
|
||
|
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 server_id = be.get_db_sid();
|
||
|
|
||
|
let (qs, _idms) = match setup_qs_idms(&mut audit, be, server_id) {
|
||
|
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();
|
||
|
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;
|
||
|
}
|
||
|
};
|
||
|
let server_id = be.get_db_sid();
|
||
|
// setup the qs - *with* init of the migrations and schema.
|
||
|
let (qs, _idms) = match setup_qs_idms(&mut audit, be, server_id) {
|
||
|
Ok(t) => t,
|
||
|
Err(e) => {
|
||
|
debug!("{}", audit);
|
||
|
error!("Unable to setup query server or idm server -> {:?}", e);
|
||
|
return;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
let mut qs_write = qs.write();
|
||
|
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_sid(&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;
|
||
|
}
|
||
|
};
|
||
|
let server_id = be.get_db_sid();
|
||
|
// setup the qs - *with* init of the migrations and schema.
|
||
|
let (_qs, idms) = match setup_qs_idms(&mut audit, be, server_id) {
|
||
|
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();
|
||
|
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 server_id = be.get_db_sid();
|
||
|
info!("Server ID -> {:?}", server_id);
|
||
|
|
||
|
let mut audit = AuditScope::new("setup_qs_idms");
|
||
|
// Start the IDM server.
|
||
|
let (qs, idms) = match setup_qs_idms(&mut audit, be, server_id) {
|
||
|
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();
|
||
|
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/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}/_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(do_nothing))
|
||
|
.route("/{id}", web::get().to(do_nothing))
|
||
|
.route("/{id}/_restore", web::get().to(do_nothing)),
|
||
|
)
|
||
|
.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();
|
||
|
}
|