3 # FILE: PrivilegeSet.php 5 # Part of the Collection Workflow Integration System (CWIS) 6 # Copyright 2013 Edward Almasy and Internet Scout Research Group 7 # http://scout.wisc.edu/cwis/ 29 # if privilege data supplied 32 # if data is an array of privileges 35 # set internal privilege set from array 36 $this->Privileges = $Data;
38 # else if data is a single privilege 39 elseif (is_numeric($Data))
41 # set internal privilege set from data 42 $this->Privileges = array($Data);
46 # set internal values from data 47 $this->LoadFromData($Data);
62 public function Data($NewValue = NULL)
64 # if new data supplied 65 if ($NewValue !== NULL)
67 # unpack privilege data and load 68 $this->LoadFromData($NewValue);
71 # serialize current data and return to caller 73 if (count($this->Privileges))
75 foreach ($this->Privileges as $Priv)
77 $Data[
"Privileges"][] = is_object($Priv)
78 ? array(
"SUBSET" => $Priv->Data())
82 $Data[
"Logic"] = $this->Logic;
83 return serialize($Data);
98 # when there are no requirements, then every user meets them 101 # for each privilege requirement 102 foreach ($this->Privileges as $Priv)
104 # if privilege is actually a privilege subgroup 105 if (is_object($Priv))
107 # check if the subgroup is satisfied 108 $Satisfied = $Priv->MeetsRequirements($User, $Resource);
110 # else if privilege is actually a condition 111 elseif (is_array($Priv))
113 # check if condition is satisfied for the given resource 114 $Satisfied = $this->MeetsCondition($Priv, $Resource, $User);
116 # else privilege is actually a privilege 119 # check if user has the specified privilege 120 $Satisfied = $User->
HasPriv( $Priv );
123 # for AND logic, we can bail as soon as the first 124 # condition is not met 125 if ($this->Logic ==
"AND")
132 # conversely, for OR logic, we can bail as soon as any 143 # report result of the test back to caller 155 # convert incoming value to array if needed 156 if (!is_array($Privileges))
158 $Privileges = array($Privileges);
161 # for each privilege passed in 162 foreach ($Privileges as $Privilege)
164 # add privilege if not currently in set 167 if (is_object($Privilege)) { $Privilege = $Privilege->Id(); }
168 $this->Privileges[] = $Privilege;
181 # remove privilege if currently in set 184 if (is_object($Privilege)) { $Privilege = $Privilege->Id(); }
185 $Index = array_search($Privilege, $this->Privileges);
186 unset($this->Privileges[$Index]);
197 # check whether privilege is in our list and report to caller 198 if (is_object($Privilege)) { $Privilege = $Privilege->Id(); }
199 return $this->IsInPrivilegeData($Privilege) ? TRUE : FALSE;
212 # grab privilege information and add logic 213 $Info = $this->Privileges;
214 $Info[
"Logic"] = $this->Logic;
216 # return privilege info array to caller 228 # create list of privileges with conditions stripped out 230 foreach ($this->Privileges as $Priv)
232 if (!is_array($Priv)) { $List[] = $Priv; }
235 # return list of privileges to caller 259 # set up condition array 261 "FieldId" => intval($FieldId),
262 "Operator" => trim($Operator),
265 # if condition is not already in set 266 if (!$this->IsInPrivilegeData($Condition))
268 # add condition to privilege set 269 $this->Privileges[] = $Condition;
289 $Field, $Value = NULL, $Operator =
"==",
290 $IncludeSubsets = FALSE)
295 $FieldId = is_object($Field) ? $Field->Id() : $Field;
297 # set up condition array 299 "FieldId" => intval($FieldId),
300 "Operator" => trim($Operator),
303 # if condition is in set 304 if ($this->IsInPrivilegeData($Condition))
306 # remove condition from privilege set 307 $Index = array_search($Condition, $this->Privileges);
308 unset($this->Privileges[$Index]);
314 foreach ($this->Privileges as $Priv)
318 $Result |= $Priv->RemoveCondition(
319 $FieldId, $Value, $Operator, TRUE);
333 # if subgroup is not already in set 334 if (!$this->IsInPrivilegeData($Set))
336 # add subgroup to privilege set 337 $this->Privileges[] = $Set;
352 if ($NewValue !== NULL)
354 $this->Logic = $NewValue ?
"AND" :
"OR";
356 return ($this->Logic ==
"AND") ? TRUE : FALSE;
367 unset($Info[
"Logic"]);
370 foreach ($Info as $Item)
372 if (is_object($Item))
374 $Result = array_merge($Result, $Item->PrivilegeFlagsChecked() );
376 elseif (!is_array($Item))
381 return array_unique($Result);
394 unset($Info[
"Logic"]);
397 foreach ($Info as $Item)
399 if (is_object($Item))
401 $Result = array_merge(
403 $Item->FieldsWithUserComparisons($ComparisonType));
405 elseif (is_array($Item))
407 if ( (($Item[
"Operator"] == $ComparisonType)
408 || ($ComparisonType === NULL)) &&
415 $Result[]= $Item[
"FieldId"];
421 return array_unique($Result);
431 foreach ($this->Privileges as $Priv)
433 $Count += is_object($Priv) ? $Priv->ComparisonCount() : 1;
448 # for each privilege requirement 449 $NecessaryPrivs = array();
450 foreach ($this->Privileges as $Priv)
452 # if requirement is comparison 456 if ($this->Logic ==
"OR")
458 # bail out because no privileges are required 462 # else if requirement is subgroup 463 elseif (is_object($Priv))
465 # retrieve possible needed privileges from subgroup 466 $SubPrivs = $Priv->GetPossibleNecessaryPrivileges();
468 # if no privileges were required by subgroup 469 if (!count($SubPrivs))
472 if ($this->Logic ==
"OR")
474 # bail out because no privileges are required 480 # add subgroup privileges to required list 481 $NecessaryPrivs = array_merge($NecessaryPrivs, $SubPrivs);
484 # else requirement is privilege 487 # add privilege to required list 488 $NecessaryPrivs[] = $Priv;
492 # return possible needed privileges to caller 493 return $NecessaryPrivs;
503 # iterate over all the privs in this privset 504 foreach ($this->Privileges as $Priv)
506 # if this priv is a field condition that references the 507 # provided FieldId, return true 508 if (is_array($Priv) && $Priv[
"FieldId"] == $FieldId)
512 # otherwise, if this was a privset then call ourself recursively 520 # found no references to this field, return FALSE 530 self::$MetadataFieldCache = array();
531 self::$ValueCache = array();
534 # ---- PRIVATE INTERFACE ------------------------------------------------- 536 private $RFactories = array();
537 private $Privileges = array();
538 private $Logic =
"OR";
540 private static $MetadataFieldCache;
541 private static $ValueCache;
550 private function LoadFromData($Serialized)
552 # save calling context in case load causes out-of-memory crash 553 $GLOBALS[
"AF"]->RecordContextInCaseOfCrash();
556 $Data = unserialize($Serialized);
559 throw new InvalidArgumentException(
560 "Invalid serialized data supplied (\"".$Serialized.
"\").");
563 # unpack privilege data (if available) and load 564 if (array_key_exists(
"Privileges", $Data))
566 $this->Privileges = array();
567 foreach ($Data[
"Privileges"] as $Priv)
569 if (is_array($Priv) && array_key_exists(
"SUBSET", $Priv))
572 $Subset->LoadFromData($Priv[
"SUBSET"]);
573 $this->Privileges[] = $Subset;
577 $this->Privileges[] = $Priv;
582 # load logic if available 583 if (array_key_exists(
"Logic", $Data))
585 $this->Logic = $Data[
"Logic"];
596 private function MeetsCondition($Condition, $Resource, $User)
598 # make sure metadata field is loaded 599 $MFieldId = $Condition[
"FieldId"];
600 if (!isset(self::$MetadataFieldCache[$MFieldId]))
602 self::$MetadataFieldCache[$MFieldId] =
608 # if the specified field does not exist 609 if (self::$MetadataFieldCache[$MFieldId] === FALSE)
611 # return a result that in effect ignores the condition 612 return ($this->Logic ==
"AND") ? TRUE : FALSE;
615 # pull out provided field 616 $Field = self::$MetadataFieldCache[$MFieldId];
617 $Operator = $Condition[
"Operator"];
618 $Value = $Condition[
"Value"];
620 # determine if the provided operator is valid for the provided field 621 if (!in_array($Operator, $this->ValidOperatorsForFieldType($Field->Type()) ))
623 throw new Exception(
"Operator ".$Operator.
" not supported for " 624 .$Field->TypeAsName().
" fields");
627 # if we don't have a specific resource to check, then we want 628 # to determine if this condition would be satisfied by any 630 if ($Resource == self::NO_RESOURCE)
632 $Count = $this->CountResourcesThatSatisfyCondition(
633 $User, $Field, $Operator, $Value);
634 return $Count > 0 ? TRUE : FALSE;
636 # else if resource is valid 637 elseif ($Resource instanceof
Resource)
639 # if this field is from a different schema than our resource 640 # and also this field is not from the User schema, then there's 641 # no comparison for us to do 642 if ($Field->SchemaId() != $Resource->SchemaId() &&
645 # return a result that in effect ignores the condition 646 return ($this->Logic ==
"AND") ? TRUE : FALSE;
649 # normalize the incoming value for comparison 650 $Value = $this->NormalizeTargetValue($Field->Type(), $User, $Value);
651 $FieldValue = $this->GetNormalizedFieldValue($Field, $Resource, $User);
653 # perform comparison, returning result 654 return $this->CompareNormalizedFieldValues($FieldValue, $Operator, $Value);
658 # error out because resource was illegal 659 throw new Exception(
"Invalid Resource passed in for privilege" 660 .
" set comparison.");
670 private function ValidOperatorsForFieldType($FieldType)
681 $ValidOps = [
"==",
"!=",
"<=",
"<",
">=",
">"];
686 $ValidOps = [
"==",
"!="];
705 private function NormalizeTargetValue($FieldType, $User, $Value)
711 # "Now" is encoded as NULL for timestamp and date comparisons 716 # otherwise, parse the value to get a numeric timestamp 719 $Value = strtotime($Value);
724 # "Current user" is encoded as NULL for user comparisons 727 $Value = $User->Id();
732 # no normalization needed for other field types 753 private function GetNormalizedFieldValue($Field, $Resource, $User)
755 # if we have a cached normalized value for this field, 756 # use that for comparisons 757 $CacheKey = $Resource->Id().
"_".$Field->Id();
758 if (!isset(self::$ValueCache[$CacheKey]))
760 # if the given field comes from the User schema and our 761 # resource does not, evaluate this comparison against the 762 # provided $User rather than the provided $Resource 763 # (this allows conditions like User: Zip Code = XXX to 764 # work as expected rather than being skipped) 765 if ($Field->SchemaId() != $Resource->SchemaId() &&
768 $FieldValue = $User->Get($Field);
772 # Note: Resource::Get() on a ControlledName with 773 # IncludeVariants=TRUE does not return CNIds for 774 # array indexes, which will break the normalization 775 # below, so do not change this to add $IncludeVariants 776 # without revising the normalization code below 777 $FieldValue = $Resource->Get($Field);
780 # normalize field value for comparison 781 switch ($Field->Type())
785 # get the UserIds or CNIds from this field 786 $FieldValue = array_keys($FieldValue);
791 # convert returned value to a numeric timestamp 792 $FieldValue = strtotime($FieldValue);
797 # no conversion needed 801 throw new Exception(
"Unsupported metadata field type (" 802 .print_r($Field->Type(), TRUE)
803 .
") for condition in privilege set with resource.");
807 # cache the normalized value for subsequent reuse 808 self::$ValueCache[$CacheKey] = $FieldValue;
811 return self::$ValueCache[$CacheKey];
822 private function CompareNormalizedFieldValues($FieldValue, $Operator, $Value)
824 # compare field value and supplied value using specified operator 826 # if this is a multi-value field, be sure that the provided 827 # operator makes sense 828 if (is_array($FieldValue) && !in_array($Operator, [
"==",
"!="]) )
831 "Multiple-value fields ony support == and != operators");
837 if (is_array($FieldValue))
839 # equality against multi-value fields is 840 # interpreted as 'contains', true if the 841 # target value is one of those set 842 $Result = in_array($Value, $FieldValue);
846 $Result = ($FieldValue == $Value);
851 if (is_array($FieldValue))
853 # not equal against multi-value fields is 854 # interpreted as 'does not contain', true as long as 855 # the target value is not one of those set 856 $Result = !in_array($Value, $FieldValue);
860 $Result = ($FieldValue != $Value);
865 $Result = ($FieldValue < $Value);
869 $Result = ($FieldValue > $Value);
873 $Result = ($FieldValue <= $Value);
877 $Result = ($FieldValue >= $Value);
881 throw new Exception(
"Unsupported condition operator (" 882 .print_r($Operator, TRUE).
") in privilege set.");
886 # report to caller whether condition was met 887 return $Result ? TRUE : FALSE;
900 private function CountResourcesThatSatisfyCondition(
901 $User, $Field, $Operator, $Value)
903 # get the SchemaId for this field 904 $ScId = $Field->SchemaId();
906 # pull out an RFactory for the field's schema 907 if (!isset($this->RFactories[$ScId]))
912 switch ($Field->Type())
919 $ValuesToMatch = array(
920 $Field->Id() => $Value,
923 $Matches = $this->RFactories[$ScId]->GetMatchingResources(
924 $ValuesToMatch, TRUE, FALSE, $Operator);
926 $Count = count($Matches);
930 # find the number of resources associated with this option 931 $Count = $this->RFactories[$ScId]->AssociatedVisibleResourceCount(
932 $Value, $User, TRUE);
934 # if our Op was !=, then subtract the resources 935 # that have the spec'd option out of the total to 936 # figure out how many lack the option 937 if ($Operator ==
"!=")
939 $Count = $this->RFactories[$ScId]->GetVisibleResourcesCount(
946 throw new Exception(
"Unsupported metadata field type (" 947 .print_r($Field->Type(), TRUE)
948 .
") for condition in privilege set without resource.");
963 private function IsInPrivilegeData($Item)
965 # step through privilege data 966 foreach ($this->Privileges as $Priv)
968 # report to caller if item is found 969 if (is_object($Item))
971 if (is_object($Priv) && ($Item == $Priv)) {
return TRUE; }
973 elseif (is_array($Item))
975 if (is_array($Priv) && ($Item == $Priv)) {
return TRUE; }
977 elseif ($Item == $Priv) {
return TRUE; }
980 # report to caller that item is not in privilege data
RemoveCondition($Field, $Value=NULL, $Operator="==", $IncludeSubsets=FALSE)
Remove condition from privilege set.
AddSet(PrivilegeSet $Set)
Add subgroup of privileges/conditions to set.
GetPossibleNecessaryPrivileges()
Get all privileges that could be necessary to fulfill privilege set requirements. ...
ComparisonCount()
Get number of privilege comparisons in set, including those in subgroups.
HasPriv($Privilege, $Privileges=NULL)
Determine if a user has a given privilege, or satisfies the conditions specified by a given privilege...
Set of privileges used to access resource information or other parts of the system.
MeetsRequirements(CWUser $User, $Resource=self::NO_RESOURCE)
Determine if a given user meets the requirements specified by this PrivilegeSet.
ChecksField($FieldId)
Determine if a PrivilegeSet checks values from a specified field.
__construct($Data=NULL)
Class constructor, used to create a new set or reload an existing set from previously-constructed dat...
IncludesPrivilege($Privilege)
Check whether this privilege set includes the specified privilege.
GetPrivilegeInfo()
Get privilege information as an array, with numerical indexes except for the logic, which is contained in a element with the index "Logic".
AddPrivilege($Privileges)
Add specified privilege to set.
PrivilegeFlagsChecked()
List which privilege flags (e.g.
GetPrivilegeList()
Get list of privileges.
Data($NewValue=NULL)
Get/set privilege set data, in the form of an opaque string.
Represents a "resource" in CWIS.
FieldsWithUserComparisons($ComparisonType=NULL)
List which fields in this privset are involved in UserIs or UserIsNot comparisons for this privilege ...
static ClearCaches()
Clear internal caches.
Factory for Resource objects.
CWIS-specific user class.
AddCondition($Field, $Value=NULL, $Operator="==")
Add condition to privilege set.
RemovePrivilege($Privilege)
Remove specified privilege from set.
AllRequired($NewValue=NULL)
Get/set whether all privileges/conditions in set are required (i.e.