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 spcified 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 158 # if there are necessary privileges for this privilege set 161 if (count($ReqPrivs))
163 # start with only those users who have at least one of those privileges 164 $UserIds = array_keys($UFactory->GetUsersWithPrivileges($ReqPrivs));
168 # start with all users 169 $UserIds = $UFactory->GetUserIds();
172 # determine of individual resources will need to be checked 173 $NeedToCheckResources =
177 # build up a list of matching users 178 $UsersThatMeetReqs = array();
180 # iterate over all the users 181 foreach ($UserIds as $UserId)
184 $User =
new CWUser($UserId);
186 if ($NeedToCheckResources)
188 # iterate over all the resources 189 foreach ($ResourceIds as $ResourceId)
191 # if we're running low on memory, nuke the resource cache 192 if ($GLOBALS[
"AF"]->GetFreeMemory() /
193 $GLOBALS[
"AF"]->GetPhpMemoryLimit() < self::$LowMemoryThresh)
195 self::$ResourceCache = [];
199 if (!isset(self::$ResourceCache[$ResourceId]))
201 self::$ResourceCache[$ResourceId] =
new Resource($ResourceId);
204 # if user meets requirements for set 206 $User, self::$ResourceCache[$ResourceId]))
208 # add resource to user's list 209 $UsersThatMeetReqs[$UserId][] = $ResourceId;
215 # if user meets requirements for set 219 $UsersThatMeetReqs[$UserId] = $ResourceIds;
224 # return IDs for users that meet requirements to caller 225 return $UsersThatMeetReqs;
236 # convert incoming value to array if needed 237 if (!is_array($Privileges))
239 $Privileges = array($Privileges);
242 # for each privilege passed in 243 foreach ($Privileges as $Privilege)
245 # add privilege if not currently in set 248 if (is_object($Privilege)) { $Privilege = $Privilege->Id(); }
249 $this->Privileges[] = $Privilege;
262 # remove privilege if currently in set 265 if (is_object($Privilege)) { $Privilege = $Privilege->Id(); }
266 $Index = array_search($Privilege, $this->Privileges);
267 unset($this->Privileges[$Index]);
278 # check whether privilege is in our list and report to caller 279 if (is_object($Privilege)) { $Privilege = $Privilege->Id(); }
280 return $this->IsInPrivilegeData($Privilege) ? TRUE : FALSE;
293 # grab privilege information and add logic 294 $Info = $this->Privileges;
295 $Info[
"Logic"] = $this->Logic;
297 # return privilege info array to caller 309 # create list of privileges with conditions stripped out 311 foreach ($this->Privileges as $Priv)
313 if (!is_array($Priv)) { $List[] = $Priv; }
316 # return list of privileges to caller 339 $FieldId = is_object($Field) ? $Field->Id() : $Field;
341 # make sure we were not passed an invalid field 344 throw new InvalidArgumentException(
"Field with negative ID supplied.");
347 # set up condition array 349 "FieldId" => intval($FieldId),
350 "Operator" => trim($Operator),
353 # if condition is not already in set 354 if (!$this->IsInPrivilegeData($Condition))
356 # add condition to privilege set 357 $this->Privileges[] = $Condition;
377 $Field, $Value = NULL, $Operator =
"==",
378 $IncludeSubsets = FALSE)
383 $FieldId = is_object($Field) ? $Field->Id() : $Field;
385 # set up condition array 387 "FieldId" => intval($FieldId),
388 "Operator" => trim($Operator),
391 # if condition is in set 392 if ($this->IsInPrivilegeData($Condition))
394 # remove condition from privilege set 395 $Index = array_search($Condition, $this->Privileges);
396 unset($this->Privileges[$Index]);
402 foreach ($this->Privileges as $Priv)
406 $Result |= $Priv->RemoveCondition(
407 $FieldId, $Value, $Operator, TRUE);
421 # if subgroup is not already in set 422 if (!$this->IsInPrivilegeData($Set))
424 # add subgroup to privilege set 425 $this->Privileges[] = $Set;
440 if ($NewValue !== NULL)
442 $this->Logic = $NewValue ?
"AND" :
"OR";
444 return ($this->Logic ==
"AND") ? TRUE : FALSE;
455 unset($Info[
"Logic"]);
458 foreach ($Info as $Item)
460 if (is_object($Item))
462 $Result = array_merge($Result, $Item->PrivilegeFlagsChecked() );
464 elseif (!is_array($Item))
469 return array_unique($Result);
482 unset($Info[
"Logic"]);
485 foreach ($Info as $Item)
487 if (is_object($Item))
489 $Result = array_merge(
491 $Item->FieldsWithUserComparisons($ComparisonType));
493 elseif (is_array($Item))
495 if ( (($Item[
"Operator"] == $ComparisonType)
496 || ($ComparisonType === NULL)) &&
503 $Result[]= $Item[
"FieldId"];
509 return array_unique($Result);
519 foreach ($this->Privileges as $Priv)
521 $Count += is_object($Priv) ? $Priv->ComparisonCount() : 1;
536 # for each privilege requirement 537 $NecessaryPrivs = array();
538 foreach ($this->Privileges as $Priv)
540 # if requirement is comparison 544 if ($this->Logic ==
"OR")
546 # bail out because no privileges are required 550 # else if requirement is subgroup 551 elseif (is_object($Priv))
553 # retrieve possible needed privileges from subgroup 554 $SubPrivs = $Priv->GetPossibleNecessaryPrivileges();
556 # if no privileges were required by subgroup 557 if (!count($SubPrivs))
560 if ($this->Logic ==
"OR")
562 # bail out because no privileges are required 568 # add subgroup privileges to required list 569 $NecessaryPrivs = array_merge($NecessaryPrivs, $SubPrivs);
572 # else requirement is privilege 575 # add privilege to required list 576 $NecessaryPrivs[] = $Priv;
580 # return possible needed privileges to caller 581 return $NecessaryPrivs;
591 # iterate over all the privs in this privset 592 foreach ($this->Privileges as $Priv)
594 # if this priv is a field condition that references the 595 # provided FieldId, return true 596 if (is_array($Priv) && $Priv[
"FieldId"] == $FieldId)
600 # otherwise, if this was a privset then call ourself recursively 608 # found no references to this field, return FALSE 618 self::$MetadataFieldCache = array();
619 self::$ResourceCache = array();
620 self::$ValueCache = array();
623 # ---- PRIVATE INTERFACE ------------------------------------------------- 625 private $RFactories = array();
626 private $Privileges = array();
627 private $Logic =
"OR";
629 private static $MetadataFieldCache;
630 private static $ResourceCache;
631 private static $ValueCache;
633 private static $LowMemoryThresh = 0.25;
642 private function LoadFromData($Serialized)
644 # save calling context in case load causes out-of-memory crash 645 $GLOBALS[
"AF"]->RecordContextInCaseOfCrash();
648 $Data = unserialize($Serialized);
651 throw new InvalidArgumentException(
652 "Invalid serialized data supplied (\"".$Serialized.
"\").");
655 # unpack privilege data (if available) and load 656 if (array_key_exists(
"Privileges", $Data))
658 $this->Privileges = array();
659 foreach ($Data[
"Privileges"] as $Priv)
661 if (is_array($Priv) && array_key_exists(
"SUBSET", $Priv))
664 $Subset->LoadFromData($Priv[
"SUBSET"]);
665 $this->Privileges[] = $Subset;
669 $this->Privileges[] = $Priv;
674 # load logic if available 675 if (array_key_exists(
"Logic", $Data))
677 $this->Logic = $Data[
"Logic"];
688 private function MeetsCondition($Condition, $Resource, $User)
690 # make sure metadata field is loaded 691 $MFieldId = $Condition[
"FieldId"];
692 if (!isset(self::$MetadataFieldCache[$MFieldId]))
694 self::$MetadataFieldCache[$MFieldId] =
700 # if the specified field does not exist 701 if (self::$MetadataFieldCache[$MFieldId] === FALSE)
703 # return a result that in effect ignores the condition 704 return ($this->Logic ==
"AND") ? TRUE : FALSE;
707 # pull out provided field 708 $Field = self::$MetadataFieldCache[$MFieldId];
709 $Operator = $Condition[
"Operator"];
710 $Value = $Condition[
"Value"];
712 # determine if the provided operator is valid for the provided field 713 if (!in_array($Operator, $this->ValidOperatorsForFieldType($Field->Type()) ))
715 throw new Exception(
"Operator ".$Operator.
" not supported for " 716 .$Field->TypeAsName().
" fields");
719 # if we don't have a specific resource to check, then we want 720 # to determine if this condition would be satisfied by any 722 if ($Resource == self::NO_RESOURCE)
724 $Count = $this->CountResourcesThatSatisfyCondition(
725 $User, $Field, $Operator, $Value);
726 return $Count > 0 ? TRUE : FALSE;
728 # else if resource is valid 729 elseif ($Resource instanceof
Resource)
731 # if this field is from a different schema than our resource 732 # and also this field is not from the User schema, then there's 733 # no comparison for us to do 734 if ($Field->SchemaId() != $Resource->SchemaId() &&
737 # return a result that in effect ignores the condition 738 return ($this->Logic ==
"AND") ? TRUE : FALSE;
741 # normalize the incoming value for comparison 742 $Value = $this->NormalizeTargetValue($Field->Type(), $User, $Value);
743 $FieldValue = $this->GetNormalizedFieldValue($Field, $Resource, $User);
745 # perform comparison, returning result 746 return $this->CompareNormalizedFieldValues($FieldValue, $Operator, $Value);
750 # error out because resource was illegal 751 throw new Exception(
"Invalid Resource passed in for privilege" 752 .
" set comparison.");
762 private function ValidOperatorsForFieldType($FieldType)
773 $ValidOps = [
"==",
"!=",
"<=",
"<",
">=",
">"];
778 $ValidOps = [
"==",
"!="];
797 private function NormalizeTargetValue($FieldType, $User, $Value)
803 # "Now" is encoded as NULL for timestamp and date comparisons 808 # otherwise, parse the value to get a numeric timestamp 811 $Value = strtotime($Value);
816 # "Current user" is encoded as NULL for user comparisons 819 $Value = $User->Id();
824 # no normalization needed for other field types 845 private function GetNormalizedFieldValue($Field, $Resource, $User)
847 # if we have a cached normalized value for this field, 848 # use that for comparisons 849 $CacheKey = $Resource->Id().
"_".$Field->Id();
850 if (!isset(self::$ValueCache[$CacheKey]))
852 # if the given field comes from the User schema and our 853 # resource does not, evaluate this comparison against the 854 # provided $User rather than the provided $Resource 855 # (this allows conditions like User: Zip Code = XXX to 856 # work as expected rather than being skipped) 857 if ($Field->SchemaId() != $Resource->SchemaId() &&
860 $FieldValue = $User->Get($Field);
864 # Note: Resource::Get() on a ControlledName with 865 # IncludeVariants=TRUE does not return CNIds for 866 # array indexes, which will break the normalization 867 # below, so do not change this to add $IncludeVariants 868 # without revising the normalization code below 869 $FieldValue = $Resource->Get($Field);
872 # normalize field value for comparison 873 switch ($Field->Type())
877 # get the UserIds or CNIds from this field 878 $FieldValue = array_keys($FieldValue);
883 # convert returned value to a numeric timestamp 884 $FieldValue = strtotime($FieldValue);
889 # no conversion needed 893 throw new Exception(
"Unsupported metadata field type (" 894 .print_r($Field->Type(), TRUE)
895 .
") for condition in privilege set with resource.");
899 # cache the normalized value for subsequent reuse 900 self::$ValueCache[$CacheKey] = $FieldValue;
903 return self::$ValueCache[$CacheKey];
914 private function CompareNormalizedFieldValues($FieldValue, $Operator, $Value)
916 # compare field value and supplied value using specified operator 918 # if this is a multi-value field, be sure that the provided 919 # operator makes sense 920 if (is_array($FieldValue) && !in_array($Operator, [
"==",
"!="]) )
923 "Multiple-value fields ony support == and != operators");
929 if (is_array($FieldValue))
931 # equality against multi-value fields is 932 # interpreted as 'contains', true if the 933 # target value is one of those set 934 $Result = in_array($Value, $FieldValue);
938 $Result = ($FieldValue == $Value);
943 if (is_array($FieldValue))
945 # not equal against multi-value fields is 946 # interpreted as 'does not contain', true as long as 947 # the target value is not one of those set 948 $Result = !in_array($Value, $FieldValue);
952 $Result = ($FieldValue != $Value);
957 $Result = ($FieldValue < $Value);
961 $Result = ($FieldValue > $Value);
965 $Result = ($FieldValue <= $Value);
969 $Result = ($FieldValue >= $Value);
973 throw new Exception(
"Unsupported condition operator (" 974 .print_r($Operator, TRUE).
") in privilege set.");
978 # report to caller whether condition was met 979 return $Result ? TRUE : FALSE;
992 private function CountResourcesThatSatisfyCondition(
993 $User, $Field, $Operator, $Value)
995 # get the SchemaId for this field 996 $ScId = $Field->SchemaId();
998 # pull out an RFactory for the field's schema 999 if (!isset($this->RFactories[$ScId]))
1004 switch ($Field->Type())
1011 $ValuesToMatch = array(
1012 $Field->Id() => $Value,
1015 $Matches = $this->RFactories[$ScId]->GetMatchingResources(
1016 $ValuesToMatch, TRUE, FALSE, $Operator);
1018 $Count = count($Matches);
1022 # find the number of resources associated with this option 1023 $Count = $this->RFactories[$ScId]->AssociatedVisibleResourceCount(
1024 $Value, $User, TRUE);
1026 # if our Op was !=, then subtract the resources 1027 # that have the spec'd option out of the total to 1028 # figure out how many lack the option 1029 if ($Operator ==
"!=")
1031 $Count = $this->RFactories[$ScId]->GetVisibleResourcesCount(
1038 throw new Exception(
"Unsupported metadata field type (" 1039 .print_r($Field->Type(), TRUE)
1040 .
") for condition in privilege set without resource.");
1055 private function IsInPrivilegeData($Item)
1057 # step through privilege data 1058 foreach ($this->Privileges as $Priv)
1060 # report to caller if item is found 1061 if (is_object($Item))
1063 if (is_object($Priv) && ($Item == $Priv)) {
return TRUE; }
1065 elseif (is_array($Item))
1067 if (is_array($Priv) && ($Item == $Priv)) {
return TRUE; }
1069 elseif ($Item == $Priv) {
return TRUE; }
1072 # 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. ...
FindUsersThatMeetRequirements($ResourceIds=array())
Find all users that meet the requirements for this privilege set.
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.
CWIS-specific user factory class.
__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.