@@ -129,8 +129,8 @@ func (wg *WeightedAuthorizationModelGraph) AssignWeights() error {
129129 tupleCycleDependencies := make (map [string ][]* WeightedAuthorizationModelEdge )
130130
131131 for nodeID , node := range wg .nodes {
132- if node . GetNodeType () == OperatorNode {
133- // Inititally defer weight assignment of operator nodes to later in `fixDependantNodesWeight()`.
132+ if wg . isLogicalOperator ( node ) {
133+ // Inititally defer weight assignment of operator nodes and the logical nodes to later in `fixDependantNodesWeight()`.
134134 // This enables more deterministic behavior of intermediate functions.
135135 continue
136136 }
@@ -150,6 +150,24 @@ func (wg *WeightedAuthorizationModelGraph) AssignWeights() error {
150150 return nil
151151}
152152
153+ func (wg * WeightedAuthorizationModelGraph ) isLogicalOperator (node * WeightedAuthorizationModelNode ) bool {
154+ // a logical ttu is when a ttu has more than one edges due to having multiple terminal types as usersets,
155+ // and should always be treated as a union among all those ttu edges as they belong to the same logical ttu
156+ // a logical userset is when we have multiple asignations to terminal types or usersets, we need to treat that
157+ // as a union for all the direct edges
158+ nodeType := node .GetNodeType ()
159+ return nodeType == OperatorNode || nodeType == LogicalTTUGrouping || nodeType == LogicalDirectGrouping
160+ }
161+
162+ func (wg * WeightedAuthorizationModelGraph ) isLogicalUnionOperator (node * WeightedAuthorizationModelNode ) bool {
163+ // a logical ttu is when a ttu has more than one edges due to having multiple terminal types as usersets,
164+ // and should always be treated as a union among all those ttu edges as they belong to the same logical ttu
165+ // a logical userset is when we have multiple asignations to terminal types or usersets, we need to treat that
166+ // as a union for all the direct edges
167+ nodeType := node .GetNodeType ()
168+ return (nodeType == OperatorNode && node .GetLabel () == UnionOperator ) || nodeType == LogicalTTUGrouping || nodeType == LogicalDirectGrouping
169+ }
170+
153171func (wg * WeightedAuthorizationModelGraph ) calculateEdgeWildcards (edge * WeightedAuthorizationModelEdge ) {
154172 // if wildcards already exist, we don't need to calculate them
155173 if len (edge .wildcards ) > 0 {
@@ -437,8 +455,9 @@ func (wg *WeightedAuthorizationModelGraph) calculateNodeWeightFromTheEdges(nodeI
437455 return tupleCycles , nil
438456 }
439457
440- // there is a cycle but the node is not responsible for the cycle and it is not an operator node
441- if node .nodeType != OperatorNode {
458+ // there is a cycle but the node is not responsible for the cycle and it is not a logical operator node
459+ // a logical operator node is an operator node, a logical ttu and a logical userset node
460+ if ! wg .isLogicalOperator (node ) {
442461 // even when there is a cycle, if the relation is not recursive then we calculate the weight using the max strategy
443462 err = wg .calculateNodeWeightWithMaxStrategy (nodeID )
444463 if err != nil {
@@ -447,8 +466,9 @@ func (wg *WeightedAuthorizationModelGraph) calculateNodeWeightFromTheEdges(nodeI
447466 return tupleCycles , nil
448467 }
449468
450- // if the node is an union operator and there is a cycle
451- if node .nodeType == OperatorNode && node .label == UnionOperator {
469+ // if the node is a logical union operator,
470+ // a logical union operator is a union node, a logical userset or a logical ttu.
471+ if wg .isLogicalUnionOperator (node ) {
452472 // if the node is the reference node of the cycle, recalculate the weight, solve the depencies and remove the node from the tuple cycle
453473 if wg .isNodeTupleCycleReference (nodeID , tupleCycles ) {
454474 err := wg .calculateNodeWeightAndFixDependencies (nodeID , tupleCycleDependencies )
@@ -497,30 +517,35 @@ func (wg *WeightedAuthorizationModelGraph) calculateNodeWeightWithMaxStrategy(no
497517// calculateNodeWeightWithMixedStrategy is a mixed weight strategy used for exclusion node (A but not B).
498518// For all A edges, we take all the types for all the edges and get the max value
499519// if more than one edge have the same type in their weights.
500- // For all
501520func (wg * WeightedAuthorizationModelGraph ) calculateNodeWeightWithMixedStrategy (nodeID string ) error {
502521 node := wg .nodes [nodeID ]
503- weights := make (map [string ]int )
504522 edges := wg .edges [nodeID ]
505523
506524 if len (edges ) == 0 && node .nodeType != SpecificType && node .nodeType != SpecificTypeWildcard {
507525 return fmt .Errorf ("%w: %s node does not have any terminal type to reach to" , ErrInvalidModel , node .uniqueLabel )
508526 }
509527
510- for idx , edge := range edges {
511- for key , value := range edge .weights {
512- if _ , ok := weights [key ]; ! ok {
513- if idx != len (edges )- 1 {
514- // This is the A edge. We take the max weight of all key
515- weights [key ] = value
516- } // otherwise, B edge requires weight to be present in A. Otherwise, we will ignore.
517- } else {
518- weights [key ] = int (math .Max (float64 (weights [key ]), float64 (value )))
519- }
528+ if node .nodeType != OperatorNode || node .label != ExclusionOperator {
529+ return fmt .Errorf ("%w: node %s cannot apply mixed strategy, only accepted exclusion nodes" , ErrInvalidModel , nodeID )
530+ }
531+
532+ // with the logical ttu and userset, an exclusion operation only has two edges
533+ // the first edge is the one that defines the userset, the second edge is the one that excludes it
534+ if len (edges ) != 2 {
535+ return fmt .Errorf ("%w: invalid number of edges for exclusion node %s" , ErrInvalidModel , nodeID )
536+ }
537+ nodeWeights := make (map [string ]int , len (edges ))
538+ for k , v := range edges [0 ].weights {
539+ nodeWeights [k ] = v
540+ }
541+
542+ for key , value := range edges [1 ].weights {
543+ if w , ok := nodeWeights [key ]; ok {
544+ nodeWeights [key ] = int (math .Max (float64 (w ), float64 (value )))
520545 }
521546 }
522547
523- node .weights = weights
548+ node .weights = nodeWeights
524549 return nil
525550}
526551
@@ -536,19 +561,9 @@ func (wg *WeightedAuthorizationModelGraph) calculateNodeWeightWithEnforceTypeStr
536561 return fmt .Errorf ("%w: %s node does not have any terminal type to reach to" , ErrInvalidModel , node .uniqueLabel )
537562 }
538563
539- // In intersection, directly-assignable types must be handled separately from rewritten types.
540- // All types flatten to edges, but directly-assignable weights are OR'd together, and that result
541- // is then AND'd with the combined weights of the rewritten edges for validity and weight assignment.
542- directlyAssignableWeights := make (map [string ]int , len (edges ))
543564 rewriteWeights := make (map [string ]int , len (edges ))
544-
545565 for _ , edge := range edges {
546- if edge .GetEdgeType () == DirectEdge {
547- for key , weight := range edge .weights {
548- directlyAssignableWeights [key ] = weight
549- }
550- continue
551- }
566+
552567 if len (rewriteWeights ) == 0 {
553568 for key , weight := range edge .weights {
554569 rewriteWeights [key ] = weight
@@ -564,18 +579,7 @@ func (wg *WeightedAuthorizationModelGraph) calculateNodeWeightWithEnforceTypeStr
564579 }
565580 }
566581
567- if len (directlyAssignableWeights ) > 0 {
568- for key := range directlyAssignableWeights {
569- if _ , existsInBoth := rewriteWeights [key ]; existsInBoth {
570- if node .weights == nil {
571- node .weights = make (map [string ]int , int (math .Min (float64 (len (rewriteWeights )), float64 (len (directlyAssignableWeights )))))
572- }
573- node .weights [key ] = int (math .Max (float64 (rewriteWeights [key ]), float64 (directlyAssignableWeights [key ])))
574- }
575- }
576- } else {
577- node .weights = rewriteWeights
578- }
582+ node .weights = rewriteWeights
579583
580584 if len (node .weights ) == 0 {
581585 return fmt .Errorf ("%w: not all paths return the same type for the node %s" , ErrInvalidModel , nodeID )
@@ -605,8 +609,8 @@ func (wg *WeightedAuthorizationModelGraph) calculateNodeWeightAndFixDependencies
605609 referenceNodeID := "R#" + nodeID
606610 edges := wg .edges [nodeID ]
607611
608- if ( node .nodeType == OperatorNode && node . label != UnionOperator ) || ( node . nodeType != SpecificTypeAndRelation && node . nodeType != OperatorNode ) {
609- return fmt .Errorf ("%w: invalid node, reference node is not a union operator or a relation: %s" , ErrTupleCycle , nodeID )
612+ if node .nodeType != SpecificTypeAndRelation && ! wg . isLogicalUnionOperator ( node ) {
613+ return fmt .Errorf ("%w: invalid node, reference node is not a union operator or a relation or a logical userset or logical TTU : %s" , ErrTupleCycle , nodeID )
610614 }
611615
612616 if len (edges ) == 0 && node .nodeType != SpecificType && node .nodeType != SpecificTypeWildcard {
0 commit comments