2 Commits

Author SHA1 Message Date
Austin Alvarado
582c595edd putting a pin in it 2024-01-20 18:27:47 +00:00
elmodor
1f2f034a48 Added maddy example config
Updated README.md for Maddy

i
2024-01-18 22:01:57 +01:00
6 changed files with 213 additions and 1 deletions

View File

@@ -374,6 +374,7 @@ folder for help with:
- [Kasm](example_configs/kasm.md)
- [KeyCloak](example_configs/keycloak.md)
- [LibreNMS](example_configs/librenms.md)
- [Maddy](example_configs/maddy.md)
- [Mastodon](example_configs/mastodon.env.example)
- [Matrix](example_configs/matrix_synapse.yml)
- [Mealie](example_configs/mealie.md)

View File

@@ -8,5 +8,21 @@ query GetGroupDetails($id: Int!) {
id
displayName
}
attributes {
name
value
}
}
schema {
groupSchema {
attributes {
name
attributeType
isList
isVisible
isEditable
isHardcoded
}
}
}
}

View File

@@ -0,0 +1,83 @@
use std::ops::Deref;
use crate::{
components::{
group_details::Attribute,
router::{AppRoute, Link},
},
infra::common_component::{CommonComponent, CommonComponentParts},
};
use anyhow::{bail, Error, Result};
use gloo_console::log;
use graphql_client::GraphQLQuery;
use yew::prelude::*;
#[derive(Properties, PartialEq)]
pub struct AttributeInputProps {
pub attribute: Attribute,
pub on_changed: Callback<(String, Vec<String>)>,
}
#[function_component(SingleAttributeInput)]
fn single_attribute_input(props: &AttributeInputProps) -> Html {
let attribute = props.attribute.clone();
let on_changed = props.on_changed.clone();
let on_input = Callback::from(move |e: InputEvent| on_changed.emit((attribute.name.clone(), vec![e.data().unwrap_or_default()])));
html!{
<div class="row mb-3">
<label for={props.attribute.name.clone()}
class="form-label col-4 col-form-label">
{props.attribute.name.clone()}
{":"}
</label>
<div class="col-8">
<input id={props.attribute.name.clone()} name={props.attribute.name.clone()} type="text" class="form-control" oninput={on_input} />
</div>
</div>
}
}
#[function_component(ListAttributeInput)]
fn list_attribute_input(props: &AttributeInputProps) -> Html {
html!{}
}
#[function_component(AttributeInput)]
fn attribute_input(props: &AttributeInputProps) -> Html {
if props.attribute.is_list {
html!{
<ListAttributeInput
attribute={props.attribute.clone()}
on_changed={props.on_changed.clone()} />
}
} else {
html!{
<SingleAttributeInput
attribute={props.attribute.clone()}
on_changed={props.on_changed.clone()} />
}
}
}
#[derive(Properties, PartialEq)]
pub struct Props {
pub attributes: Vec<Attribute>,
}
#[function_component(GroupAttributesForm)]
pub fn group_attributes_form(Props{ attributes }: &Props) -> Html {
let attributes = use_state(|| attributes.clone());
let on_changed = {
let attributes = attributes.clone();
Callback::from(move |(name, value): (String, Vec<String>)| {
let mut new_attributes = attributes.deref().clone();
new_attributes.iter_mut().filter(|attribute| attribute.name == name).for_each(|attribute| attribute.value = value.clone());
attributes.set(new_attributes.clone());
log!("New attributes:");
new_attributes.iter().for_each(|attribute| log!("Name: {attribute.name}, Value: {attribute.value}"));
})
};
html!{
{for attributes.iter().map(|attribute| html!{<AttributeInput attribute={attribute.clone()} on_changed={on_changed.clone()} />})}
}
}

View File

