From: Ondřej Kuzník Date: Wed, 10 Dec 2025 12:40:11 +0000 (+0000) Subject: ITS#9640 Introduce the increment privilege X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=b8df68996957a9750816554227b24c15913a962e;p=thirdparty%2Fopenldap.git ITS#9640 Introduce the increment privilege --- diff --git a/doc/man/man5/slapd.access.5 b/doc/man/man5/slapd.access.5 index 0104ad9b01..870d713b7b 100644 --- a/doc/man/man5/slapd.access.5 +++ b/doc/man/man5/slapd.access.5 @@ -760,8 +760,8 @@ field will have. Its component are defined as .LP .nf - ::= none|disclose|auth|compare|search|read|{write|add|delete}|manage - ::= {=|+|\-}{0|d|x|c|s|r|{w|a|z}|m}+ + ::= none|disclose|auth|compare|search|read|{write|add|delete|increment}|manage + ::= {=|+|\-}{0|d|x|c|s|r|{w|a|z|i}|m}+ .fi .LP The modifier @@ -805,11 +805,12 @@ attribute that is defined as not user modifiable. The .BR write access is actually the combination of -.BR add -and +.BR add , .BR delete , -which respectively restrict the write privilege to add or delete -the specified +and +.BR increment , +which respectively restrict the write privilege to add, delete or +increment the specified .BR . .LP @@ -852,6 +853,8 @@ for write, for add, .B z for delete, +.B i +for increment, .B r for read, .B s @@ -866,7 +869,7 @@ More than one of the above privileges can be added in one statement. .B 0 indicates no privileges and is used only by itself (e.g., +0). Note that -.B +az +.B +azi is equivalent to .BR +w . .LP @@ -1013,6 +1016,8 @@ In detail, is required to add new values, .B delete (=z) is required to delete existing values, +.B increment (=i) +is required to increment values, and both .B delete and diff --git a/servers/slapd/acl.c b/servers/slapd/acl.c index f6f0587dff..6e841a7548 100644 --- a/servers/slapd/acl.c +++ b/servers/slapd/acl.c @@ -1967,8 +1967,21 @@ acl_check_modlist( } switch ( mlist->sml_op ) { - case LDAP_MOD_REPLACE: case LDAP_MOD_INCREMENT: + assert( mlist->sml_values != NULL ); + assert( BER_BVISNULL( &mlist->sml_values[1] ) ); + + if ( ! access_allowed( op, e, + mlist->sml_desc, &mlist->sml_values[0], + ( mlist->sml_flags & SLAP_MOD_MANAGING ) ? ACL_MANAGE : ACL_WINCR, + &state ) ) + { + ret = 0; + goto done; + } + break; + + case LDAP_MOD_REPLACE: /* * We must check both permission to delete the whole * attribute and permission to add the specific attributes. diff --git a/servers/slapd/aclparse.c b/servers/slapd/aclparse.c index b41951d469..5a0174f72c 100644 --- a/servers/slapd/aclparse.c +++ b/servers/slapd/aclparse.c @@ -1961,6 +1961,9 @@ accessmask2str( slap_mask_t mask, char *buf, int debug ) } else if ( ACL_LVL_IS_WDEL(mask) ) { ptr = lutil_strcopy( ptr, "delete" ); + } else if ( ACL_LVL_IS_WINCR(mask) ) { + ptr = lutil_strcopy( ptr, "increment" ); + } else if ( ACL_LVL_IS_MANAGE(mask) ) { ptr = lutil_strcopy( ptr, "manage" ); @@ -2001,6 +2004,10 @@ accessmask2str( slap_mask_t mask, char *buf, int debug ) } else if ( ACL_PRIV_ISSET(mask, ACL_PRIV_WDEL) ) { none = 0; *ptr++ = 'z'; + + } else if ( ACL_PRIV_ISSET(mask, ACL_PRIV_WINCR) ) { + none = 0; + *ptr++ = 'i'; } if ( ACL_PRIV_ISSET(mask, ACL_PRIV_READ) ) { @@ -2081,6 +2088,9 @@ str2accessmask( const char *str ) } else if( TOLOWER((unsigned char) str[i]) == 'z' ) { ACL_PRIV_SET(mask, ACL_PRIV_WDEL); + } else if( TOLOWER((unsigned char) str[i]) == 'i' ) { + ACL_PRIV_SET(mask, ACL_PRIV_WINCR); + } else if( TOLOWER((unsigned char) str[i]) == 'r' ) { ACL_PRIV_SET(mask, ACL_PRIV_READ); @@ -2132,6 +2142,9 @@ str2accessmask( const char *str ) } else if ( strcasecmp( str, "delete" ) == 0 ) { ACL_LVL_ASSIGN_WDEL(mask); + } else if ( strcasecmp( str, "increment" ) == 0 ) { + ACL_LVL_ASSIGN_WINCR(mask); + } else if ( strcasecmp( str, "write" ) == 0 ) { ACL_LVL_ASSIGN_WRITE(mask); @@ -2177,8 +2190,8 @@ acl_usage(void) " ::= exact | regex | ip | ipv6 | path\n" " ::= exact | regex | base(Object) | sub(tree)\n" " ::= [[real]self]{|}\n" - " ::= none|disclose|auth|compare|search|read|{write|add|delete}|manage\n" - " ::= {=|+|-}{0|d|x|c|s|r|{w|a|z}|m}+\n" + " ::= none|disclose|auth|compare|search|read|{write|add|delete|increment}|manage\n" + " ::= {=|+|-}{0|d|x|c|s|r|{w|a|z|i}|m}+\n" " ::= [ stop | continue | break ]\n" #ifdef SLAP_DYNACL #ifdef SLAPD_ACI_ENABLED @@ -2404,6 +2417,9 @@ access2str( slap_access_t access ) } else if ( access == ACL_WDEL ) { return "delete"; + } else if ( access == ACL_WDEL ) { + return "increment"; + } else if ( access == ACL_MANAGE ) { return "manage"; @@ -2442,6 +2458,9 @@ str2access( const char *str ) } else if ( strcasecmp( str, "delete" ) == 0 ) { return ACL_WDEL; + } else if ( strcasecmp( str, "increment" ) == 0 ) { + return ACL_WDEL; + } else if ( strcasecmp( str, "manage" ) == 0 ) { return ACL_MANAGE; } diff --git a/servers/slapd/overlays/constraint.c b/servers/slapd/overlays/constraint.c index f4ffcf7377..13af13a2f3 100644 --- a/servers/slapd/overlays/constraint.c +++ b/servers/slapd/overlays/constraint.c @@ -1069,7 +1069,8 @@ constraint_update( Operation *op, SlapReply *rs ) if ((( m->sml_op & LDAP_MOD_OP ) != LDAP_MOD_ADD) && (( m->sml_op & LDAP_MOD_OP ) != LDAP_MOD_REPLACE) && - (( m->sml_op & LDAP_MOD_OP ) != LDAP_MOD_DELETE)) + (( m->sml_op & LDAP_MOD_OP ) != LDAP_MOD_DELETE) && + (( m->sml_op & LDAP_MOD_OP ) != LDAP_MOD_INCREMENT)) continue; /* we only care about ADD and REPLACE modifications */ /* and DELETE are used to track attribute count */ diff --git a/servers/slapd/slap.h b/servers/slapd/slap.h index 5754d91277..1305b47b94 100644 --- a/servers/slapd/slap.h +++ b/servers/slapd/slap.h @@ -1280,8 +1280,9 @@ typedef enum slap_access_t { /* write granularity */ ACL_WADD = ACL_WRITE_|ACL_QUALIFIER1, ACL_WDEL = ACL_WRITE_|ACL_QUALIFIER2, + ACL_WINCR = ACL_WRITE_|ACL_QUALIFIER3, - ACL_WRITE = ACL_WADD|ACL_WDEL + ACL_WRITE = ACL_WADD|ACL_WDEL|ACL_WINCR } slap_access_t; typedef enum slap_control_e { @@ -1390,7 +1391,8 @@ typedef struct Access { #define ACL_PRIV_READ ACL_ACCESS2PRIV( ACL_READ ) #define ACL_PRIV_WADD ACL_ACCESS2PRIV( ACL_WADD ) #define ACL_PRIV_WDEL ACL_ACCESS2PRIV( ACL_WDEL ) -#define ACL_PRIV_WRITE ( ACL_PRIV_WADD | ACL_PRIV_WDEL ) +#define ACL_PRIV_WINCR ACL_ACCESS2PRIV( ACL_WINCR ) +#define ACL_PRIV_WRITE ( ACL_PRIV_WADD | ACL_PRIV_WDEL | ACL_PRIV_WINCR ) #define ACL_PRIV_MANAGE ACL_ACCESS2PRIV( ACL_MANAGE ) /* NOTE: always use the highest level; current: 0x00ffUL */ @@ -1428,6 +1430,7 @@ typedef struct Access { #define ACL_LVL_READ (ACL_PRIV_READ|ACL_LVL_SEARCH) #define ACL_LVL_WADD (ACL_PRIV_WADD|ACL_LVL_READ) #define ACL_LVL_WDEL (ACL_PRIV_WDEL|ACL_LVL_READ) +#define ACL_LVL_WINCR (ACL_PRIV_WINCR|ACL_LVL_READ) #define ACL_LVL_WRITE (ACL_PRIV_WRITE|ACL_LVL_READ) #define ACL_LVL_MANAGE (ACL_PRIV_MANAGE|ACL_LVL_WRITE) @@ -1440,6 +1443,7 @@ typedef struct Access { #define ACL_LVL_IS_READ(m) ACL_LVL((m),ACL_LVL_READ) #define ACL_LVL_IS_WADD(m) ACL_LVL((m),ACL_LVL_WADD) #define ACL_LVL_IS_WDEL(m) ACL_LVL((m),ACL_LVL_WDEL) +#define ACL_LVL_IS_WINCR(m) ACL_LVL((m),ACL_LVL_WINCR) #define ACL_LVL_IS_WRITE(m) ACL_LVL((m),ACL_LVL_WRITE) #define ACL_LVL_IS_MANAGE(m) ACL_LVL((m),ACL_LVL_MANAGE) @@ -1451,6 +1455,7 @@ typedef struct Access { #define ACL_LVL_ASSIGN_READ(m) ACL_PRIV_ASSIGN((m),ACL_LVL_READ) #define ACL_LVL_ASSIGN_WADD(m) ACL_PRIV_ASSIGN((m),ACL_LVL_WADD) #define ACL_LVL_ASSIGN_WDEL(m) ACL_PRIV_ASSIGN((m),ACL_LVL_WDEL) +#define ACL_LVL_ASSIGN_WINCR(m) ACL_PRIV_ASSIGN((m),ACL_LVL_WINCR) #define ACL_LVL_ASSIGN_WRITE(m) ACL_PRIV_ASSIGN((m),ACL_LVL_WRITE) #define ACL_LVL_ASSIGN_MANAGE(m) ACL_PRIV_ASSIGN((m),ACL_LVL_MANAGE) diff --git a/tests/data/acl.out.provider b/tests/data/acl.out.provider index 8fd99a621f..b6b1aa16c4 100644 --- a/tests/data/acl.out.provider +++ b/tests/data/acl.out.provider @@ -362,6 +362,6 @@ dn: ou=People,dc=example,dc=com objectClass: organizationalUnit objectClass: extensibleObject ou: People -uidNumber: 0 -gidNumber: 0 +uidNumber: 56 +gidNumber: 50 diff --git a/tests/data/slapd-acl.conf b/tests/data/slapd-acl.conf index 90bb9ba6b1..545da3710a 100644 --- a/tests/data/slapd-acl.conf +++ b/tests/data/slapd-acl.conf @@ -137,6 +137,18 @@ access to dn.subtree="ou=Add & Delete,dc=example,dc=com" by dn.exact="cn=James A Jones 1,ou=Alumni Association,ou=People,dc=example,dc=com" write by * read +access to dn="ou=People,dc=example,dc=com" + by dn.exact="cn=James A Jones 1,ou=Alumni Association,ou=People,dc=example,dc=com" write continue + by dn.exact="cn=James A Jones 1,ou=Alumni Association,ou=People,dc=example,dc=com" -i + by * break + +access to dn="ou=People,dc=example,dc=com" attrs=uidNumber val.regex="^5*$" + by dn.exact="cn=Bjorn Jensen,ou=Information Technology Division,ou=People,dc=example,dc=com" increment + by * break + +access to dn="ou=People,dc=example,dc=com" attrs=uidNumber val="1" + by dn.exact="cn=Barbara Jensen,ou=Information Technology Division,ou=People,dc=example,dc=com" increment + # fall into global ACLs database monitor diff --git a/tests/scripts/test006-acls b/tests/scripts/test006-acls index 8fd239af17..ac0c163c94 100755 --- a/tests/scripts/test006-acls +++ b/tests/scripts/test006-acls @@ -635,6 +635,135 @@ case $RC in ;; esac +$LDAPMODIFY -D "$JAJDN" -H $URI1 -w jaj >> \ + $TESTOUT 2>&1 << EOMODS16 +dn: ou=People,dc=example,dc=com +changetype: modify +increment: uidNumber +uidNumber: 50 +- +EOMODS16 +RC=$? +case $RC in +50) + ;; +0) + echo "ldapmodify should have failed ($RC)!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit 1 + ;; +*) + echo "ldapmodify failed ($RC)!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit $RC + ;; +esac + +$LDAPMODIFY -D "$JAJDN" -H $URI1 -w jaj >> \ + $TESTOUT 2>&1 << EOMODS17 +dn: ou=People,dc=example,dc=com +changetype: modify +replace: gidNumber +gidNumber: 50 +- +EOMODS17 +RC=$? +case $RC in +0) + ;; +*) + echo "ldapmodify failed ($RC)!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit $RC + ;; +esac + +$LDAPMODIFY -D "$BJORNSDN" -H $URI1 -w bjorn >> \ + $TESTOUT 2>&1 << EOMODS18 +dn: ou=People,dc=example,dc=com +changetype: modify +increment: uidNumber +uidNumber: 55 +- +EOMODS18 +RC=$? +case $RC in +0) + ;; +*) + echo "ldapmodify failed ($RC)!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit $RC + ;; +esac + +$LDAPMODIFY -D "$BABSDN" -H $URI1 -w bjensen >> \ + $TESTOUT 2>&1 << EOMODS19 +dn: ou=People,dc=example,dc=com +changetype: modify +increment: uidNumber +uidNumber: -1 +- +EOMODS19 +RC=$? +case $RC in +50) + ;; +0) + echo "ldapmodify should have failed ($RC)!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit 1 + ;; +*) + echo "ldapmodify failed ($RC)!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit $RC + ;; +esac + +$LDAPMODIFY -D "$BABSDN" -H $URI1 -w bjensen >> \ + $TESTOUT 2>&1 << EOMODS20 +dn: ou=People,dc=example,dc=com +changetype: modify +increment: uidNumber +uidNumber: 1 +- +EOMODS20 +RC=$? +case $RC in +0) + ;; +*) + echo "ldapmodify failed ($RC)!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit $RC + ;; +esac + +$LDAPMODIFY -D "$BJORNSDN" -H $URI1 -w bjorn >> \ + $TESTOUT 2>&1 << EOMODS21 +dn: ou=People,dc=example,dc=com +changetype: modify +replace: uidNumber +uidNumber: 55 +- +EOMODS21 +RC=$? +case $RC in +50) + ;; +0) + echo "ldapmodify should have failed ($RC)!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit 1 + ;; +*) + echo "ldapmodify failed ($RC)!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit $RC + ;; +esac + echo "Using ldapsearch to retrieve all the entries..." echo "# Using ldapsearch to retrieve all the entries..." >> $SEARCHOUT $LDAPSEARCH -S "" -b "$BASEDN" -H $URI1 \