11use std:: {
22 collections:: { BTreeMap , HashMap } ,
3- fmt:: Display ,
3+ fmt:: { Display , Write } ,
44 io:: { Cursor , Read } ,
55 num:: ParseIntError ,
66 str:: FromStr ,
@@ -90,13 +90,19 @@ const LDAP_FIELD_USER_NAME: &str = "userPrincipalName";
9090const LDAP_FIELD_USER_PRIMARY_GROUP_RID : & str = "primaryGroupID" ;
9191const LDAP_FIELD_GROUP_MEMBER : & str = "member" ;
9292
93- #[ tracing:: instrument( skip( tls, base_distinguished_name, custom_attribute_mappings) ) ]
93+ #[ tracing:: instrument( skip(
94+ tls,
95+ base_distinguished_name,
96+ custom_attribute_mappings,
97+ additional_group_attribute_filters,
98+ ) ) ]
9499pub ( crate ) async fn get_user_info (
95100 request : & UserInfoRequest ,
96101 ldap_server : & str ,
97102 tls : & TlsClientDetails ,
98103 base_distinguished_name : & str ,
99104 custom_attribute_mappings : & BTreeMap < String , String > ,
105+ additional_group_attribute_filters : & BTreeMap < String , String > ,
100106) -> Result < UserInfo , Error > {
101107 let ldap_tls = utils:: tls:: configure_native_tls ( tls)
102108 . await
@@ -168,16 +174,27 @@ pub(crate) async fn get_user_info(
168174 base_distinguished_name,
169175 & user,
170176 custom_attribute_mappings,
177+ additional_group_attribute_filters,
171178 )
172179 . await
173180}
174181
175- #[ tracing:: instrument( skip( ldap, base_dn, user, custom_attribute_mappings) , fields( user. dn) ) ]
182+ #[ tracing:: instrument(
183+ skip(
184+ ldap,
185+ base_dn,
186+ user,
187+ custom_attribute_mappings,
188+ additional_group_attribute_filters,
189+ ) ,
190+ fields( user. dn) ,
191+ ) ]
176192async fn user_attributes (
177193 ldap : & mut Ldap ,
178194 base_dn : & str ,
179195 user : & SearchEntry ,
180196 custom_attribute_mappings : & BTreeMap < String , String > ,
197+ additional_group_attribute_filters : & BTreeMap < String , String > ,
181198) -> Result < UserInfo , Error > {
182199 let user_sid = user
183200 . bin_attrs
@@ -242,7 +259,14 @@ async fn user_attributes(
242259 } )
243260 . collect :: < HashMap < _ , _ > > ( ) ;
244261 let groups = if let Some ( user_sid) = & user_sid {
245- user_group_distinguished_names ( ldap, base_dn, user, user_sid) . await ?
262+ user_group_distinguished_names (
263+ ldap,
264+ base_dn,
265+ user,
266+ user_sid,
267+ additional_group_attribute_filters,
268+ )
269+ . await ?
246270 } else {
247271 tracing:: debug!( user. dn, "user has no SID, cannot fetch groups..." ) ;
248272 Vec :: new ( )
@@ -257,12 +281,13 @@ async fn user_attributes(
257281}
258282
259283/// Gets the distinguished names of all of `user`'s groups, both primary and secondary.
260- #[ tracing:: instrument( skip( ldap, base_dn, user, user_sid) ) ]
284+ #[ tracing:: instrument( skip( ldap, base_dn, user, user_sid, additional_group_attribute_filters ) ) ]
261285async fn user_group_distinguished_names (
262286 ldap : & mut Ldap ,
263287 base_dn : & str ,
264288 user : & SearchEntry ,
265289 user_sid : & SecurityId ,
290+ additional_group_attribute_filters : & BTreeMap < String , String > ,
266291) -> Result < Vec < String > , Error > {
267292 // User group memberships are tricky, because users have exactly one *primary* and any number of *secondary* groups.
268293 // Additionally groups can be members of other groups.
@@ -309,10 +334,22 @@ async fn user_group_distinguished_names(
309334 "({LDAP_FIELD_GROUP_MEMBER}{LDAP_MATCHING_RULE_IN_CHAIN}=<SID={primary_group_sid}>)"
310335 ) ;
311336
337+ // Users can also specify custom filters via `group_attribute_filters`
338+ let custom_group_filter =
339+ additional_group_attribute_filters
340+ . iter ( )
341+ . fold ( String :: new ( ) , |mut out, ( k, v) | {
342+ // NOTE: This is technically an LDAP injection vuln, but these are provided statically by the OPA administrator,
343+ // who would be able to do plenty of other harm... (like providing their own OPA images that do whatever they want).
344+ // We could base64 the value to "defuse" it entirely, but that would also prevent using wildcards.
345+ write ! ( out, "({k}={v})" ) . expect ( "string concatenation is infallible" ) ;
346+ out
347+ } ) ;
348+
312349 // Let's put it all together, and make it go...
313350 let groups_filter =
314351 format ! ( "(|{primary_group_filter}{primary_group_parents_filter}{secondary_groups_filter})" ) ;
315- let groups_query_filter = format ! ( "(&(objectClass=group){groups_filter})" ) ;
352+ let groups_query_filter = format ! ( "(&(objectClass=group){custom_group_filter}{ groups_filter})" ) ;
316353 let requested_group_attrs = [ LDAP_FIELD_OBJECT_DISTINGUISHED_NAME ] ;
317354 tracing:: debug!(
318355 groups_query_filter,
0 commit comments