.B constraint_attribute <attribute_name>[,...] <type> <value> [<extra> [...]]
Specifies the constraint which should apply to the comma-separated
attribute list named as the first parameter.
-Six types of constraint are currently supported -
+Eight types of constraint are currently supported -
.BR regex ,
.BR negregex ,
.BR size ,
.BR count ,
.BR uri ,
+.BR neguri ,
+.BR set ,
and
-.BR set .
+.BR negset .
The parameter following the
.B regex
.BR regex (7)
). The parameter following the
.B uri
+or
+.B neguri
type is an LDAP URI. The URI will be evaluated using an internal search.
It must not include a hostname, and it must include a list of attributes
to evaluate.
The parameter following the
.B set
+or
+.B negset
type is a string that is interpreted according to the syntax in use
for ACL sets. This allows one to construct constraints based on the contents
of the entry.
constraint_attribute mail negregex ^[[:alnum:]]+@notallowed.com$
constraint_attribute title uri
ldap:///dc=catalog,dc=example,dc=com?title?sub?(objectClass=titleCatalog)
+constraint_attribute cn neguri
+ ldap:///ou=People,dc=example,dc=com?cn,sn?sub?(objectClass=inetOrgPerson)
constraint_attribute cn,sn,givenName set
"(this/givenName + [ ] + this/sn) & this/cn"
restrict="ldap:///ou=People,dc=example,dc=com??sub?(objectClass=inetOrgPerson)"
in a separate database, otherwise the initial set of
titleCatalog entries could not be populated while the
constraint is in effect.)
+With the type "neguri" (negated "uri"), it would
+ensure uniqueness of one (or more) attributes against one (or
+more) other attributes. A specification like the above
+would reject any
+.B cn
+attributes whose
+.B any
+values
+.B was already listed
+in another
+.B cn
+or
+.B sn
+attributes of any
+.B inetOrgPerson
+entries in the given scope.
Finally, it requires the values of the attribute
.B cn
to be constructed by pairing values of the attributes
.BR givenName ,
separated by a space, but only for entries derived from the objectClass
.BR inetOrgPerson .
+Should the type "negset" have been used instead of "set", the
+condition would have been reversed.
.RE
.SH FILES
.TP
#define REGEX_STR "regex"
#define NEG_REGEX_STR "negregex"
#define URI_STR "uri"
+#define NEG_URI_STR "neguri"
#define SET_STR "set"
+#define NEG_SET_STR "negset"
#define SIZE_STR "size"
#define COUNT_STR "count"
CONSTRAINT_REGEX,
CONSTRAINT_NEG_REGEX,
CONSTRAINT_SET,
+ CONSTRAINT_NEG_SET,
CONSTRAINT_URI,
+ CONSTRAINT_NEG_URI,
};
static ConfigDriver constraint_cf_gen;
static ConfigTable constraintcfg[] = {
- { "constraint_attribute", "attribute[list]> (regex|negregex|uri|set|size|count) <value> [<restrict URI>]",
+ { "constraint_attribute", "attribute[list]> (regex|negregex|uri|neguri|set|negset|size|count) <value> [<restrict URI>]",
4, 0, 0, ARG_MAGIC | CONSTRAINT_ATTRIBUTE, constraint_cf_gen,
"( OLcfgOvAt:13.1 NAME 'olcConstraintAttribute' "
"DESC 'constraint for list of attributes' "
tstr = SET_STR;
quotes = 1;
break;
+ case CONSTRAINT_NEG_SET:
+ tstr = NEG_SET_STR;
+ quotes = 1;
+ break;
case CONSTRAINT_URI:
tstr = URI_STR;
quotes = 1;
break;
+ case CONSTRAINT_NEG_URI:
+ tstr = NEG_URI_STR;
+ quotes = 1;
+ break;
default:
abort();
}
ap.count = strtoull(c->argv[3], &endptr, 10);
if ( *endptr )
rc = ARG_BAD_CONF;
- } else if ( strcasecmp( c->argv[2], URI_STR ) == 0 ) {
+ } else if ( strcasecmp( c->argv[2], URI_STR ) == 0 || strcasecmp( c->argv[2], NEG_URI_STR ) == 0 ) {
int err;
- ap.type = CONSTRAINT_URI;
+ ap.type = strcasecmp( c->argv[2], URI_STR ) == 0 ? CONSTRAINT_URI : CONSTRAINT_NEG_URI;
err = ldap_url_parse(c->argv[3], &ap.lud);
if ( err != LDAP_URL_SUCCESS ) {
snprintf( c->cr_msg, sizeof( c->cr_msg ),
ber_str2bv( c->argv[3], 0, 1, &ap.val );
ap.type = CONSTRAINT_SET;
+ } else if ( strcasecmp( c->argv[2], NEG_SET_STR ) == 0 ) {
+ ap.set = 1;
+ ber_str2bv( c->argv[3], 0, 1, &ap.val );
+ ap.type = CONSTRAINT_NEG_SET;
+
} else {
snprintf( c->cr_msg, sizeof( c->cr_msg ),
"%s %s: Unknown constraint type: %s",
if (regexec(c->re, bv->bv_val, 0, NULL, 0) != REG_NOMATCH)
return LDAP_CONSTRAINT_VIOLATION; /* regular expression violation */
break;
- case CONSTRAINT_URI: {
+ case CONSTRAINT_URI: /* fallthrough */
+ case CONSTRAINT_NEG_URI:
+ {
Operation nop = *op;
slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
slap_callback cb = { 0 };
return rc; /* unexpected error */
}
- if (!found)
+ if (found ^ c->type == CONSTRAINT_URI)
return LDAP_CONSTRAINT_VIOLATION; /* constraint violation */
break;
}
if (acl_match_set(&cp->val, op, op->ora_e, NULL) == 0)
rc = LDAP_CONSTRAINT_VIOLATION;
break;
+ case CONSTRAINT_NEG_SET:
+ if (acl_match_set(&cp->val, op, op->ora_e, NULL) != 0)
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ break;
default:
for ( i = 0; b[i].bv_val; i++ ) {
rc = constraint_violation( cp, &b[i], op );
}
}
- if (cp->type == CONSTRAINT_SET && target_entry) {
+ if ((cp->type == CONSTRAINT_SET || cp->type == CONSTRAINT_NEG_SET) && target_entry) {
if (target_entry_copy == NULL) {
Modifications *ml;
}
}
- if ( acl_match_set(&cp->val, op, target_entry_copy, NULL) == 0) {
+ if ((acl_match_set(&cp->val, op, target_entry_copy, NULL) == 1) ^ (cp->type == CONSTRAINT_SET)) {
rc = LDAP_CONSTRAINT_VIOLATION;
goto mod_violation;
}
FAIL
FAIL
FAIL
+OK
+FAIL
+FAIL
+FAIL
ou: users
objectclass: organizationalUnit
+dn: ou=users2,dc=example,dc=com
+ou: users2
+objectclass: organizationalUnit
+
dn: ou=groups,dc=example,dc=com
ou: groups
objectclass: organizationalUnit
--- /dev/null
+dn: givenName=John,ou=users2,dc=example,dc=com
+changetype: modify
+replace: cn
+cn: John Rouge
--- /dev/null
+dn: givenName=John,ou=users2,dc=example,dc=com
+changetype: modify
+replace: sn
+sn: le Rouge
--- /dev/null
+dn: givenName=John,ou=users2,dc=example,dc=com
+changetype: modify
+replace: uid
+uid: 1
--- /dev/null
+dn: givenName=John,ou=users2,dc=example,dc=com
+changetype: modify
+replace: uid
+uid: 4
--- /dev/null
+dn: givenName=John,ou=users2,dc=example,dc=com
+objectclass: inetOrgPerson
+objectclass: organizationalPerson
+cn: John le Rouge
+givenname: John
+sn: Rouge
+mail: original@example.com
+description: desc1
+description: desc2
+uid: 3
CONSTRAINTDIR="$DATADIR/constraint"
ROOTLDIF="$CONSTRAINTDIR/root.ldif"
USERLDIF="$CONSTRAINTDIR/user.ldif"
+USER2LDIF="$CONSTRAINTDIR/user2.ldif"
RESULTOUT="$CONSTRAINTDIR/constraint.out"
SCRIPTOUT="$TESTDIR/constraint.out"
USERDN="cn=John Doe,ou=users,$BASEDN"
+USER2DN="givenName=John,ou=users2,$BASEDN"
CONFDIR=$TESTDIR/slapd.d
mkdir -p $TESTDIR $CONFDIR $DBDIR1
olcConstraintAttribute: uid
uri "ldap:///ou=groups,$BASEDN?uid?one?(objectClass=inetOrgPerson)"
restrict="ldap:///ou=users,$BASEDN??one"
+olcConstraintAttribute: cn,sn,givenName
+ negset "(this/givenName + [ ] + this/sn) & this/cn"
+ restrict="ldap:///$USER2DN??sub?(objectClass=inetOrgPerson)"
+olcConstraintAttribute: uid
+ neguri "ldap:///ou=groups,$BASEDN?uid?one?(objectClass=inetOrgPerson)"
+ restrict="ldap:///ou=users2,$BASEDN??one"
EOF
$SLAPADD -F $CONFDIR -n 0 -l $TESTDIR/config.ldif
test $KILLSERVERS != no && kill -HUP $PID
exit $RC
fi
+$LDAPADD -D "$MANAGERDN" -H $URI1 -w $PASSWD -f $USER2LDIF >/dev/null 2>&1
+RC=$?
+if test $RC != 0 ; then
+ echo "ldapadd failed ($RC)!"
+ test $KILLSERVERS != no && kill -HUP $PID
+ exit $RC
+fi
echo "Running constraint tests..."
-for ldif in $CONSTRAINTDIR/*ok*.ldif $CONSTRAINTDIR/*fail*.ldif; do
+for ldif in $CONSTRAINTDIR/t_ok*.ldif $CONSTRAINTDIR/t_fail*.ldif; do
### reload
$LDAPDELETE -D "$MANAGERDN" -H $URI1 -w $PASSWD "$USERDN" >/dev/null 2>&1
RC=$?
fi
done
+echo "Running *neg* constraint tests..."
+for ldif in $CONSTRAINTDIR/tn_ok*.ldif $CONSTRAINTDIR/tn_fail*.ldif; do
+ ### reload
+ $LDAPDELETE -D "$MANAGERDN" -H $URI1 -w $PASSWD "$USER2DN" >/dev/null 2>&1
+ RC=$?
+ if test $RC != 0 ; then
+ echo "ldapdelete failed ($RC)!"
+ test $KILLSERVERS != no && kill -HUP $PID
+ exit $RC
+ fi
+ $LDAPADD -D "$MANAGERDN" -H $URI1 -w $PASSWD -f $USER2LDIF >/dev/null 2>&1
+ RC=$?
+ if test $RC != 0 ; then
+ echo "ldapadd failed ($RC)!"
+ test $KILLSERVERS != no && kill -HUP $PID
+ exit $RC
+ fi
+
+ ### info
+ echo -n " [$ldif]: "
+
+ ### modify
+ $LDAPMODIFY -H $URI1 -x -D "$MANAGERDN" -f $ldif -w $PASSWD >/dev/null 2>&1
+ RC=$?
+ if test $RC = 0 ; then
+ echo "OK" | tee -a $SCRIPTOUT
+ elif test $RC = 19 ; then
+ echo "FAIL" | tee -a $SCRIPTOUT
+ else
+ echo "UNEXPECTED ($RC)"
+ fi
+done
+
echo "Comparing output..."
$DIFF $SCRIPTOUT $RESULTOUT > $CMPOUT
RC=$?