Skip to content
This repository has been archived by the owner on Dec 1, 2024. It is now read-only.

Commit

Permalink
experimental DITStructureRule.Govern method
Browse files Browse the repository at this point in the history
  • Loading branch information
Jesse Coretta authored and Jesse Coretta committed Oct 31, 2024
1 parent 9bf4310 commit 5242819
Show file tree
Hide file tree
Showing 6 changed files with 426 additions and 25 deletions.
148 changes: 141 additions & 7 deletions ds.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
package schemax

//import "fmt"

/*
ds.go contains all DIT structure rule related methods and functions.
*/

/*
NewDITStructureRules initializes a new [DITStructureRules] instance.
*/
Expand Down Expand Up @@ -598,8 +604,12 @@ func (r *dITStructureRule) setRuleID(x any) {
}

/*
SetSuperRule assigns the provided input [DITStructureRule] instance(s) to the
receiver's SUP clause.
SetSuperRule assigns the provided input [DITStructureRule] instance(s)
to the receiver's SUP clause.
If the input arguments contain the `self` special keyword, the receiver
instance will be added to the underlying instance of [DITStructureRules].
This is meant to allow recursive (self-referencing) rules.
This is a fluent method.
*/
Expand All @@ -612,16 +622,22 @@ func (r DITStructureRule) SetSuperRule(m ...any) DITStructureRule {
}

