]> git.ipfire.org Git - thirdparty/openldap.git/commitdiff
ITS#9640 Introduce the increment privilege
authorOndřej Kuzník <ondra@mistotebe.net>
Wed, 10 Dec 2025 12:40:11 +0000 (12:40 +0000)
committerQuanah Gibson-Mount <quanah@openldap.org>
Thu, 11 Dec 2025 20:42:19 +0000 (20:42 +0000)
doc/man/man5/slapd.access.5
servers/slapd/acl.c
servers/slapd/aclparse.c
servers/slapd/overlays/constraint.c
servers/slapd/slap.h
tests/data/acl.out.provider
tests/data/slapd-acl.conf
tests/scripts/test006-acls

index 0104ad9b01f4801acdab4ea2405da8df9a938f31..870d713b7bb4bc1ef752e54acd15a6194d4d698a 100644 (file)
@@ -760,8 +760,8 @@ field will have.
 Its component are defined as
 .LP
 .nf
-       <level> ::= none|disclose|auth|compare|search|read|{write|add|delete}|manage
-       <priv> ::= {=|+|\-}{0|d|x|c|s|r|{w|a|z}|m}+
+       <level> ::= none|disclose|auth|compare|search|read|{write|add|delete|increment}|manage
+       <priv> ::= {=|+|\-}{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 <what> .
 
 .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
index f6f0587dff0eb066d27833504cb72919ada78803..6e841a75480d8533a4520003eac41bcef360b41d 100644 (file)
@@ -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.
index b41951d469df62a7eced0d3ec156671384e61823..5a0174f72c77d4a39c60c02a222e86fabc69b26f 100644 (file)
@@ -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)
                "<peernamestyle> ::= exact | regex | ip | ipv6 | path\n"
                "<domainstyle> ::= exact | regex | base(Object) | sub(tree)\n"
                "<access> ::= [[real]self]{<level>|<priv>}\n"
-               "<level> ::= none|disclose|auth|compare|search|read|{write|add|delete}|manage\n"
-               "<priv> ::= {=|+|-}{0|d|x|c|s|r|{w|a|z}|m}+\n"
+               "<level> ::= none|disclose|auth|compare|search|read|{write|add|delete|increment}|manage\n"
+               "<priv> ::= {=|+|-}{0|d|x|c|s|r|{w|a|z|i}|m}+\n"
                "<control> ::= [ 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;
        }
index f4ffcf7377841ad6c4c11e2e2414cd2c3bbcd6b8..13af13a2f323540201f12854820a051bfb1a546d 100644 (file)
@@ -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 */
index 5754d91277c452d0f89362dda506089cf1333b0f..1305b47b94ae7b8af3868e0a79a05516ebb5ab50 100644 (file)
@@ -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)
 
index 8fd99a621ffcadb9d0af9c3f0219e23ea8219dfe..b6b1aa16c40c53aa7fb522b2ae5e0c6402fe52e8 100644 (file)
@@ -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
 
index 90bb9ba6b1268b4685822e2e4f19614a30f89de5..545da3710aae59b2c776ab4e5b35ba5466c85510 100644 (file)
@@ -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
index 8fd239af170422551b7b8cf80c53d3ba1b7aef49..ac0c163c9440ac35ebc523b93314f3571d7f07fd 100755 (executable)
@@ -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 \