@@ -2,6 +2,7 @@ use crate::{
components::{
add_group_member::{self, AddGroupMemberComponent},
remove_user_from_group::RemoveUserFromGroupComponent,
group_attributes_form::GroupAttributesForm,
router::{AppRoute, Link},
},
infra::common_component::{CommonComponent, CommonComponentParts},
@@ -22,12 +23,22 @@ pub struct GetGroupDetails;
pub type Group = get_group_details::GetGroupDetailsGroup;
pub type User = get_group_details::GetGroupDetailsGroupUsers;
pub type AddGroupMemberUser = add_group_member::User;
pub type AttributeSchema = get_group_details::GetGroupDetailsSchemaGroupSchemaAttributes;
#[derive(Clone, PartialEq, Eq)]
pub struct Attribute {
pub name: String,
pub value: Vec<String>,
pub attribute_type: String,
pub is_list: bool,
}
pub struct GroupDetails {
common: CommonComponentParts<Self>,
/// The group info. If none, the error is in `error`. If `error` is None, then we haven't
/// received the server response yet.
group: Option<Group>,
attributes: Vec<Attribute>,
}
/// State machine describing the possible transitions of the component state.
@@ -185,7 +196,22 @@ impl CommonComponent<GroupDetails> for GroupDetails {
fn handle_msg(&mut self, _: &Context<Self>, msg: <Self as Component>::Message) -> Result<bool> {
match msg {
Msg::GroupDetailsResponse(response) => match response {
Ok(group) => self.group = Some(group.group),
Ok(response) => {
let group = response.group;
self.group = Some(group.clone());
let set_attributes = group.attributes.clone();
let mut attribute_schema = response.schema.group_schema.attributes;
attribute_schema.retain(|schema| !schema.is_hardcoded);
let attributes = attribute_schema.into_iter().map(|schema| {
Attribute {
name: schema.name.clone(),
value: set_attributes.iter().find(|attribute_value| attribute_value.name == schema.name).unwrap().value.clone(),
attribute_type: format!("{:?}",schema.attribute_type),
is_list: schema.is_list,
}
}).collect();
self.attributes = attributes;
},
Err(e) => {
self.group = None;
bail!("Error getting user details: {}", e);
@@ -222,6 +248,7 @@ impl Component for GroupDetails {
let mut table = Self {
common: CommonComponentParts::<Self>::create(),
group: None,
attributes: Vec::default(),
};
table.get_group_details(ctx);
table
@@ -239,6 +266,7 @@ impl Component for GroupDetails {
html! {
<div>
{self.view_details(u)}
<GroupAttributesForm attributes={self.attributes.clone()} />
{self.view_user_list(ctx, u)}
{self.view_add_user_button(ctx, u)}
{self.view_messages(error)}

View File

@@ -6,6 +6,7 @@ pub mod create_group;
pub mod create_user;
pub mod delete_group;
pub mod delete_user;
pub mod group_attributes_form;
pub mod group_details;
pub mod group_table;
pub mod login;

83
example_configs/maddy.md Normal file
View File

@@ -0,0 +1,83 @@
# Configuration for Maddy Mail Server
Documentation for maddy LDAP can be found [here](https://maddy.email/reference/auth/ldap/).
Maddy will automatically create an imap-acct if a new user connects via LDAP.
Replace `dc=example,dc=com` with your LLDAP configured domain.
## Simple Setup
Depending on the mail client(s) the simple setup can work for you. However, if this does not work for you, follow the instructions in the `Advanced Setup` section.
### DN Template
You only have to specify the dn template:
```
dn_template "cn={username},ou=people,dc=example,dc=com"
```
### Config Example with Docker
Example maddy configuration with LLDAP running in docker.
You can replace `local_authdb` with another name if you want to use multiple auth backends.
If you only want to use one storage backend make sure to disable `auth.pass_table local_authdb` in your config if it is still active.
```
auth.ldap local_authdb {
urls ldap://lldap:3890
dn_template "cn={username},ou=people,dc=example,dc=com"
starttls off
debug off
connect_timeout 1m
}
```
## Advanced Setup
If the simple setup does not work for you, you can use a proper lookup.
### Bind Credentials
If you have a service account in LLDAP with restricted rights (e.g. `lldap_strict_readonly`), replace `admin` with your LLDAP service account.
Replace `admin_password` with the password of either the admin or service account.
```
bind plain "cn=admin,ou=people,dc=example,dc=com" "admin_password"
```
If you do not want to use plain auth check the [maddy LDAP page](https://maddy.email/reference/auth/ldap/) for other options.
### Base DN
```
base_dn "dc=example,dc=com"
```
### Filter
Depending on the mail client, maddy receives and sends either the username or the full E-Mail address as username (even if the username is not an E-Mail).
For the username use:
```
filter "(&(objectClass=person)(uid={username}))"
```
For mapping the username (as E-Mail):
```
filter "(&(objectClass=person)(mail={username}))"
```
For allowing both, username and username as E-Mail use:
```
filter "(&(|(uid={username})(mail={username}))(objectClass=person))"
```
### Config Example with Docker
Example maddy configuration with LLDAP running in docker.
You can replace `local_authdb` with another name if you want to use multiple auth backends.
If you only want to use one storage backend make sure to disable `auth.pass_table local_authdb` in your config if it is still active.
```
auth.ldap local_authdb {
urls ldap://lldap:3890
bind plain "cn=admin,ou=people,dc=example,dc=com" "admin_password"
base_dn "dc=example,dc=com"
filter "(&(|(uid={username})(mail={username}))(objectClass=person))"
starttls off
debug off
connect_timeout 1m
}
```