func (r *dITStructureRule) setSuperRule(m ...any) {
var err error
for i := 0; i < len(m) && err == nil; i++ {
for i := 0; i < len(m); i++ {
var def DITStructureRule
switch tv := m[i].(type) {
case string:
case uint64, uint, int:
def = r.schema.DITStructureRules().get(tv)
case string:
if lc(tv) == `self` {
// handle recursive reference
def = DITStructureRule{r}
} else {
def = r.schema.DITStructureRules().get(tv)
}
case DITStructureRule:
def = tv
default:
err = ErrInvalidType
continue
}

r.SuperRules.Push(def)
Expand Down Expand Up @@ -773,6 +789,124 @@ func (r DITStructureRule) String() (dsr string) {
return
}

/*
Govern returns an error following an analysis of the input dn string
value.
The analysis of the DN will verify whether the RDN component complies
with the receiver instance.
If the receiver instance is subordinate to a superior structure rule,
the parent RDN -- if present in the DN -- shall be similarly analyzed.
The process continues throughout the entire structure rule "chain". A
DN must comply with ALL rules in a particular chain in order to "pass".
The flat integer value describes the number of commas (starting from
the far right) to IGNORE during the delimitation process. This allows
for so-called "flattened root suffix" values, e.g.: "dc=example,dc=com",
to be identified, thus avoiding WRONGFUL delimitation to "dc=example"
AND "dc=com" as separate and distinct entries.
Please note this is a mock model of the analyses which compatible
directory products shall execute. Naturally, there is no database (DIT)
thus it is only a measure of the full breadth of structure rule checks.
*/
func (r DITStructureRule) Govern(dn string, flat ...int) (err error) {
if r.IsZero() {
err = ErrNilReceiver
return
}

gdn := tokenizeDN(dn, flat...)
if gdn.isZero() {
err = ErrInvalidDNOrFlatInt
return
}

rdn := gdn.components[0]

var mok int
var moks []string

// gather name form components
must := r.Form().Must()
may := r.Form().May()
noc := r.Form().OC() // named object class

// Iterate each ATV within the RDN.
for i := 0; i < len(rdn); i++ {
atv := rdn[i]
at := atv[0] // attribute type

if sch := r.Schema(); !sch.IsZero() {
// every attribute type must be
// present within the underlying
// schema (when non-zero) ...
if !sch.AttributeTypes().Contains(at) {
err = ErrAttributeTypeNotFound
return
}
}

// Make sure the named object class (i.e.: the
// STRUCTURAL class present in the receiver's
// name form "OC" clause) facilitates the type
// in some way.
if !(noc.Must().Contains(at) || noc.May().Contains(at)) {
err = ErrNamingViolationBadClassAttr
return
}

if must.Contains(at) {
if !strInSlice(at, moks) {
mok++
moks = append(moks, at)
}
} else if !may.Contains(at) {
err = ErrNamingViolationUnsanctioned
return
}
}

// NO required RDN types were satisfied.
if mok == 0 {
err = ErrNamingViolationMissingMust
return
}

// If there are no errors AND there are super rules,
// try to find the right rule chain to follow.
err = r.governRecurse(gdn, flat...)

return
}

func (r DITStructureRule) governRecurse(gdn *governedDistinguishedName, flat ...int) (err error) {
sr := r.SuperRules()

if len(gdn.components) > 1 && sr.Len() > 0 {
for i := 0; i < sr.Len(); i++ {
pdn := &governedDistinguishedName{
components: gdn.components[1:],
flat: gdn.flat,
length: gdn.length - 1,
}

// Recurse parent DN
if err = sr.Index(i).Govern(detokenizeDN(pdn), flat...); err != nil {
if sr.Len() == i+1 {
break // we failed, and there are no more rules.
}
// we failed, BUT there are more rules to try; continue.
} else {
break // we passed! stop processing.
}
}
}

return
}

/*
Inventory returns an instance of [Inventory] which represents the current
inventory of [DITStructureRule] instances within the receiver.
Expand Down Expand Up @@ -811,7 +945,7 @@ func (r DITStructureRules) iDsStringer(_ ...any) (present string) {
padchar = ``
}

joined := join(_present, padchar+` `+padchar)
joined := join(_present, padchar)
present = `(` + padchar + joined + padchar + `)`
}

Expand Down
77 changes: 77 additions & 0 deletions ds_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,22 @@ import (
"testing"
)

func ExampleDITStructureRule_Govern() {
dn := `dc=example,dc=com` // flattened context (1 comma)

// create a new DIT structure rule to leverage
// RFC 2377's 'domainNameForm' definition
dcdsr := mySchema.NewDITStructureRule().
SetRuleID(13).
SetName(`domainStructureRule`).
SetForm(mySchema.NameForms().Get(`domainNameForm`)).
SetStringer()

err := dcdsr.Govern(dn, 1) // flat int
fmt.Println(err)
// Output: <nil>
}

/*
This example demonstrates the creation of a [DITStructureRule].
*/
Expand Down Expand Up @@ -505,6 +521,66 @@ func ExampleDITStructureRule_Parse_bogus() {
// Output: Inconsistent antlr4512.DITStructureRule parse results or bad content
}

func TestDITStructureRule_Govern(t *testing.T) {
// create a new DIT structure rule to leverage
// RFC 2377's 'domainNameForm' definition
dcdsr := mySchema.NewDITStructureRule().
SetRuleID(13).
SetName(`domainStructureRule`).
SetForm(mySchema.NameForms().Get(`domainNameForm`)).
SetStringer()
mySchema.DITStructureRules().Push(dcdsr)

ounf := mySchema.NewNameForm().
SetNumericOID(`1.3.6.1.4.1.56521.999.55.11.33`).
SetName(`ouNameForm`).
SetOC(`organizationalUnit`).
SetMust(`ou`).
SetStringer()
mySchema.NameForms().Push(ounf)

oudsr := mySchema.NewDITStructureRule().
SetRuleID(14).
SetName(`ouStructureRule`).
SetForm(mySchema.NameForms().Get(`ouNameForm`)).
SetSuperRule(13, `self`).
SetStringer()

mySchema.DITStructureRules().Push(oudsr)

for idx, strukt := range []struct {
DN string
L int
ID int
}{
{`dc=example,dc=com`, 1, 13},
{`o=example`, 1, 13},
{`ou=People,dc=example,dc=com`, 1, 14},
{`ou=People,dc=example,dc=com`, -1, 13},
{`ou=Employees,ou=People,dc=example,dc=com`, 1, 14},
{`x=People,dc=example,dc=com`, 1, 14},
{`ou=People+ou=Employees,dc=example,dc=com`, 1, 14},
{`ou=People+cn=Employees,dc=example,dc=com`, 1, 14},
{`ou=People+ou=Employees,dc=example,dc=com`, 1, 14},
{`ou=Employees,ou=People,dc=example,dc=com`, 1, 13},
} {
rule := mySchema.DITStructureRules().Get(strukt.ID)
even := idx%2 == 0

if err := rule.Govern(strukt.DN, strukt.L); err != nil {
if even {
t.Errorf("%s[%d] failed: %v (%v)", t.Name(), idx, strukt.DN, err)
return
}
} else {
if !even {
t.Errorf("%s[%d] failed: expected error, got nothing", t.Name(), idx)
return
}
}
}
}

/*
Do stupid things to make schemax panic, gain additional
coverage in the process.
Expand All @@ -528,6 +604,7 @@ func TestDITStructureRule_codecov(t *testing.T) {
r.RuleID()
r.setOID(``)
r.macro()
r.Govern(`bogusdn`)
r.Parse(`crap`)
r.IsIdentifiedAs(`nothing`)
r.Replace(DITStructureRule{&dITStructureRule{}})
Expand Down
39 changes: 22 additions & 17 deletions err.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,28 @@ for use within this package as well as by end-users writing closures.
import "errors"

var (
ErrNilSyntaxQualifier error = errors.New("No SyntaxQualifier instance assigned to LDAPSyntax")
ErrNilValueQualifier error = errors.New("No ValueQualifier instance assigned to AttributeType")
ErrNilAssertionMatcher error = errors.New("No AssertionMatcher instance assigned to MatchingRule")
ErrNilReceiver error = errors.New("Receiver instance is nil")
ErrNilInput error = errors.New("Input instance is nil")
ErrNilDef error = errors.New("Referenced definition is nil or not specified")
ErrNilSchemaRef error = errors.New("Receiver instance lacks a Schema reference")
ErrDefNonCompliant error = errors.New("Definition failed compliancy checks")
ErrInvalidInput error = errors.New("Input instance not compatible")
ErrInvalidSyntax error = errors.New("Value does not meet the prescribed syntax qualifications")
ErrInvalidValue error = errors.New("Value does not meet the prescribed value qualifications")
ErrNoMatch error = errors.New("Values do not match according to prescribed assertion match")
ErrInvalidType error = errors.New("Incompatible type for operation")
ErrTypeAssert error = errors.New("Type assertion failed")
ErrNotUnique error = errors.New("Definition is already defined")
ErrNotEqual error = errors.New("Values are not equal")
ErrMissingNumericOID error = errors.New("Missing or invalid numeric OID for definition")
ErrNamingViolationMissingMust error = errors.New("Naming violation; required attribute type not used")
ErrNamingViolationUnsanctioned error = errors.New("Naming violation; unsanctioned attribute type used")
ErrNamingViolationChildlessSSR error = errors.New("Naming violation; childless superior structure rule")
ErrNamingViolationBadClassAttr error = errors.New("Naming violation; named object class does not facilitate one or more attribute types present")
ErrNilSyntaxQualifier error = errors.New("No SyntaxQualifier instance assigned to LDAPSyntax")
ErrNilValueQualifier error = errors.New("No ValueQualifier instance assigned to AttributeType")
ErrNilAssertionMatcher error = errors.New("No AssertionMatcher instance assigned to MatchingRule")
ErrNilReceiver error = errors.New("Receiver instance is nil")
ErrNilInput error = errors.New("Input instance is nil")
ErrNilDef error = errors.New("Referenced definition is nil or not specified")
ErrNilSchemaRef error = errors.New("Receiver instance lacks a Schema reference")
ErrDefNonCompliant error = errors.New("Definition failed compliancy checks")
ErrInvalidInput error = errors.New("Input instance not compatible")
ErrInvalidSyntax error = errors.New("Value does not meet the prescribed syntax qualifications")
ErrInvalidValue error = errors.New("Value does not meet the prescribed value qualifications")
ErrNoMatch error = errors.New("Values do not match according to prescribed assertion match")
ErrInvalidType error = errors.New("Incompatible type for operation")
ErrTypeAssert error = errors.New("Type assertion failed")
ErrNotUnique error = errors.New("Definition is already defined")
ErrNotEqual error = errors.New("Values are not equal")
ErrMissingNumericOID error = errors.New("Missing or invalid numeric OID for definition")
ErrInvalidDNOrFlatInt error = errors.New("Invalid DN or flattened integer")

ErrOrderingRuleNotFound error = errors.New("ORDERING MatchingRule not found")
ErrSubstringRuleNotFound error = errors.New("SUBSTR MatchingRule not found")
Expand Down
Loading

0 comments on commit 5242819

Please sign in to comment.