CWIS Developer Documentation
PrivilegeSet.php
Go to the documentation of this file.
1 <?PHP
2 #
3 # FILE: PrivilegeSet.php
4 #
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/
8 #
9 
17 {
27  public function __construct($Data = NULL)
28  {
29  # if privilege data supplied
30  if ($Data !== NULL)
31  {
32  # if data is an array of privileges
33  if (is_array($Data))
34  {
35  # set internal privilege set from array
36  $this->Privileges = $Data;
37  }
38  # else if data is a single privilege
39  elseif (is_numeric($Data))
40  {
41  # set internal privilege set from data
42  $this->Privileges = array($Data);
43  }
44  else
45  {
46  # set internal values from data
47  $this->LoadFromData($Data);
48  }
49  }
50  }
51 
62  public function Data($NewValue = NULL)
63  {
64  # if new data supplied
65  if ($NewValue !== NULL)
66  {
67  # unpack privilege data and load
68  $this->LoadFromData($NewValue);
69  }
70 
71  # serialize current data and return to caller
72  $Data = array();
73  if (count($this->Privileges))
74  {
75  foreach ($this->Privileges as $Priv)
76  {
77  $Data["Privileges"][] = is_object($Priv)
78  ? array("SUBSET" => $Priv->Data())
79  : $Priv;
80  }
81  }
82  $Data["Logic"] = $this->Logic;
83  return serialize($Data);
84  }
85 
96  public function MeetsRequirements(CWUser $User, $Resource = self::NO_RESOURCE)
97  {
98  # when there are no requirements, then every user meets them
99  $Satisfied = TRUE;
100 
101  # for each privilege requirement
102  foreach ($this->Privileges as $Priv)
103  {
104  # if privilege is actually a privilege subgroup
105  if (is_object($Priv))
106  {
107  # check if the subgroup is satisfied
108  $Satisfied = $Priv->MeetsRequirements($User, $Resource);
109  }
110  # else if privilege is actually a condition
111  elseif (is_array($Priv))
112  {
113  # check if condition is satisfied for the given resource
114  $Satisfied = $this->MeetsCondition($Priv, $Resource, $User);
115  }
116  # else privilege is actually a privilege
117  else
118  {
119  # check if user has the specified privilege
120  $Satisfied = $User->HasPriv( $Priv );
121  }
122 
123  # for AND logic, we can bail as soon as the first
124  # condition is not met
125  if ($this->Logic == "AND")
126  {
127  if (!$Satisfied)
128  {
129  break;
130  }
131  }
132  # conversely, for OR logic, we can bail as soon as any
133  # condition is met
134  else
135  {
136  if ($Satisfied)
137  {
138  break;
139  }
140  }
141  }
142 
143  # report result of the test back to caller
144  return $Satisfied;
145  }
146 
153  public function AddPrivilege($Privileges)
154  {
155  # convert incoming value to array if needed
156  if (!is_array($Privileges))
157  {
158  $Privileges = array($Privileges);
159  }
160 
161  # for each privilege passed in
162  foreach ($Privileges as $Privilege)
163  {
164  # add privilege if not currently in set
165  if (!$this->IncludesPrivilege($Privilege))
166  {
167  if (is_object($Privilege)) { $Privilege = $Privilege->Id(); }
168  $this->Privileges[] = $Privilege;
169  }
170  }
171  }
172 
179  public function RemovePrivilege($Privilege)
180  {
181  # remove privilege if currently in set
182  if ($this->IncludesPrivilege($Privilege))
183  {
184  if (is_object($Privilege)) { $Privilege = $Privilege->Id(); }
185  $Index = array_search($Privilege, $this->Privileges);
186  unset($this->Privileges[$Index]);
187  }
188  }
189 
195  public function IncludesPrivilege($Privilege)
196  {
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;
200  }
201 
210  public function GetPrivilegeInfo()
211  {
212  # grab privilege information and add logic
213  $Info = $this->Privileges;
214  $Info["Logic"] = $this->Logic;
215 
216  # return privilege info array to caller
217  return $Info;
218  }
219 
226  public function GetPrivilegeList()
227  {
228  # create list of privileges with conditions stripped out
229  $List = array();
230  foreach ($this->Privileges as $Priv)
231  {
232  if (!is_array($Priv)) { $List[] = $Priv; }
233  }
234 
235  # return list of privileges to caller
236  return $List;
237  }
238 
254  public function AddCondition($Field, $Value = NULL, $Operator = "==")
255  {
256  # get field ID
258 
259  # set up condition array
260  $Condition = array(
261  "FieldId" => intval($FieldId),
262  "Operator" => trim($Operator),
263  "Value" => $Value);
264 
265  # if condition is not already in set
266  if (!$this->IsInPrivilegeData($Condition))
267  {
268  # add condition to privilege set
269  $this->Privileges[] = $Condition;
270  return TRUE;
271  }
272  return FALSE;
273  }
274 
288  public function RemoveCondition(
289  $Field, $Value = NULL, $Operator = "==",
290  $IncludeSubsets = FALSE)
291  {
292  $Result = FALSE;
293 
294  # get field ID
295  $FieldId = is_object($Field) ? $Field->Id() : $Field;
296 
297  # set up condition array
298  $Condition = array(
299  "FieldId" => intval($FieldId),
300  "Operator" => trim($Operator),
301  "Value" => $Value);
302 
303  # if condition is in set
304  if ($this->IsInPrivilegeData($Condition))
305  {
306  # remove condition from privilege set
307  $Index = array_search($Condition, $this->Privileges);
308  unset($this->Privileges[$Index]);
309  $Result = TRUE;
310  }
311 
312  if ($IncludeSubsets)
313  {
314  foreach ($this->Privileges as $Priv)
315  {
316  if ($Priv instanceof PrivilegeSet)
317  {
318  $Result |= $Priv->RemoveCondition(
319  $FieldId, $Value, $Operator, TRUE);
320  }
321  }
322  }
323 
324  return $Result;
325  }
326 
331  public function AddSet(PrivilegeSet $Set)
332  {
333  # if subgroup is not already in set
334  if (!$this->IsInPrivilegeData($Set))
335  {
336  # add subgroup to privilege set
337  $this->Privileges[] = $Set;
338  }
339  }
340 
350  public function AllRequired($NewValue = NULL)
351  {
352  if ($NewValue !== NULL)
353  {
354  $this->Logic = $NewValue ? "AND" : "OR";
355  }
356  return ($this->Logic == "AND") ? TRUE : FALSE;
357  }
358 
364  public function PrivilegeFlagsChecked()
365  {
366  $Info = $this->GetPrivilegeInfo();
367  unset($Info["Logic"]);
368 
369  $Result = array();
370  foreach ($Info as $Item)
371  {
372  if (is_object($Item))
373  {
374  $Result = array_merge($Result, $Item->PrivilegeFlagsChecked() );
375  }
376  elseif (!is_array($Item))
377  {
378  $Result[]= $Item;
379  }
380  }
381  return array_unique($Result);
382  }
383 
391  public function FieldsWithUserComparisons($ComparisonType = NULL)
392  {
393  $Info = $this->GetPrivilegeInfo();
394  unset($Info["Logic"]);
395 
396  $Result = array();
397  foreach ($Info as $Item)
398  {
399  if (is_object($Item))
400  {
401  $Result = array_merge(
402  $Result,
403  $Item->FieldsWithUserComparisons($ComparisonType));
404  }
405  elseif (is_array($Item))
406  {
407  if ( (($Item["Operator"] == $ComparisonType)
408  || ($ComparisonType === NULL)) &&
409  MetadataSchema::FieldExistsInAnySchema($Item["FieldId"]))
410  {
411  $Field = new MetadataField($Item["FieldId"]);
412 
413  if ($Field->Type() == MetadataSchema::MDFTYPE_USER)
414  {
415  $Result[]= $Item["FieldId"];
416  }
417  }
418  }
419  }
420 
421  return array_unique($Result);
422  }
423 
428  public function ComparisonCount()
429  {
430  $Count = 0;
431  foreach ($this->Privileges as $Priv)
432  {
433  $Count += is_object($Priv) ? $Priv->ComparisonCount() : 1;
434  }
435  return $Count;
436  }
437 
447  {
448  # for each privilege requirement
449  $NecessaryPrivs = array();
450  foreach ($this->Privileges as $Priv)
451  {
452  # if requirement is comparison
453  if (is_array($Priv))
454  {
455  # if logic is OR
456  if ($this->Logic == "OR")
457  {
458  # bail out because no privileges are required
459  return array();
460  }
461  }
462  # else if requirement is subgroup
463  elseif (is_object($Priv))
464  {
465  # retrieve possible needed privileges from subgroup
466  $SubPrivs = $Priv->GetPossibleNecessaryPrivileges();
467 
468  # if no privileges were required by subgroup
469  if (!count($SubPrivs))
470  {
471  # if logic is OR
472  if ($this->Logic == "OR")
473  {
474  # bail out because no privileges are required
475  return array();
476  }
477  }
478  else
479  {
480  # add subgroup privileges to required list
481  $NecessaryPrivs = array_merge($NecessaryPrivs, $SubPrivs);
482  }
483  }
484  # else requirement is privilege
485  else
486  {
487  # add privilege to required list
488  $NecessaryPrivs[] = $Priv;
489  }
490  }
491 
492  # return possible needed privileges to caller
493  return $NecessaryPrivs;
494  }
495 
501  public function ChecksField($FieldId)
502  {
503  # iterate over all the privs in this privset
504  foreach ($this->Privileges as $Priv)
505  {
506  # if this priv is a field condition that references the
507  # provided FieldId, return true
508  if (is_array($Priv) && $Priv["FieldId"] == $FieldId)
509  {
510  return TRUE;
511  }
512  # otherwise, if this was a privset then call ourself recursively
513  elseif ($Priv instanceof PrivilegeSet &&
514  $Priv->ChecksField($FieldId))
515  {
516  return TRUE;
517  }
518  }
519 
520  # found no references to this field, return FALSE
521  return FALSE;
522  }
523 
528  public static function ClearCaches()
529  {
530  self::$MetadataFieldCache = array();
531  self::$ValueCache = array();
532  }
533 
534  # ---- PRIVATE INTERFACE -------------------------------------------------
535 
536  private $RFactories = array();
537  private $Privileges = array();
538  private $Logic = "OR";
539 
540  private static $MetadataFieldCache;
541  private static $ValueCache;
542 
543  const NO_RESOURCE = "XXX NO RESOURCE XXX";
544 
550  private function LoadFromData($Serialized)
551  {
552  # save calling context in case load causes out-of-memory crash
553  $GLOBALS["AF"]->RecordContextInCaseOfCrash();
554 
555  # unpack new data
556  $Data = unserialize($Serialized);
557  if ($Data === FALSE)
558  {
559  throw new InvalidArgumentException(
560  "Invalid serialized data supplied (\"".$Serialized."\").");
561  }
562 
563  # unpack privilege data (if available) and load
564  if (array_key_exists("Privileges", $Data))
565  {
566  $this->Privileges = array();
567  foreach ($Data["Privileges"] as $Priv)
568  {
569  if (is_array($Priv) && array_key_exists("SUBSET", $Priv))
570  {
571  $Subset = new PrivilegeSet();
572  $Subset->LoadFromData($Priv["SUBSET"]);
573  $this->Privileges[] = $Subset;
574  }
575  else
576  {
577  $this->Privileges[] = $Priv;
578  }
579  }
580  }
581 
582  # load logic if available
583  if (array_key_exists("Logic", $Data))
584  {
585  $this->Logic = $Data["Logic"];
586  }
587  }
588 
596  private function MeetsCondition($Condition, $Resource, $User)
597  {
598  # make sure metadata field is loaded
599  $MFieldId = $Condition["FieldId"];
600  if (!isset(self::$MetadataFieldCache[$MFieldId]))
601  {
602  self::$MetadataFieldCache[$MFieldId] =
603  !MetadataSchema::FieldExistsInAnySchema($Condition["FieldId"])
604  ? FALSE
605  : new MetadataField($MFieldId);
606  }
607 
608  # if the specified field does not exist
609  if (self::$MetadataFieldCache[$MFieldId] === FALSE)
610  {
611  # return a result that in effect ignores the condition
612  return ($this->Logic == "AND") ? TRUE : FALSE;
613  }
614 
615  # pull out provided field
616  $Field = self::$MetadataFieldCache[$MFieldId];
617  $Operator = $Condition["Operator"];
618  $Value = $Condition["Value"];
619 
620  # determine if the provided operator is valid for the provided field
621  if (!in_array($Operator, $this->ValidOperatorsForFieldType($Field->Type()) ))
622  {
623  throw new Exception("Operator ".$Operator." not supported for "
624  .$Field->TypeAsName()." fields");
625  }
626 
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
629  # resource
630  if ($Resource == self::NO_RESOURCE)
631  {
632  $Count = $this->CountResourcesThatSatisfyCondition(
633  $User, $Field, $Operator, $Value);
634  return $Count > 0 ? TRUE : FALSE;
635  }
636  # else if resource is valid
637  elseif ($Resource instanceof Resource)
638  {
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() &&
643  $Field->SchemaId() != MetadataSchema::SCHEMAID_USER)
644  {
645  # return a result that in effect ignores the condition
646  return ($this->Logic == "AND") ? TRUE : FALSE;
647  }
648 
649  # normalize the incoming value for comparison
650  $Value = $this->NormalizeTargetValue($Field->Type(), $User, $Value);
651  $FieldValue = $this->GetNormalizedFieldValue($Field, $Resource, $User);
652 
653  # perform comparison, returning result
654  return $this->CompareNormalizedFieldValues($FieldValue, $Operator, $Value);
655  }
656  else
657  {
658  # error out because resource was illegal
659  throw new Exception("Invalid Resource passed in for privilege"
660  ." set comparison.");
661  }
662  }
663 
670  private function ValidOperatorsForFieldType($FieldType)
671  {
672  switch ($FieldType)
673  {
675  $ValidOps = ["=="];
676  break;
677 
681  $ValidOps = ["==", "!=", "<=", "<", ">=", ">"];
682  break;
683 
686  $ValidOps = ["==", "!="];
687  break;
688 
689  default:
690  $ValidOps = [];
691  break;
692  }
693 
694  return $ValidOps;
695  }
696 
705  private function NormalizeTargetValue($FieldType, $User, $Value)
706  {
707  switch ($FieldType)
708  {
711  # "Now" is encoded as NULL for timestamp and date comparisons
712  if ($Value === NULL)
713  {
714  $Value = time();
715  }
716  # otherwise, parse the value to get a numeric timestamp
717  else
718  {
719  $Value = strtotime($Value);
720  }
721  break;
722 
724  # "Current user" is encoded as NULL for user comparisons
725  if ($Value === NULL)
726  {
727  $Value = $User->Id();
728  }
729  break;
730 
731  default:
732  # no normalization needed for other field types
733  break;
734  }
735 
736  return $Value;
737  }
738 
753  private function GetNormalizedFieldValue($Field, $Resource, $User)
754  {
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]))
759  {
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() &&
766  $Field->SchemaId() == MetadataSchema::SCHEMAID_USER)
767  {
768  $FieldValue = $User->Get($Field);
769  }
770  else
771  {
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);
778  }
779 
780  # normalize field value for comparison
781  switch ($Field->Type())
782  {
785  # get the UserIds or CNIds from this field
786  $FieldValue = array_keys($FieldValue);
787  break;
788 
791  # convert returned value to a numeric timestamp
792  $FieldValue = strtotime($FieldValue);
793  break;
794 
797  # no conversion needed
798  break;
799 
800  default:
801  throw new Exception("Unsupported metadata field type ("
802  .print_r($Field->Type(), TRUE)
803  .") for condition in privilege set with resource.");
804  break;
805  }
806 
807  # cache the normalized value for subsequent reuse
808  self::$ValueCache[$CacheKey] = $FieldValue;
809  }
810 
811  return self::$ValueCache[$CacheKey];
812  }
813 
822  private function CompareNormalizedFieldValues($FieldValue, $Operator, $Value)
823  {
824  # compare field value and supplied value using specified operator
825 
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, ["==", "!="]) )
829  {
830  throw new Exception(
831  "Multiple-value fields ony support == and != operators");
832  }
833 
834  switch ($Operator)
835  {
836  case "==":
837  if (is_array($FieldValue))
838  {
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);
843  }
844  else
845  {
846  $Result = ($FieldValue == $Value);
847  }
848  break;
849 
850  case "!=":
851  if (is_array($FieldValue))
852  {
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);
857  }
858  else
859  {
860  $Result = ($FieldValue != $Value);
861  }
862  break;
863 
864  case "<":
865  $Result = ($FieldValue < $Value);
866  break;
867 
868  case ">":
869  $Result = ($FieldValue > $Value);
870  break;
871 
872  case "<=":
873  $Result = ($FieldValue <= $Value);
874  break;
875 
876  case ">=":
877  $Result = ($FieldValue >= $Value);
878  break;
879 
880  default:
881  throw new Exception("Unsupported condition operator ("
882  .print_r($Operator, TRUE).") in privilege set.");
883  break;
884  }
885 
886  # report to caller whether condition was met
887  return $Result ? TRUE : FALSE;
888  }
889 
900  private function CountResourcesThatSatisfyCondition(
901  $User, $Field, $Operator, $Value)
902  {
903  # get the SchemaId for this field
904  $ScId = $Field->SchemaId();
905 
906  # pull out an RFactory for the field's schema
907  if (!isset($this->RFactories[$ScId]))
908  {
909  $this->RFactories[$ScId] = new ResourceFactory($ScId);
910  }
911 
912  switch ($Field->Type())
913  {
919  $ValuesToMatch = array(
920  $Field->Id() => $Value,
921  );
922 
923  $Matches = $this->RFactories[$ScId]->GetMatchingResources(
924  $ValuesToMatch, TRUE, FALSE, $Operator);
925 
926  $Count = count($Matches);
927  break;
928 
930  # find the number of resources associated with this option
931  $Count = $this->RFactories[$ScId]->AssociatedVisibleResourceCount(
932  $Value, $User, TRUE);
933 
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 == "!=")
938  {
939  $Count = $this->RFactories[$ScId]->GetVisibleResourcesCount(
940  $User) - $Count;
941  }
942 
943  break;
944 
945  default:
946  throw new Exception("Unsupported metadata field type ("
947  .print_r($Field->Type(), TRUE)
948  .") for condition in privilege set without resource.");
949  break;
950  }
951 
952  return $Count;
953  }
954 
963  private function IsInPrivilegeData($Item)
964  {
965  # step through privilege data
966  foreach ($this->Privileges as $Priv)
967  {
968  # report to caller if item is found
969  if (is_object($Item))
970  {
971  if (is_object($Priv) && ($Item == $Priv)) { return TRUE; }
972  }
973  elseif (is_array($Item))
974  {
975  if (is_array($Priv) && ($Item == $Priv)) { return TRUE; }
976  }
977  elseif ($Item == $Priv) { return TRUE; }
978  }
979 
980  # report to caller that item is not in privilege data
981  return FALSE;
982  }
983 }
RemoveCondition($Field, $Value=NULL, $Operator="==", $IncludeSubsets=FALSE)
Remove condition from privilege set.
AddSet(PrivilegeSet $Set)
Add subgroup of privileges/conditions to set.
static GetCanonicalFieldIdentifier($Field, $SchemaId=NULL)
Retrieve canonical identifier for field.
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...
Definition: CWUser.php:154
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".
static FieldExistsInAnySchema($Field)
Determine if a Field exists in any schema.
AddPrivilege($Privileges)
Add specified privilege to set.
PrivilegeFlagsChecked()
List which privilege flags (e.g.
GetPrivilegeList()
Get list of privileges.
Object representing a locally-defined type of metadata field.
Data($NewValue=NULL)
Get/set privilege set data, in the form of an opaque string.
Represents a "resource" in CWIS.
Definition: Resource.php:13
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.
Definition: CWUser.php:13
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.