1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176: 177: 178: 179: 180: 181: 182: 183: 184: 185: 186: 187: 188: 189: 190: 191: 192: 193: 194: 195: 196: 197: 198: 199: 200: 201: 202: 203: 204: 205: 206: 207: 208: 209: 210: 211: 212: 213: 214: 215: 216: 217: 218: 219: 220: 221: 222: 223: 224: 225: 226: 227: 228: 229: 230: 231: 232: 233: 234: 235: 236: 237: 238: 239: 240: 241: 242: 243: 244: 245: 246: 247: 248: 249: 250: 251: 252: 253: 254: 255: 256: 257: 258: 259: 260: 261: 262: 263: 264: 265: 266: 267: 268: 269: 270: 271: 272: 273: 274: 275: 276: 277: 278: 279: 280: 281: 282: 283: 284: 285: 286: 287: 288: 289: 290: 291: 292: 293: 294: 295: 296: 297: 298: 299: 300: 301: 302: 303: 304: 305: 306: 307: 308: 309: 310: 311: 312: 313: 314: 315: 316: 317: 318: 319: 320: 321: 322: 323: 324: 325: 326: 327: 328: 329: 330: 331: 332: 333: 334: 335: 336: 337: 338: 339: 340: 341: 342: 343: 344: 345: 346: 347: 348: 349: 350: 351: 352: 353: 354: 355: 356: 357: 358: 359: 360: 361: 362: 363: 364: 365: 366: 367: 368: 369: 370: 371: 372: 373: 374: 375: 376: 377: 378: 379: 380: 381: 382: 383: 384: 385: 386: 387: 388: 389: 390: 391: 392: 393: 394: 395: 396: 397: 398: 399: 400: 401: 402: 403: 404: 405: 406: 407: 408: 409: 410: 411: 412: 413: 414: 415: 416: 417: 418: 419: 420: 421: 422: 423: 424: 425: 426: 427: 428: 429: 430: 431: 432: 433: 434: 435: 436: 437: 438: 439: 440: 441: 442: 443: 444: 445: 446: 447: 448: 449: 450: 451: 452: 453: 454: 455: 456: 457: 458: 459: 460: 461: 462: 463: 464: 465: 466: 467: 468: 469: 470: 471: 472: 473: 474: 475: 476: 477: 478: 479: 480: 481: 482: 483: 484: 485: 486: 487: 488: 489: 490: 491: 492: 493: 494: 495: 496: 497: 498: 499: 500: 501: 502: 503: 504: 505: 506: 507: 508: 509: 510: 511: 512: 513: 514: 515: 516: 517: 518: 519: 520: 521: 522: 523: 524: 525: 526: 527: 528: 529: 530: 531: 532: 533: 534: 535: 536: 537: 538: 539: 540: 541: 542: 543: 544: 545: 546: 547: 548: 549: 550: 551: 552: 553: 554: 555: 556: 557: 558: 559: 560: 561: 562: 563: 564: 565: 566: 567: 568: 569: 570: 571: 572: 573: 574: 575: 576: 577: 578: 579: 580: 581: 582: 583: 584: 585: 586: 587: 588: 589: 590: 591: 592: 593: 594: 595: 596: 597: 598: 599: 600: 601: 602: 603: 604: 605: 606: 607: 608: 609: 610: 611: 612: 613: 614: 615: 616: 617: 618: 619: 620: 621: 622: 623: 624: 625: 626: 627: 628: 629: 630: 631: 632: 633: 634: 635: 636: 637: 638: 639: 640: 641: 642: 643: 644: 645: 646: 647: 648: 649: 650: 651: 652: 653: 654: 655: 656: 657: 658: 659: 660: 661: 662: 663: 664: 665: 666: 667: 668: 669: 670: 671: 672: 673: 674: 675: 676: 677: 678: 679: 680: 681: 682: 683: 684: 685: 686: 687: 688: 689: 690: 691: 692: 693: 694: 695: 696: 697: 698: 699: 700: 701: 702: 703: 704: 705: 706: 707: 708: 709: 710: 711: 712: 713: 714: 715: 716: 717: 718: 719: 720: 721: 722: 723: 724: 725: 726: 727: 728: 729: 730: 731: 732: 733: 734: 735: 736: 737: 738: 739: 740: 741: 742: 743: 744: 745: 746: 747: 748: 749: 750: 751: 752: 753: 754: 755: 756: 757: 758: 759: 760: 761: 762: 763: 764: 765: 766: 767: 768: 769: 770: 771: 772: 773: 774: 775: 776: 777: 778: 779: 780: 781: 782: 783: 784: 785: 786: 787: 788: 789: 790: 791: 792: 793: 794: 795:
<?php
namespace Nethgui\Authorization;
class PolicyRule
{
private $id;
private $effect;
private $description;
private $matcher;
private $specificity;
private $isFinal = FALSE;
private function __construct()
{
$this->id = 0;
$this->description = '';
$this->effect = 'DENY';
$this->matcher = array();
}
public static function createFromObject($o)
{
$instance = new static();
foreach ($o as $property => $value) {
$property = strtolower($property);
if ($property === 'id') {
$instance->id = intval($value);
} elseif ($property === 'description') {
$instance->description = $value;
} elseif ($property === 'effect') {
$instance->effect = strtoupper($value);
} elseif ($property === 'final') {
$instance->isFinal = (boolean) $value;
} else {
$instance->matcher[$property] = $instance->parseMatcher($value);
}
}
return $instance;
}
public function getDescription()
{
return sprintf('%s (score = %.2f)', $this->description, $this->getSpecificity());
}
public function getIdentifier()
{
return $this->id;
}
public function isAllow()
{
return $this->effect === 'ALLOW';
}
public function isApplicableTo($request)
{
foreach ($request as $matcherName => $obj) {
if ( ! isset($this->matcher[$matcherName])) {
continue;
}
$result = $this->matcher[$matcherName]->evaluate($this->asAttributeProvider($obj));
if ( ! $result) {
return FALSE;
}
}
return TRUE;
}
private function asAttributeProvider($o)
{
$retval = NULL;
if ($o instanceof AuthorizationAttributesProviderInterface) {
$retval = $o;
} else {
$retval = new StringAttributesProvider($o);
}
return $retval;
}
public function isFinal()
{
return $this->isFinal === TRUE;
}
private function getSpecificity()
{
if ( ! isset($this->specificity)) {
$this->specificity = 0.0;
foreach ($this->matcher as $matcher) {
$this->specificity += $matcher->getSpecificity();
}
}
return $this->specificity;
}
public function compare(PolicyRule $other)
{
if ($this->getSpecificity() === $other->getSpecificity()) {
return 0;
} elseif ($this->getSpecificity() > $other->getSpecificity()) {
return 1;
} else {
return -1;
}
}
private function parseMatcher($value)
{
if (is_array($value)) {
if (empty($value)) {
return $this->parseMatcher('!*');
}
$a = $this->parseMatcher(\Nethgui\array_head($value));
if (count($value) == 1) {
return $a;
}
$b = $this->parseMatcher(\Nethgui\array_rest($value));
return new AnyOfExpression($a, $b);
}
$parser = new PolicyExpressionParser($value);
$parseTree = $parser->parse();
return $parseTree;
}
}
class PolicyExpressionParser
{
private $tokens;
private $symbol;
private $value;
private $offset;
const T_EOF = 0;
const T_LITERAL = 1;
const T_WHITESPACE = 2;
const T_BOOLEAN_AND = 3;
const T_BOOLEAN_OR = 4;
const T_NEGATION = 5;
const T_LEFT_PARENS = 6;
const T_RIGHT_PARENS = 7;
const T_ATTRIBUTE = 8;
const T_EQUAL = 9;
const T_IN = 10;
const T_IS = 11;
const T_HAS = 12;
const T_TRUE = 13;
const T_FALSE = 14;
public function __construct($text)
{
if ( ! is_string($text)) {
throw new \InvalidArgumentException(sprintf('%s: data must be a string', __CLASS__), 1327658033);
}
$this->input = $text;
$this->tokens = preg_split("/(\b(?:TRUE|FALSE|AND|OR|NOT|HAS|IS)\b|\.[A-Za-z][A-Za-z0-9_-]*|\(|\))/ ", $text, NULL, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY | PREG_SPLIT_OFFSET_CAPTURE);
}
public function parse()
{
return $this->expression();
}
private function eat($token)
{
if ($token !== $this->symbol) {
$this->throwError();
}
return $this->read();
}
private function read()
{
list($token, $offset) = array_shift($this->tokens);
$this->offset = $offset;
switch ($token) {
case FALSE;
$this->symbol = self::T_EOF;
break;
case 'AND':
$this->symbol = self::T_BOOLEAN_AND;
break;
case 'OR':
$this->symbol = self::T_BOOLEAN_OR;
break;
case 'NOT':
$this->symbol = self::T_NEGATION;
break;
case "(":
$this->symbol = self::T_LEFT_PARENS;
break;
case ")":
$this->symbol = self::T_RIGHT_PARENS;
break;
case "IS":
$this->symbol = self::T_IS;
break;
case "HAS":
$this->symbol = self::T_HAS;
break;
case "TRUE":
$this->symbol = self::T_TRUE;
break;
case "FALSE":
$this->symbol = self::T_FALSE;
break;
default:
$token = trim($token);
if (strlen($token) > 0) {
if ($token{0} === '.') {
$this->symbol = self::T_ATTRIBUTE;
} else {
$this->symbol = self::T_LITERAL;
}
} else {
$this->symbol = self::T_WHITESPACE;
}
}
$this->value = $token;
if ($this->symbol === self::T_WHITESPACE) {
$this->read();
}
return $this;
}
private function tokenName($s)
{
switch ($s) {
case self::T_LITERAL: return 'T_LITERAL';
case self::T_WHITESPACE: return 'T_WHITESPACE';
case self::T_BOOLEAN_AND: return 'T_BOOLEAN_AND';
case self::T_BOOLEAN_OR: return 'T_BOOLEAN_OR';
case self::T_NEGATION: return 'T_NEGATION';
case self::T_LEFT_PARENS: return 'T_LEFT_PARENS';
case self::T_RIGHT_PARENS: return 'T_RIGHT_PARENS';
case self::T_ATTRIBUTE: return 'T_ATTRIBUTE';
case self::T_IS: return 'T_IS';
case self::T_HAS: return 'T_HAS';
case self::T_FALSE: return 'T_FALSE';
case self::T_TRUE: return 'T_TRUE';
default: return 'UNKNOWN ' . var_export($s, TRUE);
}
}
private function throwError()
{
$tok = $this->tokenName($this->symbol);
$expr = '`' . substr($this->input, 0, $this->offset) . '` {!} `' . substr($this->input, $this->offset) . '`';
throw new \UnexpectedValueException(sprintf('%s: unexpected token %s; %s', __CLASS__, $tok, $expr), 1327593544);
}
private function expression()
{
$this->read();
return $this->orExp();
}
private function orExp()
{
return $this->orExpTail($this->orOp());
}
private function orExpTail(AbstractExpression $p)
{
if ($this->symbol === self::T_BOOLEAN_OR) {
$this->eat(self::T_BOOLEAN_OR);
return new AnyOfExpression($p, $this->orExp());
}
return $p;
}
private function orOp()
{
return $this->andExp();
}
private function andExp()
{
return $this->andExpTail($this->andOp());
}
private function andExpTail(AbstractExpression $p)
{
if ($this->symbol == self::T_BOOLEAN_AND) {
$this->eat(self::T_BOOLEAN_AND);
return new AllOfExpression($p, $this->andExp());
}
return $p;
}
private function andOp()
{
if ($this->symbol === self::T_NEGATION) {
$this->eat(self::T_NEGATION);
return new NegationExpression($this->matchExp());
} else {
return $this->matchExp();
}
$this->throwError();
}
private function matchExp()
{
return $this->matchExpTail($this->operand());
}
private function operand()
{
switch ($this->symbol) {
case self::T_ATTRIBUTE:
$operand = new AttributeExpression(substr($this->value, 1));
$this->eat(self::T_ATTRIBUTE);
return $operand;
case self::T_LITERAL:
$operand = new StringMatchExpression($this->value);
$this->eat(self::T_LITERAL);
return $operand;
case self::T_LEFT_PARENS:
$this->eat(self::T_LEFT_PARENS);
$operand = $this->orExp();
$this->eat(self::T_RIGHT_PARENS);
return $operand;
case self::T_TRUE:
$this->eat(self::T_TRUE);
return new Constant(TRUE);
case self::T_FALSE:
$this->eat(self::T_FALSE);
return new Constant(FALSE);
}
$this->throwError();
}
private function matchExpTail(AbstractExpression $p)
{
if ($this->symbol === self::T_IS) {
$this->eat(self::T_IS);
return new IsExpression($p, $this->operand());
} elseif ($this->symbol === self::T_HAS) {
$this->eat(self::T_HAS);
return new HasExpression($p, $this->operand());
}
return $p;
}
}
abstract class AbstractExpression
{
abstract public function evaluate(AuthorizationAttributesProviderInterface $value);
public function getSpecificity()
{
return 0.0;
}
}
abstract class BinaryExpression extends AbstractExpression
{
protected $a;
protected $b;
public function __construct(AbstractExpression $a, AbstractExpression $b)
{
$this->a = $a;
$this->b = $b;
}
}
class IsExpression extends BinaryExpression
{
public function evaluate(AuthorizationAttributesProviderInterface $value)
{
return $this->b->evaluate(new StringAttributesProvider(strval($this->a->evaluate($value))));
}
public function getSpecificity()
{
return $this->b->getSpecificity();
}
}
class HasExpression extends BinaryExpression
{
public function evaluate(AuthorizationAttributesProviderInterface $value)
{
$arr = $this->a->evaluate($value);
if ( ! is_array($arr)) {
return FALSE;
}
foreach ($arr as $itemValue) {
if ($this->b->evaluate(new StringAttributesProvider($itemValue)) === TRUE) {
return TRUE;
}
}
return FALSE;
}
public function getSpecificity()
{
return $this->b->getSpecificity();
}
}
class AnyOfExpression extends BinaryExpression
{
public function evaluate(AuthorizationAttributesProviderInterface $value)
{
$evA = $this->a->evaluate($value);
$evB = $this->b->evaluate($value);
return $evA || $evB;
}
public function getSpecificity()
{
return min($this->a->getSpecificity(), $this->b->getSpecificity());
}
}
class AllOfExpression extends BinaryExpression
{
public function evaluate(AuthorizationAttributesProviderInterface $value)
{
$evA = (bool) $this->a->evaluate($value);
$evB = (bool) $this->b->evaluate($value);
return $evA && $evB;
}
public function getSpecificity()
{
return max($this->a->getSpecificity(), $this->b->getSpecificity());
}
}
class AttributeExpression extends AbstractExpression
{
private $attributeName;
public function __construct($attributeName)
{
$this->attributeName = $attributeName;
}
public function evaluate(AuthorizationAttributesProviderInterface $value)
{
return $value->getAuthorizationAttribute($this->attributeName);
}
public function getSpecificity()
{
return 0.5;
}
}
class NegationExpression extends AbstractExpression
{
private $inner;
public function __construct(AbstractExpression $inner)
{
$this->inner = $inner;
}
public function evaluate(AuthorizationAttributesProviderInterface $value)
{
return ! $this->inner->evaluate($value);
}
public function getSpecificity()
{
return 1.0 - $this->inner->getSpecificity();
}
}
class StringMatchExpression extends AbstractExpression
{
private $pattern;
private $regexPattern;
private $stars;
public function __construct($pattern)
{
$this->stars = 0;
$this->pattern = $pattern;
$regexPattern = preg_quote($pattern, '#');
$regexPattern = str_replace('\*', '.*', $regexPattern, $this->stars);
$this->regexPattern = '#^' . $regexPattern . '$#';
}
public function evaluate(AuthorizationAttributesProviderInterface $value)
{
if ($this->stars === 0) {
return $value->asAuthorizationString() === $this->pattern;
}
return preg_match($this->regexPattern, $value->asAuthorizationString()) > 0;
}
public function getSpecificity()
{
if ($this->stars === 0) {
return 1.0;
} elseif ($this->pattern === '*') {
return 0.0;
}
return 1.0 / (1.0 + (float) $this->stars);
}
}
class Constant extends AbstractExpression
{
private $value;
public function __construct($constValue)
{
$this->value = $constValue;
}
public function evaluate(AuthorizationAttributesProviderInterface $value)
{
return $this->value;
}
}