mirror of
https://github.com/kanidm/kanidm.git
synced 2025-06-01 22:03:54 +02:00
Compare commits
7 commits
2f9537dfb2
...
64196307bc
Author | SHA1 | Date | |
---|---|---|---|
|
64196307bc | ||
|
9611a7f976 | ||
|
490a6caa18 | ||
|
f61bab6631 | ||
|
036ac23151 | ||
|
7a825ccc6d | ||
|
7b40b094b5 |
book/src/integrations
libs/client/src
proto/src
server
core/src
lib/src
testkit/tests
tools/cli/src
|
@ -85,7 +85,7 @@ URL **(recommended)**
|
|||
|
||||
<dt>
|
||||
|
||||
[WebFinger URL **(discouraged)**](#webfinger)
|
||||
[WebFinger URL](#webfinger) **(discouraged)**
|
||||
|
||||
</dt>
|
||||
|
||||
|
@ -458,58 +458,84 @@ Each client has unique signing keys and access secrets, so this is limited to ea
|
|||
|
||||
## WebFinger
|
||||
|
||||
[WebFinger](https://datatracker.ietf.org/doc/html/rfc7033) provides a mechanism
|
||||
for discovering information about people or other entities. It can be used by an
|
||||
identity provider to supply OpenID Connect discovery information.
|
||||
[WebFinger][webfinger] provides a mechanism for discovering information about
|
||||
entities at a well-known URL (`https://{hostname}/.well-known/webfinger`).
|
||||
|
||||
Kanidm provides
|
||||
[an Identity Provider Discovery for OIDC URL](https://datatracker.ietf.org/doc/html/rfc7033#section-3.1)
|
||||
response to all incoming WebFinger requests, using a user's SPN as their account
|
||||
ID. This does not match on email addresses as they are not guaranteed to be
|
||||
unique.
|
||||
It can be used by a WebFinger client to
|
||||
[discover the OIDC issuer URL][webfinger-oidc] of an identity provider from the
|
||||
hostname alone, and seems to be intended to support dynamic client registration
|
||||
flows for large public identity providers.
|
||||
|
||||
However, WebFinger has a number of flaws which make it difficult to use with
|
||||
Kanidm:
|
||||
Kanidm v1.5.1 and later can respond to WebFinger requests, using a user's SPN as
|
||||
part of [an `acct` URI][rfc7565] (eg: `acct:user@idm.example.com`). While SPNs
|
||||
and `acct` URIs look like email addresses, [as per RFC 7565][rfc7565s4], there
|
||||
is no guarantee that it is valid for any particular application protocol, unless
|
||||
an administrator explicitly provides for it.
|
||||
|
||||
When setting up an application to authenticate with Kanidm, WebFinger **does not
|
||||
add any security** over configuring an OpenID Discovery URL directly. In an OIDC
|
||||
context, the specification makes a number of flawed assumptions which make it
|
||||
difficult to use with Kanidm:
|
||||
|
||||
* WebFinger assumes that the identity provider will give the same `iss`
|
||||
(Issuer) for every OAuth 2.0/OIDC client, and there is no standard way for a
|
||||
WebFinger client to report its client ID.
|
||||
(issuer) and OpenID Discovery document, including all URLs and signing keys,
|
||||
for *all* OAuth 2.0/OIDC clients.
|
||||
|
||||
Kanidm uses a *different* `iss` (Issuer) value for each client.
|
||||
Kanidm uses *different* `iss` (issuer), signing keys, and some client-specific
|
||||
endpoint URLs, which ensures that tokens can only be used with their intended
|
||||
service.
|
||||
|
||||
* WebFinger requires that this be served at the *root* of the domain of a user's
|
||||
* WebFinger endpoints must be served at the *root* of the domain of a user's
|
||||
SPN (ie: information about the user with SPN `user@idm.example.com` is at
|
||||
`https://idm.example.com/.well-known/webfinger`).
|
||||
`https://idm.example.com/.well-known/webfinger?resource=acct%3Auser%40idm.example.com`).
|
||||
|
||||
Kanidm *does not* provide a WebFinger endpoint at its root URL, because it has
|
||||
no way to know *which* OAuth 2.0/OIDC client a WebFinger request is associated
|
||||
with, so could report an incorrect `iss` (Issuer).
|
||||
Unlike OIDC Discovery, WebFinger clients do not report their OAuth 2.0/OIDC
|
||||
client ID in the request, so there is no way to tell them apart.
|
||||
|
||||
You will need a load balancer in front of Kanidm's HTTPS server to redirect
|
||||
requests to the appropriate `/oauth2/openid/:client_id:/.well-known/webfinger`
|
||||
URL. If the client does not follow redirects, you may need to rewrite the
|
||||
request in the load balancer instead.
|
||||
As a result, Kanidm *does not* provide a WebFinger endpoint at its root URL,
|
||||
because it could report an incorrect `iss` (issuer) and lead the client to an
|
||||
incorrect OIDC discovery document.
|
||||
|
||||
You will need a load balancer in front of Kanidm's HTTPS server to send a HTTP
|
||||
307 redirect to the appropriate
|
||||
`/oauth2/openid/:client_id:/.well-known/webfinger` URL, *while preserving all
|
||||
query parameters*. For example, with Caddy:
|
||||
|
||||
```caddy
|
||||
# Match on a prefix, and use {uri} to preserve all query parameters.
|
||||
# This only supports *one* client.
|
||||
example.com {
|
||||
redir /.well-known/webfinger https://idm.example.com/oauth2/openid/:client_id:{uri} 307
|
||||
}
|
||||
```
|
||||
|
||||
If you have *multiple* WebFinger clients, it will need to map some other
|
||||
property of the request (such as a source IP address or `User-Agent` header)
|
||||
to a client ID, and redirect to the appropriate WebFinger URL for that client.
|
||||
|
||||
* Kanidm responds to *all* WebFinger queries with
|
||||
[an Identity Provider Discovery for OIDC URL](https://datatracker.ietf.org/doc/html/rfc7033#section-3.1),
|
||||
**regardless** of what
|
||||
[`rel` parameter](https://datatracker.ietf.org/doc/html/rfc7033#section-4.4.4.1)
|
||||
was specified.
|
||||
|
||||
This is to work around
|
||||
[a broken client](https://tailscale.com/kb/1240/sso-custom-oidc) which doesn't
|
||||
send a `rel` parameter, but expects an Identity Provider Discovery issuer URL
|
||||
in response.
|
||||
[an Identity Provider Discovery for OIDC URL][webfinger-oidc], **ignoring**
|
||||
any supplied [`rel` parameter][webfinger-rel].
|
||||
|
||||
If you want to use WebFinger in any *other* context on Kanidm's hostname,
|
||||
you'll need a load balancer in front of Kanidm which matches on some property
|
||||
of the request.
|
||||
|
||||
Because of the flaws of the WebFinger specification and the deployment
|
||||
difficulties they introduce, we recommend that applications use OpenID Connect
|
||||
Discovery or OAuth 2.0 Authorisation Server Metadata for client configuration
|
||||
instead of WebFinger.
|
||||
WebFinger clients *may* omit the `rel=` parameter, so if you host another
|
||||
service with relations for a Kanidm [`acct:` entity][rfc7565s4] and a client
|
||||
*does not* supply the `rel=` parameter, your load balancer will need to merge
|
||||
JSON responses from Kanidm and the other service(s).
|
||||
|
||||
Because of these issues, we recommend that applications support *directly*
|
||||
configuring OIDC using a Discovery URL or OAuth 2.0 Authorisation Server
|
||||
Metadata URL instead of WebFinger.
|
||||
|
||||
If a WebFinger client only checks WebFinger once during setup, you may wish to
|
||||
temporarily serve an appropriate static WebFinger document for that client
|
||||
instead.
|
||||
|
||||
[rfc7565]: https://datatracker.ietf.org/doc/html/rfc7565
|
||||
[rfc7565s4]: https://datatracker.ietf.org/doc/html/rfc7565#section-4
|
||||
[webfinger]: https://datatracker.ietf.org/doc/html/rfc7033
|
||||
[webfinger-oidc]: https://datatracker.ietf.org/doc/html/rfc7033#section-3.1
|
||||
[webfinger-rel]: https://datatracker.ietf.org/doc/html/rfc7033#section-4.3
|
||||
|
|
|
@ -30,8 +30,8 @@ use compact_jwt::Jwk;
|
|||
use kanidm_proto::constants::uri::V1_AUTH_VALID;
|
||||
use kanidm_proto::constants::{
|
||||
ATTR_DOMAIN_DISPLAY_NAME, ATTR_DOMAIN_LDAP_BASEDN, ATTR_DOMAIN_SSID, ATTR_ENTRY_MANAGED_BY,
|
||||
ATTR_KEY_ACTION_REVOKE, ATTR_LDAP_ALLOW_UNIX_PW_BIND, ATTR_NAME, CLIENT_TOKEN_CACHE, KOPID,
|
||||
KSESSIONID, KVERSION,
|
||||
ATTR_KEY_ACTION_REVOKE, ATTR_LDAP_ALLOW_UNIX_PW_BIND, ATTR_LDAP_MAX_QUERYABLE_ATTRS, ATTR_NAME,
|
||||
CLIENT_TOKEN_CACHE, KOPID, KSESSIONID, KVERSION,
|
||||
};
|
||||
use kanidm_proto::internal::*;
|
||||
use kanidm_proto::v1::*;
|
||||
|
@ -2082,6 +2082,18 @@ impl KanidmClient {
|
|||
.await
|
||||
}
|
||||
|
||||
/// Sets the maximum number of LDAP attributes that can be queryed in a single operation
|
||||
pub async fn idm_domain_set_ldap_max_queryable_attrs(
|
||||
&self,
|
||||
max_queryable_attrs: usize,
|
||||
) -> Result<(), ClientError> {
|
||||
self.perform_put_request(
|
||||
&format!("/v1/domain/_attr/{}", ATTR_LDAP_MAX_QUERYABLE_ATTRS),
|
||||
vec![max_queryable_attrs.to_string()],
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn idm_set_ldap_allow_unix_password_bind(
|
||||
&self,
|
||||
enable: bool,
|
||||
|
|
|
@ -94,6 +94,7 @@ pub enum Attribute {
|
|||
LdapEmailAddress,
|
||||
/// An LDAP Compatible sshkeys virtual attribute
|
||||
LdapKeys,
|
||||
LdapMaxQueryableAttrs,
|
||||
LegalName,
|
||||
LimitSearchMaxResults,
|
||||
LimitSearchMaxFilterTest,
|
||||
|
@ -322,6 +323,7 @@ impl Attribute {
|
|||
Attribute::LdapAllowUnixPwBind => ATTR_LDAP_ALLOW_UNIX_PW_BIND,
|
||||
Attribute::LdapEmailAddress => ATTR_LDAP_EMAIL_ADDRESS,
|
||||
Attribute::LdapKeys => ATTR_LDAP_KEYS,
|
||||
Attribute::LdapMaxQueryableAttrs => ATTR_LDAP_MAX_QUERYABLE_ATTRS,
|
||||
Attribute::LdapSshPublicKey => ATTR_LDAP_SSHPUBLICKEY,
|
||||
Attribute::LegalName => ATTR_LEGALNAME,
|
||||
Attribute::LimitSearchMaxResults => ATTR_LIMIT_SEARCH_MAX_RESULTS,
|
||||
|
@ -505,6 +507,7 @@ impl Attribute {
|
|||
ATTR_LDAP_ALLOW_UNIX_PW_BIND => Attribute::LdapAllowUnixPwBind,
|
||||
ATTR_LDAP_EMAIL_ADDRESS => Attribute::LdapEmailAddress,
|
||||
ATTR_LDAP_KEYS => Attribute::LdapKeys,
|
||||
ATTR_LDAP_MAX_QUERYABLE_ATTRS => Attribute::LdapMaxQueryableAttrs,
|
||||
ATTR_SSH_PUBLICKEY => Attribute::SshPublicKey,
|
||||
ATTR_LEGALNAME => Attribute::LegalName,
|
||||
ATTR_LINKEDGROUP => Attribute::LinkedGroup,
|
||||
|
|
|
@ -39,6 +39,8 @@ pub const DEFAULT_SERVER_ADDRESS: &str = "127.0.0.1:8443";
|
|||
pub const DEFAULT_SERVER_LOCALHOST: &str = "localhost:8443";
|
||||
/// The default LDAP bind address for the Kanidm client
|
||||
pub const DEFAULT_LDAP_LOCALHOST: &str = "localhost:636";
|
||||
/// The default amount of attributes that can be queried in LDAP
|
||||
pub const DEFAULT_LDAP_MAXIMUM_QUERYABLE_ATTRIBUTES: usize = 16;
|
||||
/// Default replication configuration
|
||||
pub const DEFAULT_REPLICATION_ADDRESS: &str = "127.0.0.1:8444";
|
||||
pub const DEFAULT_REPLICATION_ORIGIN: &str = "repl://localhost:8444";
|
||||
|
@ -102,6 +104,7 @@ pub const ATTR_DYNGROUP_FILTER: &str = "dyngroup_filter";
|
|||
pub const ATTR_DYNGROUP: &str = "dyngroup";
|
||||
pub const ATTR_DYNMEMBER: &str = "dynmember";
|
||||
pub const ATTR_LDAP_EMAIL_ADDRESS: &str = "emailaddress";
|
||||
pub const ATTR_LDAP_MAX_QUERYABLE_ATTRS: &str = "ldap_max_queryable_attrs";
|
||||
pub const ATTR_EMAIL_ALTERNATIVE: &str = "emailalternative";
|
||||
pub const ATTR_EMAIL_PRIMARY: &str = "emailprimary";
|
||||
pub const ATTR_EMAIL: &str = "email";
|
||||
|
|
|
@ -116,7 +116,6 @@ pub struct ServerConfig {
|
|||
///
|
||||
/// If unset, the LDAP server will be disabled.
|
||||
pub ldapbindaddress: Option<String>,
|
||||
|
||||
/// The role of this server, one of write_replica, write_replica_no_ui, read_only_replica, defaults to [ServerRole::WriteReplica]
|
||||
#[serde(default)]
|
||||
pub role: ServerRole,
|
||||
|
|
|
@ -1045,6 +1045,7 @@ lazy_static! {
|
|||
Attribute::DomainDisplayName,
|
||||
Attribute::DomainName,
|
||||
Attribute::DomainLdapBasedn,
|
||||
Attribute::LdapMaxQueryableAttrs,
|
||||
Attribute::DomainSsid,
|
||||
Attribute::DomainUuid,
|
||||
// Grants read access to the key object.
|
||||
|
@ -1058,6 +1059,7 @@ lazy_static! {
|
|||
Attribute::DomainDisplayName,
|
||||
Attribute::DomainSsid,
|
||||
Attribute::DomainLdapBasedn,
|
||||
Attribute::LdapMaxQueryableAttrs,
|
||||
Attribute::LdapAllowUnixPwBind,
|
||||
Attribute::KeyActionRevoke,
|
||||
Attribute::KeyActionRotate,
|
||||
|
@ -1065,6 +1067,7 @@ lazy_static! {
|
|||
modify_present_attrs: vec![
|
||||
Attribute::DomainDisplayName,
|
||||
Attribute::DomainLdapBasedn,
|
||||
Attribute::LdapMaxQueryableAttrs,
|
||||
Attribute::DomainSsid,
|
||||
Attribute::LdapAllowUnixPwBind,
|
||||
Attribute::KeyActionRevoke,
|
||||
|
@ -1100,6 +1103,7 @@ lazy_static! {
|
|||
Attribute::DomainDisplayName,
|
||||
Attribute::DomainName,
|
||||
Attribute::DomainLdapBasedn,
|
||||
Attribute::LdapMaxQueryableAttrs,
|
||||
Attribute::DomainSsid,
|
||||
Attribute::DomainUuid,
|
||||
Attribute::KeyInternalData,
|
||||
|
@ -1111,6 +1115,7 @@ lazy_static! {
|
|||
Attribute::DomainDisplayName,
|
||||
Attribute::DomainSsid,
|
||||
Attribute::DomainLdapBasedn,
|
||||
Attribute::LdapMaxQueryableAttrs,
|
||||
Attribute::LdapAllowUnixPwBind,
|
||||
Attribute::KeyActionRevoke,
|
||||
Attribute::KeyActionRotate,
|
||||
|
@ -1119,6 +1124,7 @@ lazy_static! {
|
|||
modify_present_attrs: vec![
|
||||
Attribute::DomainDisplayName,
|
||||
Attribute::DomainLdapBasedn,
|
||||
Attribute::LdapMaxQueryableAttrs,
|
||||
Attribute::DomainSsid,
|
||||
Attribute::LdapAllowUnixPwBind,
|
||||
Attribute::KeyActionRevoke,
|
||||
|
@ -1156,6 +1162,7 @@ lazy_static! {
|
|||
Attribute::DomainDisplayName,
|
||||
Attribute::DomainName,
|
||||
Attribute::DomainLdapBasedn,
|
||||
Attribute::LdapMaxQueryableAttrs,
|
||||
Attribute::DomainSsid,
|
||||
Attribute::DomainUuid,
|
||||
Attribute::KeyInternalData,
|
||||
|
@ -1167,6 +1174,7 @@ lazy_static! {
|
|||
Attribute::DomainDisplayName,
|
||||
Attribute::DomainSsid,
|
||||
Attribute::DomainLdapBasedn,
|
||||
Attribute::LdapMaxQueryableAttrs,
|
||||
Attribute::DomainAllowEasterEggs,
|
||||
Attribute::LdapAllowUnixPwBind,
|
||||
Attribute::KeyActionRevoke,
|
||||
|
@ -1176,6 +1184,7 @@ lazy_static! {
|
|||
modify_present_attrs: vec![
|
||||
Attribute::DomainDisplayName,
|
||||
Attribute::DomainLdapBasedn,
|
||||
Attribute::LdapMaxQueryableAttrs,
|
||||
Attribute::DomainSsid,
|
||||
Attribute::DomainAllowEasterEggs,
|
||||
Attribute::LdapAllowUnixPwBind,
|
||||
|
|
|
@ -167,6 +167,17 @@ pub static ref SCHEMA_ATTR_DOMAIN_LDAP_BASEDN: SchemaAttribute = SchemaAttribute
|
|||
..Default::default()
|
||||
};
|
||||
|
||||
pub static ref SCHEMA_ATTR_LDAP_MAXIMUM_QUERYABLE_ATTRIBUTES: SchemaAttribute = SchemaAttribute {
|
||||
uuid: UUID_SCHEMA_ATTR_LDAP_MAXIMUM_QUERYABLE_ATTRIBUTES,
|
||||
name: Attribute::LdapMaxQueryableAttrs,
|
||||
description: "The maximum number of LDAP attributes that can be queried in one operation".to_string(),
|
||||
|
||||
multivalue: false,
|
||||
sync_allowed: true,
|
||||
syntax: SyntaxType::Uint32,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
pub static ref SCHEMA_ATTR_DOMAIN_DISPLAY_NAME: SchemaAttribute = SchemaAttribute {
|
||||
uuid: UUID_SCHEMA_ATTR_DOMAIN_DISPLAY_NAME,
|
||||
name: Attribute::DomainDisplayName,
|
||||
|
@ -1227,6 +1238,7 @@ pub static ref SCHEMA_CLASS_DOMAIN_INFO_DL10: SchemaClass = SchemaClass {
|
|||
systemmay: vec![
|
||||
Attribute::DomainSsid,
|
||||
Attribute::DomainLdapBasedn,
|
||||
Attribute::LdapMaxQueryableAttrs,
|
||||
Attribute::LdapAllowUnixPwBind,
|
||||
Attribute::Image,
|
||||
Attribute::PatchLevel,
|
||||
|
|
|
@ -131,7 +131,8 @@ pub const UUID_SCHEMA_ATTR_PRIMARY_CREDENTIAL: Uuid = uuid!("00000000-0000-0000-
|
|||
pub const UUID_SCHEMA_CLASS_PERSON: Uuid = uuid!("00000000-0000-0000-0000-ffff00000044");
|
||||
pub const UUID_SCHEMA_CLASS_GROUP: Uuid = uuid!("00000000-0000-0000-0000-ffff00000045");
|
||||
pub const UUID_SCHEMA_CLASS_ACCOUNT: Uuid = uuid!("00000000-0000-0000-0000-ffff00000046");
|
||||
// GAP - 47
|
||||
pub const UUID_SCHEMA_ATTR_LDAP_MAXIMUM_QUERYABLE_ATTRIBUTES: Uuid =
|
||||
uuid!("00000000-0000-0000-0000-ffff00000187");
|
||||
pub const UUID_SCHEMA_ATTR_ATTRIBUTENAME: Uuid = uuid!("00000000-0000-0000-0000-ffff00000048");
|
||||
pub const UUID_SCHEMA_ATTR_CLASSNAME: Uuid = uuid!("00000000-0000-0000-0000-ffff00000049");
|
||||
pub const UUID_SCHEMA_ATTR_LEGALNAME: Uuid = uuid!("00000000-0000-0000-0000-ffff00000050");
|
||||
|
|
|
@ -60,6 +60,7 @@ pub struct LdapServer {
|
|||
basedn: String,
|
||||
dnre: Regex,
|
||||
binddnre: Regex,
|
||||
max_queryable_attrs: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -79,6 +80,12 @@ impl LdapServer {
|
|||
.qs_read
|
||||
.internal_search_uuid(UUID_DOMAIN_INFO)?;
|
||||
|
||||
// Get the maximum number of queryable attributes from the domain entry
|
||||
let max_queryable_attrs = domain_entry
|
||||
.get_ava_single_uint32(Attribute::LdapMaxQueryableAttrs)
|
||||
.map(|u| u as usize)
|
||||
.unwrap_or(DEFAULT_LDAP_MAXIMUM_QUERYABLE_ATTRIBUTES);
|
||||
|
||||
let basedn = domain_entry
|
||||
.get_ava_single_iutf8(Attribute::DomainLdapBasedn)
|
||||
.map(|s| s.to_string())
|
||||
|
@ -154,6 +161,7 @@ impl LdapServer {
|
|||
basedn,
|
||||
dnre,
|
||||
binddnre,
|
||||
max_queryable_attrs,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -239,11 +247,11 @@ impl LdapServer {
|
|||
let mut all_attrs = false;
|
||||
let mut all_op_attrs = false;
|
||||
|
||||
// TODO #3406: limit the number of attributes here!
|
||||
let attrs_len = sr.attrs.len();
|
||||
if sr.attrs.is_empty() {
|
||||
// If [], then "all" attrs
|
||||
all_attrs = true;
|
||||
} else {
|
||||
} else if attrs_len < self.max_queryable_attrs {
|
||||
sr.attrs.iter().for_each(|a| {
|
||||
if a == "*" {
|
||||
all_attrs = true;
|
||||
|
@ -267,6 +275,12 @@ impl LdapServer {
|
|||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
admin_error!(
|
||||
"Too many LDAP attributes requested. Maximum allowed is {}, while your search query had {}",
|
||||
self.max_queryable_attrs, attrs_len
|
||||
);
|
||||
return Err(OperationError::ResourceLimit);
|
||||
}
|
||||
|
||||
// We need to retain this to know what the client requested.
|
||||
|
@ -2631,4 +2645,106 @@ mod tests {
|
|||
&OperationError::InvalidAttributeName("invalid".to_string()),
|
||||
);
|
||||
}
|
||||
|
||||
#[idm_test]
|
||||
async fn test_ldap_maximum_queryable_attributes(
|
||||
idms: &IdmServer,
|
||||
_idms_delayed: &IdmServerDelayed,
|
||||
) {
|
||||
// Set the max queryable attrs to 2
|
||||
|
||||
let mut server_txn = idms.proxy_write(duration_from_epoch_now()).await.unwrap();
|
||||
|
||||
let set_ldap_maximum_queryable_attrs = ModifyEvent::new_internal_invalid(
|
||||
filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(UUID_DOMAIN_INFO))),
|
||||
ModifyList::new_purge_and_set(Attribute::LdapMaxQueryableAttrs, Value::Uint32(2)),
|
||||
);
|
||||
assert!(server_txn
|
||||
.qs_write
|
||||
.modify(&set_ldap_maximum_queryable_attrs)
|
||||
.and_then(|_| server_txn.commit())
|
||||
.is_ok());
|
||||
|
||||
let ldaps = LdapServer::new(idms).await.expect("failed to start ldap");
|
||||
|
||||
let usr_uuid = Uuid::new_v4();
|
||||
let grp_uuid = Uuid::new_v4();
|
||||
let app_uuid = Uuid::new_v4();
|
||||
let app_name = "testapp1";
|
||||
|
||||
// Setup person, group and application
|
||||
{
|
||||
let e1 = entry_init!(
|
||||
(Attribute::Class, EntryClass::Object.to_value()),
|
||||
(Attribute::Class, EntryClass::Account.to_value()),
|
||||
(Attribute::Class, EntryClass::Person.to_value()),
|
||||
(Attribute::Name, Value::new_iname("testperson1")),
|
||||
(Attribute::Uuid, Value::Uuid(usr_uuid)),
|
||||
(Attribute::Description, Value::new_utf8s("testperson1")),
|
||||
(Attribute::DisplayName, Value::new_utf8s("testperson1"))
|
||||
);
|
||||
|
||||
let e2 = entry_init!(
|
||||
(Attribute::Class, EntryClass::Object.to_value()),
|
||||
(Attribute::Class, EntryClass::Group.to_value()),
|
||||
(Attribute::Name, Value::new_iname("testgroup1")),
|
||||
(Attribute::Uuid, Value::Uuid(grp_uuid))
|
||||
);
|
||||
|
||||
let e3 = entry_init!(
|
||||
(Attribute::Class, EntryClass::Object.to_value()),
|
||||
(Attribute::Class, EntryClass::ServiceAccount.to_value()),
|
||||
(Attribute::Class, EntryClass::Application.to_value()),
|
||||
(Attribute::Name, Value::new_iname(app_name)),
|
||||
(Attribute::Uuid, Value::Uuid(app_uuid)),
|
||||
(Attribute::LinkedGroup, Value::Refer(grp_uuid))
|
||||
);
|
||||
|
||||
let ct = duration_from_epoch_now();
|
||||
let mut server_txn = idms.proxy_write(ct).await.unwrap();
|
||||
assert!(server_txn
|
||||
.qs_write
|
||||
.internal_create(vec![e1, e2, e3])
|
||||
.and_then(|_| server_txn.commit())
|
||||
.is_ok());
|
||||
}
|
||||
|
||||
// Setup the anonymous login
|
||||
let anon_t = ldaps.do_bind(idms, "", "").await.unwrap().unwrap();
|
||||
assert_eq!(
|
||||
anon_t.effective_session,
|
||||
LdapSession::UnixBind(UUID_ANONYMOUS)
|
||||
);
|
||||
|
||||
let invalid_search = SearchRequest {
|
||||
msgid: 1,
|
||||
base: "dc=example,dc=com".to_string(),
|
||||
scope: LdapSearchScope::Subtree,
|
||||
filter: LdapFilter::Present(Attribute::ObjectClass.to_string()),
|
||||
attrs: vec![
|
||||
"objectClass".to_string(),
|
||||
"cn".to_string(),
|
||||
"givenName".to_string(),
|
||||
],
|
||||
};
|
||||
|
||||
let valid_search = SearchRequest {
|
||||
msgid: 1,
|
||||
base: "dc=example,dc=com".to_string(),
|
||||
scope: LdapSearchScope::Subtree,
|
||||
filter: LdapFilter::Present(Attribute::ObjectClass.to_string()),
|
||||
attrs: vec!["objectClass: person".to_string()],
|
||||
};
|
||||
|
||||
let invalid_res: Result<Vec<LdapMsg>, OperationError> = ldaps
|
||||
.do_search(idms, &invalid_search, &anon_t, Source::Internal)
|
||||
.await;
|
||||
|
||||
let valid_res: Result<Vec<LdapMsg>, OperationError> = ldaps
|
||||
.do_search(idms, &valid_search, &anon_t, Source::Internal)
|
||||
.await;
|
||||
|
||||
assert_eq!(invalid_res, Err(OperationError::ResourceLimit));
|
||||
assert!(valid_res.is_ok());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ lazy_static! {
|
|||
// modification of some domain info types for local configuratiomn.
|
||||
Attribute::DomainSsid,
|
||||
Attribute::DomainLdapBasedn,
|
||||
Attribute::LdapMaxQueryableAttrs,
|
||||
Attribute::LdapAllowUnixPwBind,
|
||||
Attribute::FernetPrivateKeyStr,
|
||||
Attribute::Es256PrivateKeyDer,
|
||||
|
|
|
@ -430,6 +430,7 @@ impl QueryServerWriteTransaction<'_> {
|
|||
let idm_schema_changes = [
|
||||
SCHEMA_ATTR_DENIED_NAME_DL10.clone().into(),
|
||||
SCHEMA_CLASS_DOMAIN_INFO_DL10.clone().into(),
|
||||
SCHEMA_ATTR_LDAP_MAXIMUM_QUERYABLE_ATTRIBUTES.clone().into(),
|
||||
];
|
||||
|
||||
idm_schema_changes
|
||||
|
|
|
@ -26,6 +26,19 @@ async fn test_idm_domain_set_ldap_basedn(rsclient: KanidmClient) {
|
|||
.expect("Failed to set idm_domain_set_ldap_basedn");
|
||||
}
|
||||
|
||||
#[kanidmd_testkit::test]
|
||||
async fn test_idm_domain_set_ldap_max_queryable_attrs(rsclient: KanidmClient) {
|
||||
rsclient
|
||||
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
|
||||
.await
|
||||
.expect("Failed to login as admin");
|
||||
|
||||
rsclient
|
||||
.idm_domain_set_ldap_max_queryable_attrs(30)
|
||||
.await
|
||||
.expect("Failed to set idm_domain_set_ldap_max_queryable_attrs");
|
||||
}
|
||||
|
||||
#[kanidmd_testkit::test]
|
||||
async fn test_idm_domain_set_display_name(rsclient: KanidmClient) {
|
||||
rsclient
|
||||
|
|
|
@ -231,6 +231,19 @@ async fn test_idm_domain_set_ldap_basedn(rsclient: KanidmClient) {
|
|||
.is_err());
|
||||
}
|
||||
|
||||
#[kanidmd_testkit::test]
|
||||
async fn test_idm_domain_set_ldap_max_queryable_attrs(rsclient: KanidmClient) {
|
||||
login_put_admin_idm_admins(&rsclient).await;
|
||||
assert!(rsclient
|
||||
.idm_domain_set_ldap_max_queryable_attrs(20)
|
||||
.await
|
||||
.is_ok());
|
||||
assert!(rsclient
|
||||
.idm_domain_set_ldap_max_queryable_attrs(10)
|
||||
.await
|
||||
.is_ok()); // Ideally this should be "is_err"
|
||||
}
|
||||
|
||||
#[kanidmd_testkit::test]
|
||||
/// Checks that a built-in group idm_all_persons has the "builtin" class as expected.
|
||||
async fn test_all_persons_has_builtin_class(rsclient: KanidmClient) {
|
||||
|
|
|
@ -14,7 +14,8 @@ impl DomainOpt {
|
|||
| DomainOpt::SetLdapAllowUnixPasswordBind { copt, .. }
|
||||
| DomainOpt::SetAllowEasterEggs { copt, .. }
|
||||
| DomainOpt::RevokeKey { copt, .. }
|
||||
| DomainOpt::Show(copt) => copt.debug,
|
||||
| DomainOpt::Show(copt)
|
||||
| DomainOpt::SetLdapMaxQueryableAttrs { copt, .. } => copt.debug,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -34,6 +35,23 @@ impl DomainOpt {
|
|||
Err(e) => handle_client_error(e, opt.copt.output_mode),
|
||||
}
|
||||
}
|
||||
DomainOpt::SetLdapMaxQueryableAttrs {
|
||||
copt,
|
||||
new_max_queryable_attrs,
|
||||
} => {
|
||||
eprintln!(
|
||||
"Attempting to set the maximum number of queryable LDAP attributes to: {:?}",
|
||||
new_max_queryable_attrs
|
||||
);
|
||||
let client = copt.to_client(OpType::Write).await;
|
||||
match client
|
||||
.idm_domain_set_ldap_max_queryable_attrs(*new_max_queryable_attrs)
|
||||
.await
|
||||
{
|
||||
Ok(_) => println!("Success"),
|
||||
Err(e) => handle_client_error(e, copt.output_mode),
|
||||
}
|
||||
}
|
||||
DomainOpt::SetLdapBasedn { copt, new_basedn } => {
|
||||
eprintln!(
|
||||
"Attempting to set the domain's ldap basedn to: {:?}",
|
||||
|
|
|
@ -205,7 +205,6 @@ pub enum GroupAccountPolicyOpt {
|
|||
copt: CommonOpt,
|
||||
},
|
||||
|
||||
|
||||
/// Set the maximum time for privilege session expiry in seconds.
|
||||
#[clap(name = "privilege-expiry")]
|
||||
PrivilegedSessionExpiry {
|
||||
|
@ -215,7 +214,6 @@ pub enum GroupAccountPolicyOpt {
|
|||
copt: CommonOpt,
|
||||
},
|
||||
|
||||
|
||||
/// The WebAuthn attestation CA list that should be enforced
|
||||
/// on members of this group. Prevents use of passkeys that are
|
||||
/// not in this list. To create this list, use `fido-mds-tool`
|
||||
|
@ -301,7 +299,6 @@ pub enum GroupAccountPolicyOpt {
|
|||
#[clap(flatten)]
|
||||
copt: CommonOpt,
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
|
@ -1320,6 +1317,14 @@ pub enum DomainOpt {
|
|||
#[clap[name = "set-displayname"]]
|
||||
/// Set the domain display name
|
||||
SetDisplayname(OptSetDomainDisplayname),
|
||||
/// Sets the maximum number of LDAP attributes that can be queried in one operation.
|
||||
#[clap[name = "set-ldap-queryable-attrs"]]
|
||||
SetLdapMaxQueryableAttrs {
|
||||
#[clap(flatten)]
|
||||
copt: CommonOpt,
|
||||
#[clap(name = "maximum-queryable-attrs")]
|
||||
new_max_queryable_attrs: usize,
|
||||
},
|
||||
#[clap[name = "set-ldap-basedn"]]
|
||||
/// Change the basedn of this server. Takes effect after a server restart.
|
||||
/// Examples are `o=organisation` or `dc=domain,dc=name`. Must be a valid ldap
|
||||
|
|
Loading…
Reference in a new issue