195 lines
6.1 KiB
Rust
195 lines
6.1 KiB
Rust
use std::time::Duration;
|
|
|
|
use kube::{
|
|
Api, CELSchema, CustomResource,
|
|
api::{Patch, PatchParams},
|
|
runtime::controller::Action,
|
|
};
|
|
use queries::AttributeType;
|
|
use schemars::JsonSchema;
|
|
use serde::{Deserialize, Serialize};
|
|
use serde_json::json;
|
|
use tracing::{debug, trace, warn};
|
|
|
|
use crate::{
|
|
context::ControllerEvents,
|
|
lldap,
|
|
resources::{Error, user_attribute},
|
|
};
|
|
|
|
use super::Reconcile;
|
|
|
|
#[derive(Deserialize, Serialize, Clone, Copy, Debug, JsonSchema)]
|
|
pub enum Type {
|
|
String,
|
|
Integer,
|
|
Jpeg,
|
|
DateTime,
|
|
}
|
|
|
|
impl From<Type> for AttributeType {
|
|
fn from(t: Type) -> Self {
|
|
match t {
|
|
Type::String => Self::String,
|
|
Type::Integer => Self::Integer,
|
|
Type::Jpeg => Self::JpegPhoto,
|
|
Type::DateTime => Self::DateTime,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(CustomResource, Deserialize, Serialize, Clone, Debug, CELSchema)]
|
|
#[kube(
|
|
kind = "UserAttribute",
|
|
group = "lldap.huizinga.dev",
|
|
version = "v1",
|
|
status = "UserAttributesStatus"
|
|
)]
|
|
#[kube(
|
|
shortname = "lua",
|
|
doc = "Custom resource for managing custom User Attributes inside of LLDAP",
|
|
printcolumn = r#"{"name":"Type", "type":"string", "description":"Type of attribute", "jsonPath":".spec.type"}"#,
|
|
printcolumn = r#"{"name":"List", "type":"boolean", "description":"Can the attribute contain multiple values", "jsonPath":".spec.list"}"#,
|
|
printcolumn = r#"{"name":"Visible", "type":"boolean", "description":"Can users see the value", "jsonPath":".spec.userVisible"}"#,
|
|
printcolumn = r#"{"name":"Editable", "type":"boolean", "description":"Can users edit the value", "jsonPath":".spec.userEditable"}"#,
|
|
printcolumn = r#"{"name":"Synced", "type":"boolean", "jsonPath":".status.synced"}"#,
|
|
printcolumn = r#"{"name":"Age", "type":"date", "jsonPath":".metadata.creationTimestamp"}"#
|
|
)]
|
|
#[kube(
|
|
rule = Rule::new("self.spec == oldSelf.spec").message("User attributes are immutable"),
|
|
rule = Rule::new("!self.spec.userEditable || self.spec.userVisible && self.spec.userEditable").message("Editable attribute must also be visible")
|
|
)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct UesrAttributeSpec {
|
|
r#type: Type,
|
|
#[serde(default)]
|
|
list: bool,
|
|
#[serde(default)]
|
|
user_visible: bool,
|
|
#[serde(default)]
|
|
user_editable: bool,
|
|
}
|
|
#[derive(Deserialize, Serialize, Clone, Default, Debug, JsonSchema)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct UserAttributesStatus {
|
|
pub synced: bool,
|
|
}
|
|
|
|
impl Reconcile for UserAttribute {
|
|
async fn reconcile(
|
|
self: std::sync::Arc<Self>,
|
|
ctx: std::sync::Arc<crate::context::Context>,
|
|
) -> super::Result<kube::runtime::controller::Action> {
|
|
let name = self
|
|
.metadata
|
|
.name
|
|
.clone()
|
|
.ok_or(Error::MissingObjectKey(".metadata.name"))?;
|
|
|
|
debug!(name, "Apply");
|
|
|
|
trace!(name, "Get existing attributes");
|
|
let lldap_client = ctx.lldap_config.build_client().await?;
|
|
let user_attributes = lldap_client.get_user_attributes().await?;
|
|
trace!("{user_attributes:?}");
|
|
|
|
let client = &ctx.client;
|
|
let api = Api::<UserAttribute>::all(client.clone());
|
|
if let Some(attribute) = user_attributes
|
|
.iter()
|
|
.find(|attribute| attribute.name == name)
|
|
{
|
|
trace!("User attribute already exists: {attribute:?}");
|
|
let mut desynced: Vec<String> = Vec::new();
|
|
if attribute.attribute_type != self.spec.r#type.into() {
|
|
desynced.push("type".into());
|
|
}
|
|
|
|
if attribute.is_list != self.spec.list {
|
|
desynced.push("list".into());
|
|
}
|
|
|
|
if attribute.is_visible != self.spec.user_visible {
|
|
desynced.push("userVisible".into());
|
|
}
|
|
|
|
if attribute.is_editable != self.spec.user_editable {
|
|
desynced.push("userEditable".into());
|
|
}
|
|
|
|
if !desynced.is_empty() {
|
|
set_status(&api, &name, false).await?;
|
|
ctx.recorder
|
|
.user_attribute_desync(self.as_ref(), &desynced)
|
|
.await?;
|
|
|
|
return Err(Error::UserAttributeDesync(desynced));
|
|
}
|
|
|
|
trace!("User attribute matches with spec");
|
|
} else {
|
|
trace!("User attribute does not exist yet");
|
|
|
|
lldap_client
|
|
.create_user_attribute(
|
|
&name,
|
|
self.spec.r#type,
|
|
self.spec.list,
|
|
self.spec.user_visible,
|
|
self.spec.user_editable,
|
|
)
|
|
.await?;
|
|
ctx.recorder.user_attribute_created(self.as_ref()).await?;
|
|
}
|
|
|
|
set_status(&api, &name, true).await?;
|
|
|
|
Ok(Action::requeue(Duration::from_secs(3600)))
|
|
}
|
|
|
|
async fn cleanup(
|
|
self: std::sync::Arc<Self>,
|
|
ctx: std::sync::Arc<crate::context::Context>,
|
|
) -> super::Result<kube::runtime::controller::Action> {
|
|
let name = self
|
|
.metadata
|
|
.name
|
|
.clone()
|
|
.ok_or(Error::MissingObjectKey(".metadata.name"))?;
|
|
|
|
debug!(name, "Cleanup");
|
|
|
|
let lldap_client = ctx.lldap_config.build_client().await?;
|
|
|
|
trace!(name, "Deleting user attribute");
|
|
match lldap_client.delete_user_attribute(&name).await {
|
|
Err(lldap::Error::GraphQl(err))
|
|
if err.message == format!("Attribute {name} is not defined in the schema") =>
|
|
{
|
|
warn!(name, "User attribute not found");
|
|
Ok(())
|
|
}
|
|
Ok(_) => {
|
|
ctx.recorder.user_attribute_deleted(self.as_ref()).await?;
|
|
Ok(())
|
|
}
|
|
Err(err) => Err(err),
|
|
}?;
|
|
|
|
Ok(Action::await_change())
|
|
}
|
|
}
|
|
|
|
async fn set_status(
|
|
api: &Api<UserAttribute>,
|
|
name: &str,
|
|
synced: bool,
|
|
) -> Result<UserAttribute, kube::Error> {
|
|
trace!(name, "Updating status");
|
|
let status = json!({
|
|
"status": UserAttributesStatus { synced }
|
|
});
|
|
api.patch_status(name, &PatchParams::default(), &Patch::Merge(&status))
|
|
.await
|
|
}
|