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();
173 $UsersThatMeetReqs = array();
174 foreach ($UserIds as $UserId)
177 $User =
new CWUser($UserId);
179 # if resources were specified 180 if (count($ResourceIds))
183 foreach ($ResourceIds as $ResourceId)
185 # if we're running low on memory, nuke the resource cache 186 if ($GLOBALS[
"AF"]->GetFreeMemory() /
187 $GLOBALS[
"AF"]->GetPhpMemoryLimit() < self::$LowMemoryThresh)
189 self::$ResourceCache = [];
193 if (!isset(self::$ResourceCache[$ResourceId]))
195 self::$ResourceCache[$ResourceId] =
new Resource($ResourceId);
198 # if user meets requirements for set 200 $User, self::$ResourceCache[$ResourceId]))
203 $UsersThatMeetReqs[$UserId][] = $ResourceId;
209 # if user meets requirements for set 213 $UsersThatMeetReqs[$UserId] = array();
218 # return IDs for users that meet requirements to caller 219 return $UsersThatMeetReqs;
230 # convert incoming value to array if needed 231 if (!is_array($Privileges))
233 $Privileges = array($Privileges);
236 # for each privilege passed in 237 foreach ($Privileges as $Privilege)
239 # add privilege if not currently in set 242 if (is_object($Privilege)) { $Privilege = $Privilege->Id(); }
243 $this->Privileges[] = $Privilege;
256 # remove privilege if currently in set 259 if (is_object($Privilege)) { $Privilege = $Privilege->Id(); }
260 $Index = array_search($Privilege, $this->Privileges);
261 unset($this->Privileges[$Index]);
272 # check whether privilege is in our list and report to caller 273 if (is_object($Privilege)) { $Privilege = $Privilege->Id(); }
274 return $this->IsInPrivilegeData($Privilege) ? TRUE : FALSE;
287 # grab privilege information and add logic 288 $Info = $this->Privileges;
289 $Info[
"Logic"] = $this->Logic;
291 # return privilege info array to caller 303 # create list of privileges with conditions stripped out 305 foreach ($this->Privileges as $Priv)
307 if (!is_array($Priv)) { $List[] = $Priv; }
310 # return list of privileges to caller 333 $FieldId = is_object($Field) ? $Field->Id() : $Field;
335 # make sure we were not passed an invalid field 338 throw new InvalidArgumentException(
"Field with negative ID supplied.");
341 # set up condition array 343 "FieldId" => intval($FieldId),
344 "Operator" => trim($Operator),
347 # if condition is not already in set 348 if (!$this->IsInPrivilegeData($Condition))
350 # add condition to privilege set 351 $this->Privileges[] = $Condition;
371 $Field, $Value = NULL, $Operator =
"==",
372 $IncludeSubsets = FALSE)
377 $FieldId = is_object($Field) ? $Field->Id() : $Field;
379 # set up condition array 381 "FieldId" => intval($FieldId),
382 "Operator" => trim($Operator),
385 # if condition is in set 386 if ($this->IsInPrivilegeData($Condition))
388 # remove condition from privilege set 389 $Index = array_search($Condition, $this->Privileges);
390 unset($this->Privileges[$Index]);
396 foreach ($this->Privileges as $Priv)
400 $Result |= $Priv->RemoveCondition(
401 $FieldId, $Value, $Operator, TRUE);
415 # if subgroup is not already in set 416 if (!$this->IsInPrivilegeData($Set))
418 # add subgroup to privilege set 419 $this->Privileges[] = $Set;
434 if ($NewValue !== NULL)
436 $this->Logic = $NewValue ?
"AND" :
"OR";
438 return ($this->Logic ==
"AND") ? TRUE : FALSE;
449 unset($Info[
"Logic"]);
452 foreach ($Info as $Item)
454 if (is_object($Item))
456 $Result = array_merge($Result, $Item->PrivilegeFlagsChecked() );
458 elseif (!is_array($Item))
463 return array_unique($Result);
476 unset($Info[
"Logic"]);
479 foreach ($Info as $Item)
481 if (is_object($Item))
483 $Result = array_merge(
485 $Item->FieldsWithUserComparisons($ComparisonType));
487 elseif (is_array($Item))
489 if ( (($Item[
"Operator"] == $ComparisonType)
490 || ($ComparisonType === NULL)) &&
497 $Result[]= $Item[
"FieldId"];
503 return array_unique($Result);
513 foreach ($this->Privileges as $Priv)
515 $Count += is_object($Priv) ? $Priv->ComparisonCount() : 1;
530 # for each privilege requirement 531 $NecessaryPrivs = array();
532 foreach ($this->Privileges as $Priv)
534 # if requirement is comparison 538 if ($this->Logic ==
"OR")
540 # bail out because no privileges are required 544 # else if requirement is subgroup 545 elseif (is_object($Priv))
547 # retrieve possible needed privileges from subgroup 548 $SubPrivs = $Priv->GetPossibleNecessaryPrivileges();
550 # if no privileges were required by subgroup 551 if (!count($SubPrivs))
554 if ($this->Logic ==
"OR")
556 # bail out because no privileges are required 562 # add subgroup privileges to required list 563 $NecessaryPrivs = array_merge($NecessaryPrivs, $SubPrivs);
566 # else requirement is privilege 569 # add privilege to required list 570 $NecessaryPrivs[] = $Priv;
574 # return possible needed privileges to caller 575 return $NecessaryPrivs;
585 # iterate over all the privs in this privset 586 foreach ($this->Privileges as $Priv)
588 # if this priv is a field condition that references the 589 # provided FieldId, return true 590 if (is_array($Priv) && $Priv[
"FieldId"] == $FieldId)
594 # otherwise, if this was a privset then call ourself recursively 602 # found no references to this field, return FALSE 612 self::$MetadataFieldCache = array();
613 self::$ResourceCache = array();
614 self::$ValueCache = array();
617 # ---- PRIVATE INTERFACE ------------------------------------------------- 619 private $RFactories = array();
620 private $Privileges = array();
621 private $Logic =
"OR";
623 private static $MetadataFieldCache;
624 private static $ResourceCache;
625 private static $ValueCache;
627 private static $LowMemoryThresh = 0.25;
636 private function LoadFromData($Serialized)
638 # save calling context in case load causes out-of-memory crash 639 $GLOBALS[
"AF"]->RecordContextInCaseOfCrash();
642 $Data = unserialize($Serialized);
645 throw new InvalidArgumentException(
646 "Invalid serialized data supplied (\"".$Serialized.
"\").");
649 # unpack privilege data (if available) and load 650 if (array_key_exists(
"Privileges", $Data))
652 $this->Privileges = array();
653 foreach ($Data[
"Privileges"] as $Priv)
655 if (is_array($Priv) && array_key_exists(
"SUBSET", $Priv))
658 $Subset->LoadFromData($Priv[
"SUBSET"]);
659 $this->Privileges[] = $Subset;
663 $this->Privileges[] = $Priv;
668 # load logic if available 669 if (array_key_exists(
"Logic", $Data))
671 $this->Logic = $Data[
"Logic"];
682 private function MeetsCondition($Condition, $Resource, $User)
684 # make sure metadata field is loaded 685 $MFieldId = $Condition[
"FieldId"];
686 if (!isset(self::$MetadataFieldCache[$MFieldId]))
688 self::$MetadataFieldCache[$MFieldId] =
694 # if the specified field does not exist 695 if (self::$MetadataFieldCache[$MFieldId] === FALSE)
697 # return a result that in effect ignores the condition 698 return ($this->Logic ==
"AND") ? TRUE : FALSE;
701 # pull out provided field 702 $Field = self::$MetadataFieldCache[$MFieldId];
703 $Operator = $Condition[
"Operator"];
704 $Value = $Condition[
"Value"];
706 # determine if the provided operator is valid for the provided field 707 if (!in_array($Operator, $this->ValidOperatorsForFieldType($Field->Type()) ))
709 throw new Exception(
"Operator ".$Operator.
" not supported for " 710 .$Field->TypeAsName().
" fields");
713 # if we don't have a specific resource to check, then we want 714 # to determine if this condition would be satisfied by any 716 if ($Resource == self::NO_RESOURCE)
718 $Count = $this->CountResourcesThatSatisfyCondition(
719 $User, $Field, $Operator, $Value);
720 return $Count > 0 ? TRUE : FALSE;
722 # else if resource is valid 723 elseif ($Resource instanceof
Resource)
725 # if this field is from a different schema than our resource 726 # and also this field is not from the User schema, then there's 727 # no comparison for us to do 728 if ($Field->SchemaId() != $Resource->SchemaId() &&
731 # return a result that in effect ignores the condition 732 return ($this->Logic ==
"AND") ? TRUE : FALSE;
735 # normalize the incoming value for comparison 736 $Value = $this->NormalizeTargetValue($Field->Type(), $User, $Value);
737 $FieldValue = $this->GetNormalizedFieldValue($Field, $Resource, $User);
739 # perform comparison, returning result 740 return $this->CompareNormalizedFieldValues($FieldValue, $Operator, $Value);
744 # error out because resource was illegal 745 throw new Exception(
"Invalid Resource passed in for privilege" 746 .
" set comparison.");
756 private function ValidOperatorsForFieldType($FieldType)
767 $ValidOps = [
"==",
"!=",
"<=",
"<",
">=",
">"];
772 $ValidOps = [
"==",
"!="];
791 private function NormalizeTargetValue($FieldType, $User, $Value)
797 # "Now" is encoded as NULL for timestamp and date comparisons 802 # otherwise, parse the value to get a numeric timestamp 805 $Value = strtotime($Value);
810 # "Current user" is encoded as NULL for user comparisons 813 $Value = $User->Id();
818 # no normalization needed for other field types 839 private function GetNormalizedFieldValue($Field, $Resource, $User)
841 # if we have a cached normalized value for this field, 842 # use that for comparisons 843 $CacheKey = $Resource->Id().
"_".$Field->Id();
844 if (!isset(self::$ValueCache[$CacheKey]))
846 # if the given field comes from the User schema and our 847 # resource does not, evaluate this comparison against the 848 # provided $User rather than the provided $Resource 849 # (this allows conditions like User: Zip Code = XXX to 850 # work as expected rather than being skipped) 851 if ($Field->SchemaId() != $Resource->SchemaId() &&
854 $FieldValue = $User->Get($Field);
858 # Note: Resource::Get() on a ControlledName with 859 # IncludeVariants=TRUE does not return CNIds for 860 # array indexes, which will break the normalization 861 # below, so do not change this to add $IncludeVariants 862 # without revising the normalization code below 863 $FieldValue = $Resource->Get($Field);
866 # normalize field value for comparison 867 switch ($Field->Type())
871 # get the UserIds or CNIds from this field 872 $FieldValue = array_keys($FieldValue);
877 # convert returned value to a numeric timestamp 878 $FieldValue = strtotime($FieldValue);
883 # no conversion needed 887 throw new Exception(
"Unsupported metadata field type (" 888 .print_r($Field->Type(), TRUE)
889 .
") for condition in privilege set with resource.");
893 # cache the normalized value for subsequent reuse 894 self::$ValueCache[$CacheKey] = $FieldValue;
897 return self::$ValueCache[$CacheKey];
908 private function CompareNormalizedFieldValues($FieldValue, $Operator, $Value)
910 # compare field value and supplied value using specified operator 912 # if this is a multi-value field, be sure that the provided 913 # operator makes sense 914 if (is_array($FieldValue) && !in_array($Operator, [
"==",
"!="]) )
917 "Multiple-value fields ony support == and != operators");
923 if (is_array($FieldValue))
925 # equality against multi-value fields is 926 # interpreted as 'contains', true if the 927 # target value is one of those set 928 $Result = in_array($Value, $FieldValue);
932 $Result = ($FieldValue == $Value);
937 if (is_array($FieldValue))
939 # not equal against multi-value fields is 940 # interpreted as 'does not contain', true as long as 941 # the target value is not one of those set 942 $Result = !in_array($Value, $FieldValue);
946 $Result = ($FieldValue != $Value);
951 $Result = ($FieldValue < $Value);
955 $Result = ($FieldValue > $Value);
959 $Result = ($FieldValue <= $Value);
963 $Result = ($FieldValue >= $Value);
967 throw new Exception(
"Unsupported condition operator (" 968 .print_r($Operator, TRUE).
") in privilege set.");
972 # report to caller whether condition was met 973 return $Result ? TRUE : FALSE;
986 private function CountResourcesThatSatisfyCondition(
987 $User, $Field, $Operator, $Value)
989 # get the SchemaId for this field 990 $ScId = $Field->SchemaId();
992 # pull out an RFactory for the field's schema 993 if (!isset($this->RFactories[$ScId]))
998 switch ($Field->Type())
1005 $ValuesToMatch = array(
1006 $Field->Id() => $Value,
1009 $Matches = $this->RFactories[$ScId]->GetMatchingResources(
1010 $ValuesToMatch, TRUE, FALSE, $Operator);
1012 $Count = count($Matches);
1016 # find the number of resources associated with this option 1017 $Count = $this->RFactories[$ScId]->AssociatedVisibleResourceCount(
1018 $Value, $User, TRUE);
1020 # if our Op was !=, then subtract the resources 1021 # that have the spec'd option out of the total to 1022 # figure out how many lack the option 1023 if ($Operator ==
"!=")
1025 $Count = $this->RFactories[$ScId]->GetVisibleResourcesCount(
1032 throw new Exception(
"Unsupported metadata field type (" 1033 .print_r($Field->Type(), TRUE)
1034 .
") for condition in privilege set without resource.");
1049 private function IsInPrivilegeData($Item)
1051 # step through privilege data 1052 foreach ($this->Privileges as $Priv)
1054 # report to caller if item is found 1055 if (is_object($Item))
1057 if (is_object($Priv) && ($Item == $Priv)) {
return TRUE; }
1059 elseif (is_array($Item))
1061 if (is_array($Priv) && ($Item == $Priv)) {
return TRUE; }
1063 elseif ($Item == $Priv) {
return TRUE; }
1066 # 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.