server, app: Add validation for attribute names (#1075)

This commit adds support for basic validation of attribute
names at creation, and also in the schema overview. Both
user and group attributes are validated with the same rules.

For now, attribute names will be considered valid, if they
only contain alphanumeric characters and dashes.

Validation has been added the following places:

- In graphql API, for creation of both user and group attributes.
  Request will be rejected, if attribute name is invalid.

- In frontend, before submitting a request to create a new user
  or group attribute. Rejection here will show an error message
  including a list of the invalid characters used.

As this change adds stricter validation to attributes, and, since
the rationale for this is partly compatibility with other LDAP
systems, this change also adds a warning in the schema overviews
to any attribute using invalid characters.
This commit is contained in:
Simon Broeng Jensen
2025-01-22 09:57:47 +01:00
committed by GitHub
parent 31a0cf5a4f
commit f5fbb31e6e
12 changed files with 401 additions and 32 deletions
@@ -12,6 +12,7 @@ use crate::{
use anyhow::{bail, Result};
use gloo_console::log;
use graphql_client::GraphQLQuery;
use lldap_validation::attributes::validate_attribute_name;
use validator_derive::Validate;
use yew::prelude::*;
use yew_form_derive::Model;
@@ -62,6 +63,13 @@ impl CommonComponent<CreateGroupAttributeForm> for CreateGroupAttributeForm {
bail!("Check the form for errors");
}
let model = self.form.model();
validate_attribute_name(&model.attribute_name).or_else(|invalid_chars| {
let invalid = String::from_iter(invalid_chars);
bail!(
"Attribute name contains one or more invalid characters: {}",
invalid
);
})?;
let attribute_type = model.attribute_type.parse::<AttributeType>().unwrap();
let req = create_group_attribute::Variables {
name: model.attribute_name,
@@ -12,6 +12,7 @@ use crate::{
use anyhow::{bail, Result};
use gloo_console::log;
use graphql_client::GraphQLQuery;
use lldap_validation::attributes::validate_attribute_name;
use validator_derive::Validate;
use yew::prelude::*;
use yew_form_derive::Model;
@@ -66,6 +67,13 @@ impl CommonComponent<CreateUserAttributeForm> for CreateUserAttributeForm {
if model.is_editable && !model.is_visible {
bail!("Editable attributes must also be visible");
}
validate_attribute_name(&model.attribute_name).or_else(|invalid_chars| {
let invalid = String::from_iter(invalid_chars);
bail!(
"Attribute name contains one or more invalid characters: {}",
invalid
);
})?;
let attribute_type = model.attribute_type.parse::<AttributeType>().unwrap();
let req = create_user_attribute::Variables {
name: model.attribute_name,
@@ -1,36 +1,52 @@
use crate::infra::attributes::AttributeDescription;
use lldap_validation::attributes::{validate_attribute_name, ALLOWED_CHARACTERS_DESCRIPTION};
use yew::{html, Html};
use crate::infra::attributes::AttributeDescription;
pub fn render_attribute_name(
hardcoded: bool,
attribute_identifier: &String,
attribute_description: &AttributeDescription,
) -> Html {
if hardcoded {
html! {
<>
{&attribute_description.attribute_name}
{
if attribute_description.aliases.is_empty() {
html!{}
} else {
html!{
<>
<br/>
<small class="text-muted">
{"Aliases: "}
{attribute_description.aliases.join(", ")}
</small>
</>
}
}
}
</>
}
fn render_attribute_aliases(attribute_description: &AttributeDescription) -> Html {
if attribute_description.aliases.is_empty() {
html! {}
} else {
html! {
{&attribute_identifier}
<>
<br/>
<small class="text-muted">
{"Aliases: "}
{attribute_description.aliases.join(", ")}
</small>
</>
}
}
}
fn render_attribute_validation_warnings(attribute_name: &str) -> Html {
match validate_attribute_name(attribute_name) {
Ok(()) => {
html! {}
}
Err(_invalid_chars) => {
html! {
<>
<br/>
<small class="text-warning">
{"Warning: This attribute uses one or more invalid characters "}
{"("}{ALLOWED_CHARACTERS_DESCRIPTION}{"). "}
{"Some clients may not support it."}
</small>
</>
}
}
}
}
pub fn render_attribute_name(
hardcoded: bool,
attribute_description: &AttributeDescription,
) -> Html {
html! {
<>
{&attribute_description.attribute_name}
{if hardcoded {render_attribute_aliases(attribute_description)} else {html!{}}}
{render_attribute_validation_warnings(attribute_description.attribute_name)}
</>
}
}
+1 -1
View File
@@ -157,7 +157,7 @@ impl GroupSchemaTable {
let desc = group::resolve_group_attribute_description_or_default(&attribute.name);
html! {
<tr key={attribute.name.clone()}>
<td>{render_attribute_name(hardcoded, &attribute.name, &desc)}</td>
<td>{render_attribute_name(hardcoded, &desc)}</td>
<td>{if attribute.is_list { format!("List<{attribute_type}>")} else {attribute_type.to_string()}}</td>
<td>{if attribute.is_visible {checkmark.clone()} else {html!{}}}</td>
{
+1 -1
View File
@@ -156,7 +156,7 @@ impl UserSchemaTable {
let desc = user::resolve_user_attribute_description_or_default(&attribute.name);
html! {
<tr key={attribute.name.clone()}>
<td>{render_attribute_name(hardcoded, &attribute.name, &desc)}</td>
<td>{render_attribute_name(hardcoded, &desc)}</td>
<td>{if attribute.is_list { format!("List<{attribute_type}>")} else {attribute_type.to_string()}}</td>
<td>{if attribute.is_editable {checkmark.clone()} else {html!{}}}</td>
<td>{if attribute.is_visible {checkmark.clone()} else {html!{}}}</td>