#
source:
TI12-security/trunk/NDG_XACML/ndg/xacml/cond/__init__.py
@
6624

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI12-security/trunk/NDG_XACML/ndg/xacml/cond/__init__.py@6624
Revision 6624, 44.0 KB checked in by pjkersha, 12 years ago (diff) |
---|

Line | |
---|---|

1 | """XACML cond module contains condition function classes |

2 | |

3 | NERC DataGrid Project |

4 | |

5 | This code is adapted from the Sun Java XACML implementation ... |

6 | |

7 | Copyright 2004 Sun Microsystems, Inc. All Rights Reserved. |

8 | |

9 | Redistribution and use in source and binary forms, with or without |

10 | modification, are permitted provided that the following conditions are met: |

11 | |

12 | 1. Redistribution of source code must retain the above copyright notice, |

13 | this list of conditions and the following disclaimer. |

14 | |

15 | 2. Redistribution in binary form must reproduce the above copyright |

16 | notice, this list of conditions and the following disclaimer in the |

17 | documentation and/or other materials provided with the distribution. |

18 | |

19 | Neither the name of Sun Microsystems, Inc. or the names of contributors may |

20 | be used to endorse or promote products derived from this software without |

21 | specific prior written permission. |

22 | |

23 | This software is provided "AS IS," without a warranty of any kind. ALL |

24 | EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING |

25 | ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE |

26 | OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") |

27 | AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE |

28 | AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS |

29 | DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST |

30 | REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, |

31 | INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY |

32 | OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, |

33 | EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. |

34 | |

35 | You acknowledge that this software is not designed or intended for use in |

36 | the design, construction, operation or maintenance of any nuclear facility. |

37 | """ |

38 | __author__ = "P J Kershaw" |

39 | __date__ = "02/04/09" |

40 | __copyright__ = "(C) 2009 Science and Technology Facilities Council" |

41 | __contact__ = "Philip.Kershaw@stfc.ac.uk" |

42 | __license__ = "BSD - see LICENSE file in top-level directory" |

43 | __contact__ = "Philip.Kershaw@stfc.ac.uk" |

44 | __revision__ = "$Id: $" |

45 | import logging |

46 | log = logging.getLogger(__name__) |

47 | |

48 | from ndg.security.common.utils.etree import QName |

49 | from ndg.xacml.exceptions import \ |

50 | UnknownIdentifierException, ParsingException |

51 | from ndg.xacml.cond.eval import Evaluatable, \ |

52 | EvaluationResult |

53 | from ndg.xacml.attr import AnyURIAttribute, \ |

54 | Base64BinaryAttribute, BooleanAttribute, DateAttribute, DateTimeAttribute,\ |

55 | DayTimeDurationAttribute, DoubleAttribute, HexBinaryAttribute, \ |

56 | IntegerAttribute, RFC822NameAttribute, StringAttribute, TimeAttribute, \ |

57 | X500NameAttribute, YearMonthDurationAttribute, AttributeFactory, \ |

58 | AttributeDesignator |

59 | |

60 | |

61 | class Apply(Evaluatable): |

62 | '''Represents the XACML ApplyType and ConditionType XML types.''' |

63 | |

64 | def __init__(self, function, evals, bagFunction=None, isCondition=False): |

65 | '''Constructs an Apply object. Throws an |

66 | IllegalArgumentException if the given parameter list |

67 | isn't valid for the given function. |

68 | |

69 | @param function the Function to use in evaluating the elements in the |

70 | apply |

71 | @param evals the contents of the apply which will be the parameters |

72 | to the function, each of which is an Evaluatable |

73 | @param bagFunction the higher-order function to use |

74 | @param isCondition Rrue if this Apply is a Condition, False otherwise |

75 | ''' |

76 | |

77 | # check that the given inputs work for the function |

78 | inputs = evals |

79 | if bagFunction is not None: |

80 | inputs = [bagFunction] |

81 | inputs += evals |

82 | |

83 | function.checkInputs(inputs) |

84 | |

85 | # if everything checks out, then store the inputs |

86 | self._function = function |

87 | self._evals = tuple(evals) |

88 | self.bagFunction = bagFunction |

89 | self.isCondition = isCondition |

90 | |

91 | @classmethod |

92 | def getConditionInstance(cls, root): |

93 | '''Returns an instance of an Apply based on the given DOM |

94 | root node. This will actually return a special kind of |

95 | Apply, namely an XML ConditionType, which is the root |

96 | of the condition logic in a RuleType. A ConditionType is the same |

97 | as an ApplyType except that it must use a FunctionId that returns |

98 | a boolean value. |

99 | |

100 | @param root the DOM root of a ConditionType XML type |

101 | ''' |

102 | from ndg.xacml.cond.factory import \ |

103 | FunctionFactory |

104 | cls.__getInstance(root, FunctionFactory.getConditionInstance(), True) |

105 | |

106 | @classmethod |

107 | def getInstance(cls, root): |

108 | '''Returns an instance of Apply based on the given root. |

109 | |

110 | @param root: the ElementTree.Element root of a ConditionType XML type |

111 | @raise ParsingException: if this is not a valid ApplyType |

112 | ''' |

113 | from ndg.xacml.cond.factory import \ |

114 | FunctionFactory |

115 | cls.__getInstance(root, FunctionFactory.getGeneralInstance(), True) |

116 | |

117 | |

118 | @classmethod |

119 | def __getInstance(cls, root, factory, isCondition): |

120 | '''This is a helper method that is called by the two getInstance |

121 | methods. It takes a factory so we know that we're getting the right |

122 | kind of function.''' |

123 | |

124 | function = cls.__getFunction(root, factory) |

125 | bagFunction = None |

126 | evals = [] |

127 | |

128 | attrFactory = AttributeFactory.getInstance() |

129 | |

130 | for elem in root: |

131 | name = QName.getLocalPart(elem.tag) |

132 | |

133 | if name == "Apply": |

134 | evals.append(Apply.getInstance(elem)) |

135 | |

136 | elif name == "AttributeValue": |

137 | try: |

138 | evals.append(attrFactory.createValue(elem)) |

139 | |

140 | except UnknownIdentifierException, e: |

141 | raise ParsingException("Unknown DataType: %s" % e) |

142 | |

143 | elif name == "SubjectAttributeDesignator": |

144 | evals.append(AttributeDesignator.getInstance(elem, |

145 | AttributeDesignator.SUBJECT_TARGET)) |

146 | |

147 | elif name =="ResourceAttributeDesignator": |

148 | evals.append(AttributeDesignator.getInstance(elem, |

149 | AttributeDesignator.RESOURCE_TARGET)) |

150 | |

151 | elif name == "ActionAttributeDesignator": |

152 | evals.append(AttributeDesignator.getInstance(elem, |

153 | AttributeDesignator.ACTION_TARGET)) |

154 | |

155 | elif name == "EnvironmentAttributeDesignator": |

156 | evals.append(AttributeDesignator.getInstance(elem, |

157 | AttributeDesignator.ENVIRONMENT_TARGET)) |

158 | |

159 | elif name == "AttributeSelector": |

160 | evals.append(AttributeSelector.getInstance(elem)) |

161 | |

162 | elif name == "Function": |

163 | # while the schema doesn't enforce this, it's illegal to |

164 | # have more than one FunctionType in a given ApplyType |

165 | if bagFunction != None: |

166 | raise ParsingException("Too many FunctionTypes") |

167 | |

168 | from ndg.xacml.cond.factory import \ |

169 | FunctionFactory |

170 | bagFunction = cls.__getFunction(elem, |

171 | FunctionFactory.getGeneralInstance()) |

172 | |

173 | return Apply(function, evals, bagFunction, isCondition) |

174 | |

175 | |

176 | @classmethod |

177 | def __getFunction(cls, root, factory): |

178 | '''Helper method that tries to get a function instance''' |

179 | |

180 | functionName = root.attrib["FunctionId"] |

181 | try: |

182 | # try to get an instance of the given function |

183 | return factory.createFunction(functionName) |

184 | |

185 | except UnknownIdentifierException, e: |

186 | raise ParsingException("Unknown FunctionId in Apply: %s" % e) |

187 | |

188 | except FunctionTypeException, e: |

189 | # try creating as an abstract function, using a general factory |

190 | try: |

191 | from ndg.xacml.cond.factory import \ |

192 | FunctionFactory |

193 | functionFactory = FunctionFactory.getGeneralInstance() |

194 | return functionFactory.createAbstractFunction(functionName, |

195 | root) |

196 | except Exception, e: |

197 | # any exception at this point is a failure |

198 | raise ParsingException("failed to create abstract function %s " |

199 | ": %s" % (functionName, e)) |

200 | |

201 | def getFunction(self): |

202 | '''Returns the Function used by this Apply. |

203 | |

204 | @return the Function''' |

205 | return self._function |

206 | |

207 | def getChildren(self): |

208 | '''Returns the List of children for this Apply. |

209 | The List contains Evaluatables. The list is |

210 | unmodifiable, and may be empty. |

211 | |

212 | @return a List of Evaluatables''' |

213 | return self._evals |

214 | |

215 | def getHigherOrderFunction(self): |

216 | '''Returns the higher order bag function used by this Apply |

217 | if it exists, or null if no higher order function is used. |

218 | |

219 | @return the higher order Function or null''' |

220 | return self.bagFunction |

221 | |

222 | def isCondition(self): |

223 | '''Returns whether or not this ApplyType is actually a ConditionType. |

224 | |

225 | @return whether or not this represents a ConditionType''' |

226 | return isCondition |

227 | |

228 | def evaluate(self, context): |

229 | '''Evaluates the apply object using the given function. This will in |

230 | turn call evaluate on all the given parameters, some of which may be |

231 | other Apply objects. |

232 | |

233 | @param context the representation of the request |

234 | |

235 | @return the result of trying to evaluate this apply object''' |

236 | parameters = self.evals |

237 | |

238 | # see if there is a higher-order function in here |

239 | if bagFunction != None: |

240 | # this is a special case, so we setup the parameters, starting |

241 | # with the function |

242 | parameters = [bagFunction] |

243 | |

244 | # now we evaluate all the parameters, returning INDETERMINATE |

245 | # if that's what any of them return, and otherwise tracking |

246 | # all the AttributeValues that get returned |

247 | for eval in self.evals: |

248 | result = eval.evaluate(context) |

249 | |

250 | # in a higher-order case, if anything is INDETERMINATE, then |

251 | # we stop right away |

252 | if result.indeterminate(): |

253 | return result |

254 | |

255 | parameters.add(result.getAttributeValue()) |

256 | |

257 | # now we can call the base function |

258 | return function.evaluate(parameters, context) |

259 | |

260 | def getType(self): |

261 | '''Returns the type of attribute that this object will return on a call |

262 | to evaluate. In practice, this will always be the same as |

263 | the result of calling getReturnType on the function used |

264 | by this object. |

265 | |

266 | @return the type returned by evaluate''' |

267 | return self.function.getReturnType() |

268 | |

269 | def evaluatesToBag(self): |

270 | '''Returns whether or not the Function will return a bag |

271 | of values on evaluation. |

272 | |

273 | @return true if evaluation will return a bag of values, false otherwise |

274 | ''' |

275 | return self.function.returnsBag() |

276 | |

277 | def encode(self, output, indenter): |

278 | '''Encodes this Apply into its XML representation and |

279 | writes this encoding to the given OutputStream with |

280 | indentation. |

281 | |

282 | @param output a stream into which the XML-encoded data is written |

283 | @param indenter an object that creates indentation strings''' |

284 | raise NotImplementedError() |

285 | |

286 | class Function(object): |

287 | '''Interface that all functions in the system must implement.''' |

288 | |

289 | def evaluate(self, inputs, context): |

290 | '''Evaluates the Function using the given inputs. |

291 | The List contains Evaluatables which are all |

292 | of the correct type if the Function has been created as |

293 | part of an Apply or TargetMatch, but which |

294 | may otherwise be invalid. Each parameter should be evaluated by the |

295 | Function, unless this is a higher-order function (in |

296 | which case the Apply has already evaluated the inputs |

297 | to check for any INDETERMINATE conditions), or the Function |

298 | doesn't need to evaluate all inputs to determine a result (as in the |

299 | case of the or function). The order of the List is |

300 | significant, so a Function should have a very good reason |

301 | if it wants to evaluate the inputs in a different order. |

302 | <p> |

303 | Note that if this is a higher-order function, like any-of, then |

304 | the first argument in the List will actually be a Function |

305 | object representing the function to apply to some bag. In this case, |

306 | the second and any subsequent entries in the list are |

307 | AttributeValue objects (no INDETERMINATE values are |

308 | allowed, so the function is not given the option of dealing with |

309 | attributes that cannot be resolved). A function needs to know if it's |

310 | a higher-order function, and therefore whether or not to look for |

311 | this case. Also, a higher-order function is responsible for checking |

312 | that the inputs that it will pass to the Function |

313 | provided as the first parameter are valid, ie. it must do a |

314 | checkInputs on its sub-function when |

315 | checkInputs is called on the higher-order function. |

316 | |

317 | @param inputs the List of inputs for the function |

318 | @param context the representation of the request |

319 | |

320 | @return a result containing the AttributeValue computed |

321 | when evaluating the function, or Status |

322 | specifying some error condition''' |

323 | raise NotImplementedError() |

324 | |

325 | |

326 | def getIdentifier(self): |

327 | '''Returns the identifier of this function as known by the factories. |

328 | In the case of the standard XACML functions, this will be one of the |

329 | URIs defined in the standard namespace. This function must always |

330 | return the complete namespace and identifier of this function. |

331 | |

332 | @return the function's identifier''' |

333 | raise NotImplementedError() |

334 | |

335 | def getReturnType(self): |

336 | '''Provides the type of AttributeValue that this function |

337 | returns from evaluate in a successful evaluation. |

338 | |

339 | @return the type returned by this function |

340 | ''' |

341 | raise NotImplementedError() |

342 | |

343 | def returnsBag(self): |

344 | '''Tells whether this function will return a bag of values or just a |

345 | single value. |

346 | |

347 | @return true if evaluation will return a bag, false otherwise''' |

348 | raise NotImplementedError() |

349 | |

350 | def checkInputs(self, inputs): |

351 | '''Checks that the given inputs are of the right types, in the right |

352 | order, and are the right number for this function to evaluate. If |

353 | the function cannot accept the inputs for evaluation, an |

354 | IllegalArgumentException is thrown. |

355 | |

356 | @param inputs a list of Evaluatables, with the first argument being a |

357 | Function if this is a higher-order function |

358 | |

359 | @throws TypeError if the inputs do match what the function accepts for |

360 | evaluation |

361 | ''' |

362 | raise NotImplementedError() |

363 | |

364 | def checkInputsNoBag(self, inputs): |

365 | '''Checks that the given inputs are of the right types, in the right |

366 | order, and are the right number for this function to evaluate. If |

367 | the function cannot accept the inputs for evaluation, an |

368 | IllegalArgumentException is thrown. Unlike the other |

369 | checkInput method in this interface, this assumes that |

370 | the parameters will never provide bags of values. This is useful if |

371 | you're considering a target function which has a designator or |

372 | selector in its input list, but which passes the values from the |

373 | derived bags one at a time to the function, so the function doesn't |

374 | have to deal with the bags that the selector or designator |

375 | generates. |

376 | |

377 | @param inputs a list of Evaluatables, with the first argument being a |

378 | Function if this is a higher-order function |

379 | |

380 | @throws TypeError if the inputs do match what the function accepts for |

381 | evaluation''' |

382 | raise NotImplementedError() |

383 | |

384 | |

385 | class FunctionBase(Function): |

386 | FUNCTION_NS = "urn:oasis:names:tc:xacml:1.0:function:" |

387 | supportedIdentifiers = () |

388 | |

389 | def __init__(self, |

390 | functionName, |

391 | functionId=None, |

392 | paramType=None, |

393 | paramIsBag=False, |

394 | numParams=-1, |

395 | minParams=0, |

396 | returnType='', |

397 | returnsBag=False): |

398 | ''' |

399 | @param functionName: the name of this function as used by the factory |

400 | and any XACML policies |

401 | @param functionId: an optional identifier that can be used by your |

402 | code for convenience |

403 | @param paramType: the type of each parameter, in order, required by |

404 | this function, as used by the factory and any XACML |

405 | documents |

406 | @param paramIsBag: whether or not each parameter is actually a bag |

407 | of values |

408 | @param numParams: the number of parameters required by this function, |

409 | or -1 if any number are allowed |

410 | @param minParams: the minimum number of parameters required if |

411 | numParams is -1 |

412 | @param returnType: the type returned by this function, as used by |

413 | the factory and any XACML documents |

414 | @param returnsBag: whether or not this function returns a bag of values |

415 | ''' |

416 | self.functionName = functionName |

417 | self.functionId = functionId |

418 | self.returnType = returnType |

419 | self.returnsBag = returnsBag |

420 | |

421 | self.paramType = paramType |

422 | |

423 | if isinstance(self.paramType, (list, tuple)): |

424 | if not self.paramType: |

425 | raise TypeError('"paramType" is set to an empty list or tuple') |

426 | self.singleType = False |

427 | |

428 | # Keep this test within the paramType is-a-list if-block otherwise |

429 | # it may fail checking the length of a bool |

430 | if len(paramIsBag) != len(self.paramType): |

431 | raise TypeError('"paramIsBag" and "paramType" inputs must ' |

432 | 'have the same length') |

433 | else: |

434 | self.singleType = True |

435 | |

436 | # These only apply if the input parameters are all of a single type |

437 | self.numParams = numParams |

438 | self.minParams = minParams |

439 | |

440 | self.paramIsBag = paramIsBag |

441 | |

442 | |

443 | def _setFunctionName(self, functionName): |

444 | if functionName not in self.__class__.supportedIdentifiers: |

445 | functionList = ', '.join(self.__class__.supportedIdentifiers) |

446 | raise TypeError("Function name [%s] is not on of the recognised " |

447 | "types: %s" % (functionName, functionList)) |

448 | self._functionName = functionName |

449 | |

450 | def _getFunctionName(self): |

451 | return getattr(self, '_functionName', None) |

452 | |

453 | functionName = property(fset=_setFunctionName, |

454 | fget=_getFunctionName) |

455 | |

456 | def checkInputs(self, inputs): |

457 | '''Checks that the given inputs are of the right types, in the right |

458 | order, and are the right number for this function to evaluate.''' |

459 | raise NotImplementedError() |

460 | |

461 | def checkInputsNoBag(self, inputs): |

462 | '''Default handling of input checking. This does some simple checking |

463 | based on the type of constructor used. If you need anything more |

464 | complex, or if you used the simple constructor, then you must |

465 | override this method. |

466 | |

467 | @param inputs: a list of Evaluatable instances |

468 | |

469 | @raise TypeError: if the inputs won't work |

470 | ''' |

471 | numInputs = len(inputs) |

472 | |

473 | if self.singleType: |

474 | # first check to see if we need bags |

475 | if sum(self.paramIsBag): |

476 | raise TypeError('"%s" needs bags on input' % self.functionName) |

477 | |

478 | # now check on the length |

479 | if self.numParams != -1: |

480 | if numInputs != self.numParams: |

481 | raise TypeError('wrong number of args to "%s"' % |

482 | self.functionName) |

483 | else: |

484 | if numInputs < self.minParams: |

485 | raise TypeError("not enough args to " % self.functionName) |

486 | |

487 | |

488 | # finally check param list |

489 | for eval in inputs: |

490 | if eval.getType().toString() != self.paramType: |

491 | raise TypeError("Illegal parameter: input type is %s but " |

492 | "%s type is %s" % |

493 | (eval.getType().toString(), |

494 | self.__class__.__name__, |

495 | self.paramType)) |

496 | |

497 | else: |

498 | # first, check the length of the inputs |

499 | if len(self.paramType) != numInputs: |

500 | raise TypeError('Wrong number of args to "%s"' % |

501 | self.functionName) |

502 | |

503 | # Ensure everything is of the same, correct type |

504 | it = zip(inputs, self.paramType, self.paramIsBag) |

505 | for eval, paramType, paramIsBag in it: |

506 | if eval.type != paramType or paramIsBag: |

507 | raise TypeError("Illegal parameter: input type is %s but " |

508 | "%s type is %s" % |

509 | (eval.type, |

510 | self.__class__.__name__, |

511 | paramType)) |

512 | |

513 | |

514 | def evaluate(self, inputs, context): |

515 | '''Evaluates the Function using the given inputs.''' |

516 | raise NotImplementedError() |

517 | |

518 | def evalArgs(self, params, context, args): |

519 | '''Evaluates each of the parameters, in order, filling in the argument |

520 | array with the resulting values. If any error occurs, this method |

521 | returns the error, otherwise null is returned, signalling that |

522 | evaluation was successful for all inputs, and the resulting argument |

523 | list can be used. |

524 | |

525 | @param params a list of Evaluatable objects representing the parameters |

526 | to evaluate |

527 | @param context the representation of the request |

528 | @param args an array as long as the params list that will, on return, |

529 | contain the AttributeValues generated from evaluating all parameters |

530 | |

531 | @return None if no errors were encountered, otherwise |

532 | an EvaluationResult representing the error |

533 | ''' |

534 | index = 0 |

535 | |

536 | for eval in params: |

537 | # get and evaluate the next parameter |

538 | result = eval.evaluate(context) |

539 | |

540 | # If there was an error, pass it back... |

541 | if result.indeterminate(): |

542 | return result |

543 | |

544 | # ...otherwise save it and keep going |

545 | args[index] = result.getAttributeValue() |

546 | index += 1 |

547 | |

548 | return None |

549 | |

550 | # TODO: Condition classes - minimal implementation until opportunity to fully |

551 | # implement |

552 | class BagFunction(FunctionBase): |

553 | def __init__(self, *arg, **kw): |

554 | raise NotImplementedError() |

555 | |

556 | class SetFunction(FunctionBase): |

557 | '''Represents all of the Set functions, though the actual implementations |

558 | are in two sub-classes specific to the condition and general set |

559 | functions.''' |

560 | |

561 | # Base name for the type-intersection functions. To get the standard |

562 | # identifier for a given type, use FunctionBase.FUNCTION_NS |

563 | # + the datatype's base name (e.g., string) + |

564 | # NAME_BASE_INTERSECTION. |

565 | NAME_BASE_INTERSECTION = "-intersection" |

566 | |

567 | # Base name for the type-at-least-one-member-of functions. To get the |

568 | # standard identifier for a given type, use |

569 | # FunctionBase.FUNCTION_NS + the datatype's base name (e.g., string) + |

570 | # NAME_BASE_AT_LEAST_ONE_MEMBER_OF. |

571 | NAME_BASE_AT_LEAST_ONE_MEMBER_OF = "-at-least-one-member-of" |

572 | |

573 | # Base name for the type-union funtions. To get the standard |

574 | # identifier for a given type, use FunctionBase.FUNCTION_NS |

575 | # + the datatype's base name (e.g., string) + NAME_BASE_UNION. |

576 | NAME_BASE_UNION = "-union" |

577 | |

578 | # Base name for the type-subset funtions. To get the standard |

579 | # identifier for a given type, use FunctionBase.FUNCTION_NS |

580 | # + the datatype's base name (e.g., string) + NAME_BASE_SUBSET. |

581 | NAME_BASE_SUBSET = "-subset" |

582 | |

583 | # Base name for the type-set-equals funtions. To get the standard |

584 | # identifier for a given type, use FunctionBase.FUNCTION_NS |

585 | # + the datatype's base name (e.g., string) + NAME_BASE_SET_EQUALS. |

586 | NAME_BASE_SET_EQUALS = "-set-equals" |

587 | |

588 | |

589 | # A complete list of all the XACML datatypes supported by the Set |

590 | # functions |

591 | baseTypes = ( |

592 | StringAttribute.identifier, |

593 | BooleanAttribute.identifier, |

594 | IntegerAttribute.identifier, |

595 | DoubleAttribute.identifier, |

596 | DateAttribute.identifier, |

597 | DateTimeAttribute.identifier, |

598 | TimeAttribute.identifier, |

599 | AnyURIAttribute.identifier, |

600 | HexBinaryAttribute.identifier, |

601 | Base64BinaryAttribute.identifier, |

602 | DayTimeDurationAttribute.identifier, |

603 | YearMonthDurationAttribute.identifier, |

604 | X500NameAttribute.identifier, |

605 | RFC822NameAttribute.identifier) |

606 | |

607 | # A complete list of all the XACML datatypes supported by the Set |

608 | # functions, using the "simple" form of the names (eg, string |

609 | # instead of http:#www.w3.org/2001/XMLSchema#string) |

610 | simpleTypes = ( |

611 | "string", |

612 | "boolean", |

613 | "integer", |

614 | "double", |

615 | "date", |

616 | "dateTime", |

617 | "time", |

618 | "anyURI", |

619 | "hexBinary", |

620 | "base64Binary", |

621 | "dayTimeDuration", |

622 | "yearMonthDuration", |

623 | "x500Name", |

624 | "rfc822Name") |

625 | |

626 | # Define as lambda to avoid reference to classes that aren't defined yet |

627 | _getSupportedIdentifiers = lambda: \ |

628 | ConditionSetFunction.supportedIdentifiers +\ |

629 | GeneralSetFunction.supportedIdentifiers |

630 | |

631 | # All the function identifiers supported by this class. |

632 | supportedIdentifiers = property(fget=_getSupportedIdentifiers) |

633 | |

634 | |

635 | def __init__(self, |

636 | functionName, |

637 | functionId, |

638 | argumentType, |

639 | returnType, |

640 | returnsBag): |

641 | '''Constuctor used by the general and condition subclasses only. |

642 | If you need to create a new SetFunction instance you |

643 | should either use one of the getInstance methods or |

644 | construct one of the sub-classes directly. |

645 | |

646 | @param functionName the identitifer for the function |

647 | @param functionId an optional, internal numeric identifier |

648 | @param argumentType the datatype this function accepts |

649 | @param returnType the datatype this function returns |

650 | @param returnsBag whether this function returns bags |

651 | ''' |

652 | super(SetFunction, self).__init__(functionName, |

653 | functionId, |

654 | argumentType, |

655 | True, |

656 | 2, |

657 | returnType, |

658 | returnsBag) |

659 | |

660 | |

661 | @classmethod |

662 | def getIntersectionInstance(cls, functionName, argumentType): |

663 | '''Creates a new instance of the intersection set function. |

664 | This should be used to create support for any new attribute types |

665 | and then the new SetFunction object should be added |

666 | to the factory (all set functions for the base types are already |

667 | installed in the factory). |

668 | |

669 | @param functionName the name of the function |

670 | @param argumentType the attribute type this function will work with |

671 | |

672 | @return a new SetFunction for the given type |

673 | ''' |

674 | return GeneralSetFunction(functionName, argumentType, |

675 | cls.NAME_BASE_INTERSECTION) |

676 | |

677 | @classmethod |

678 | def getAtLeastOneInstance(cls, functionName, argumentType): |

679 | '''Creates a new instance of the at-least-one-member-of set function. |

680 | This should be used to create support for any new attribute types |

681 | and then the new SetFunction object should be added |

682 | to the factory (all set functions for the base types are already |

683 | installed in the factory). |

684 | |

685 | @param functionName the name of the function |

686 | @param argumentType the attribute type this function will work with |

687 | |

688 | @return a new SetFunction for the given type |

689 | ''' |

690 | return ConditionSetFunction(functionName, argumentType, |

691 | cls.NAME_BASE_AT_LEAST_ONE_MEMBER_OF) |

692 | |

693 | @classmethod |

694 | def getUnionInstance(cls, functionName, argumentType): |

695 | '''Creates a new instance of the union set function. |

696 | This should be used to create support for any new attribute types |

697 | and then the new SetFunction object should be added |

698 | to the factory (all set functions for the base types are already |

699 | installed in the factory). |

700 | |

701 | @param functionName the name of the function |

702 | @param argumentType the attribute type this function will work with |

703 | |

704 | @return a new SetFunction for the given type |

705 | ''' |

706 | return GeneralSetFunction(functionName, argumentType, |

707 | cls.NAME_BASE_UNION) |

708 | |

709 | def getSubsetInstance(cls, functionName, argumentType): |

710 | '''Creates a new instance of the subset set function. |

711 | This should be used to create support for any new attribute types |

712 | and then the new SetFunction object should be added |

713 | to the factory (all set functions for the base types are already |

714 | installed in the factory). |

715 | |

716 | @param functionName the name of the function |

717 | @param argumentType the attribute type this function will work with |

718 | |

719 | @return a new SetFunction for the given type |

720 | ''' |

721 | return ConditionSetFunction(functionName, argumentType, |

722 | cls.NAME_BASE_SUBSET) |

723 | |

724 | def getSetEqualsInstance(cls, functionName, argumentType): |

725 | '''Creates a new instance of the equals set function. |

726 | This should be used to create support for any new attribute types |

727 | and then the new SetFunction object should be added |

728 | to the factory (all set functions for the base types are already |

729 | installed in the factory). |

730 | |

731 | @param functionName the name of the function |

732 | @param argumentType the attribute type this function will work with |

733 | |

734 | @return a new SetFunction for the given type |

735 | ''' |

736 | return ConditionSetFunction(functionName, argumentType, |

737 | cls.NAME_BASE_SET_EQUALS) |

738 | |

739 | |

740 | class ConditionSetFunction(SetFunction): |

741 | '''Specific SetFunction class that supports all of the |

742 | condition set functions: type-at-least-one-member-of, type-subset, and |

743 | type-set-equals.''' |

744 | |

745 | # Private identifiers for the supported functions |

746 | (ID_BASE_AT_LEAST_ONE_MEMBER_OF, |

747 | ID_BASE_SUBSET, |

748 | ID_BASE_SET_EQUALS) = range(3) |

749 | |

750 | # Mapping of function name to its associated id and parameter type |

751 | idMap = {} |

752 | typeMap = {} |

753 | for baseType, simpleType in zip(SetFunction.baseTypes, |

754 | SetFunction.simpleTypes): |

755 | baseName = SetFunction.FUNCTION_NS + simpleType |

756 | |

757 | idMap[baseName + SetFunction.NAME_BASE_AT_LEAST_ONE_MEMBER_OF] = \ |

758 | ID_BASE_AT_LEAST_ONE_MEMBER_OF |

759 | idMap[baseName + SetFunction.NAME_BASE_SUBSET] = ID_BASE_SUBSET |

760 | idMap[baseName + SetFunction.NAME_BASE_SET_EQUALS] = ID_BASE_SET_EQUALS |

761 | |

762 | typeMap[baseName+SetFunction.NAME_BASE_AT_LEAST_ONE_MEMBER_OF]=baseType |

763 | typeMap[baseName + SetFunction.NAME_BASE_SUBSET] = baseType |

764 | typeMap[baseName + SetFunction.NAME_BASE_SET_EQUALS] = baseType |

765 | |

766 | del baseName |

767 | |

768 | # the actual supported ids |

769 | supportedIdentifiers = tuple(idMap.keys()) |

770 | |

771 | idMap.update({ |

772 | SetFunction.NAME_BASE_AT_LEAST_ONE_MEMBER_OF: |

773 | ID_BASE_AT_LEAST_ONE_MEMBER_OF, |

774 | SetFunction.NAME_BASE_SUBSET: ID_BASE_SUBSET, |

775 | SetFunction.NAME_BASE_SET_EQUALS: ID_BASE_SET_EQUALS} |

776 | ) |

777 | |

778 | |

779 | def __init__(self, functionName, dataType=None): |

780 | '''Constructor that is used to create one of the condition standard |

781 | set functions. The name supplied must be one of the standard XACML |

782 | functions supported by this class, including the full namespace, |

783 | otherwise an exception is thrown. Look in SetFunction |

784 | for details about the supported names. |

785 | |

786 | @param functionName the name of the function to create |

787 | |

788 | @throws IllegalArgumentException if the function is unknown |

789 | ''' |

790 | if dataType is None: |

791 | dataType = ConditionSetFunction.typeMap[functionName] |

792 | |

793 | super(ConditionSetFunction, self).__init__(functionName, |

794 | ConditionSetFunction.idMap[functionName], |

795 | dataType, |

796 | BooleanAttribute.identifier, |

797 | False) |

798 | |

799 | |

800 | def evaluate(self, inputs, context): |

801 | '''Evaluates the function, using the specified parameters. |

802 | |

803 | @param inputs a list of Evaluatable objects representing the arguments |

804 | passed to the function |

805 | @param context an EvaluationCtx so that the Evaluatable objects can be |

806 | evaluated |

807 | @return an EvaluationResult representing the function's result |

808 | ''' |

809 | |

810 | # Evaluate the arguments |

811 | argValues = AttributeValue[len(inputs)] |

812 | evalResult = self.evalArgs(inputs, context, argValues) |

813 | if evalResult is not None: |

814 | return evalResult |

815 | |

816 | # Setup the two bags we'll be using |

817 | bags = argValues[:1] |

818 | |

819 | result = None |

820 | |

821 | if self.functionId == \ |

822 | ConditionSetFunction.ID_BASE_AT_LEAST_ONE_MEMBER_OF: |

823 | |

824 | #-at-least-one-member-of takes two bags of the same type and |

825 | # returns a boolean |

826 | |

827 | # true if at least one element in the first argument is in the |

828 | # second argument (using the-is-in semantics) |

829 | |

830 | result = BooleanAttribute.getFalseInstance() |

831 | for it in bags[0]: |

832 | if it in bags[1]: |

833 | result = BooleanAttribute.getTrueInstance() |

834 | break |

835 | |

836 | elif self.functionId == ConditionSetFunction.ID_BASE_SUBSET: |

837 | #-set-equals takes two bags of the same type and returns |

838 | # a boolean |

839 | |

840 | # returns true if the first argument is a subset of the second |

841 | # argument (ie, all the elements in the first bag appear in |

842 | # the second bag) ... ignore all duplicate values in both |

843 | # input bags |

844 | |

845 | subset = bags[1].containsAll(bags[0]) |

846 | result = BooleanAttribute.getInstance(subset) |

847 | |

848 | elif self.functionId == ConditionSetFunction.ID_BASE_SET_EQUALS: |

849 | #-set-equals takes two bags of the same type and returns |

850 | # a boolean |

851 | |

852 | # returns true if the two inputs contain the same elements |

853 | # discounting any duplicates in either input ... this is the same |

854 | # as applying the and function on the subset function with |

855 | # the two inputs, and then the two inputs reversed (ie, are the |

856 | # two inputs subsets of each other) |

857 | |

858 | equals = bags[1].containsAll(bags[0] and \ |

859 | bags[0].containsAll(bags[1])) |

860 | result = BooleanAttribute.getInstance(equals) |

861 | |

862 | return EvaluationResult(result) |

863 | |

864 | |

865 | class GeneralSetFunction(SetFunction): |

866 | supportedIdentifiers = () |

867 | def __init__(self, *arg, **kw): |

868 | raise NotImplementedError() |

869 | |

870 | class ConditionBagFunction(BagFunction): |

871 | def __init__(self, *arg, **kw): |

872 | raise NotImplementedError() |

873 | |

874 | class HigherOrderFunction(Function): |

875 | supportedIdentifiers = () |

876 | def __init__(self, *arg, **kw): |

877 | raise NotImplementedError() |

878 | |

879 | # TODO: Function classes - minimal implementation until opportunity to fully |

880 | # implement |

881 | class LogicalFunction(FunctionBase): |

882 | |

883 | def __init__(self, *arg, **kw): |

884 | raise NotImplementedError() |

885 | |

886 | class NOfFunction(FunctionBase): |

887 | |

888 | def __init__(self, *arg, **kw): |

889 | raise NotImplementedError() |

890 | |

891 | class NotFunction(FunctionBase): |

892 | |

893 | def __init__(self, *arg, **kw): |

894 | raise NotImplementedError() |

895 | |

896 | class ComparisonFunction(FunctionBase): |

897 | |

898 | def __init__(self, *arg, **kw): |

899 | raise NotImplementedError() |

900 | |

901 | class MatchFunction(FunctionBase): |

902 | NAME_REGEXP_STRING_MATCH = FunctionBase.FUNCTION_NS + "regexp-string-match" |

903 | NAME_RFC822NAME_MATCH = FunctionBase.FUNCTION_NS + "rfc822Name-match" |

904 | NAME_X500NAME_MATCH = FunctionBase.FUNCTION_NS + "x500Name-match" |

905 | |

906 | supportedIdentifiers = ( |

907 | NAME_REGEXP_STRING_MATCH, |

908 | NAME_RFC822NAME_MATCH, |

909 | NAME_X500NAME_MATCH) |

910 | |

911 | functionIds = range(3) |

912 | ID_REGEXP_STRING_MATCH, ID_X500NAME_MATCH, ID_RFC822NAME_MATCH=functionIds |

913 | getId = dict(zip(supportedIdentifiers, functionIds)) |

914 | argParams = ( |

915 | (StringAttribute.identifier,)*2, |

916 | (X500NameAttribute.identifier,)*2, |

917 | (StringAttribute.identifier, |

918 | RFC822NameAttribute.identifier) |

919 | ) |

920 | getArgumentTypes = dict(zip(supportedIdentifiers, argParams)) |

921 | |

922 | bagParams = (False, False) |

923 | |

924 | lut = { |

925 | NAME_REGEXP_STRING_MATCH: 'regexpStringMatch', |

926 | NAME_RFC822NAME_MATCH: 'rfc822NameMatch', |

927 | NAME_X500NAME_MATCH: 'x500NameMatch' |

928 | } |

929 | |

930 | def __init__(self, functionName, **kw): |

931 | super(MatchFunction, self).__init__(functionName, |

932 | functionId=MatchFunction.getId[functionName], |

933 | paramType=MatchFunction.getArgumentTypes[functionName], |

934 | paramIsBag=MatchFunction.bagParams, |

935 | returnType=BooleanAttribute.identifier, |

936 | returnsBag=False) |

937 | |

938 | |

939 | def regexpStringMatch(self, regex, val): |

940 | return re.match(regex, val) is not None |

941 | |

942 | def rfc822NameMatch(self, *inputs): |

943 | raise NotImplementedError() |

944 | |

945 | def x500NameMatch(self, *inputs): |

946 | raise NotImplementedError() |

947 | |

948 | def evaluate(self, inputs, context): |

949 | matchFunction = getattr(self, MatchFunction.lut[self.functionName]) |

950 | match = matchFunction(self, *inputs) |

951 | if match: |

952 | return EvaluationResult(status=Status.STATUS_OK) |

953 | |

954 | |

955 | class EqualFunction(FunctionBase): |

956 | supportedIdentifiers = ( |

957 | FunctionBase.FUNCTION_NS + "anyURI-equal", |

958 | FunctionBase.FUNCTION_NS + "base64Binary-equal", |

959 | FunctionBase.FUNCTION_NS + "boolean-equal", |

960 | FunctionBase.FUNCTION_NS + "date-equal", |

961 | FunctionBase.FUNCTION_NS + "dateTime-equal", |

962 | FunctionBase.FUNCTION_NS + "dayTimeDuration-equal", |

963 | FunctionBase.FUNCTION_NS + "double-equal", |

964 | FunctionBase.FUNCTION_NS + "hexBinary-equal", |

965 | FunctionBase.FUNCTION_NS + "integer-equal", |

966 | FunctionBase.FUNCTION_NS + "rfc822Name-equal", |

967 | FunctionBase.FUNCTION_NS + "string-equal", |

968 | FunctionBase.FUNCTION_NS + "time-equal", |

969 | FunctionBase.FUNCTION_NS + "x500Name-equal", |

970 | FunctionBase.FUNCTION_NS + "yearMonthDuration-equal" |

971 | ) |

972 | |

973 | (NAME_ANYURI_EQUAL, |

974 | NAME_BASE64BINARY_EQUAL, |

975 | NAME_BOOLEAN_EQUAL, |

976 | NAME_DATE_EQUAL, |

977 | NAME_DATETIME_EQUAL, |

978 | NAME_DAYTIME_DURATION_EQUAL, |

979 | NAME_DOUBLE_EQUAL, |

980 | NAME_HEXBINARY_EQUAL, |

981 | NAME_INTEGER_EQUAL, |

982 | NAME_RFC822NAME_EQUAL, |

983 | NAME_STRING_EQUAL, |

984 | NAME_TIME_EQUAL, |

985 | NAME_X500NAME_EQUAL, |

986 | NAME_YEARMONTH_DURATION_EQUAL) = supportedIdentifiers |

987 | |

988 | lut = { |

989 | NAME_STRING_EQUAL: 'stringEqual' |

990 | } |

991 | |

992 | _attrClasses = ( |

993 | AnyURIAttribute, |

994 | Base64BinaryAttribute, |

995 | BooleanAttribute, |

996 | DateAttribute, |

997 | DateTimeAttribute, |

998 | DayTimeDurationAttribute, |

999 | DoubleAttribute, |

1000 | HexBinaryAttribute, |

1001 | IntegerAttribute, |

1002 | RFC822NameAttribute, |

1003 | StringAttribute, |

1004 | TimeAttribute, |

1005 | X500NameAttribute, |

1006 | YearMonthDurationAttribute |

1007 | ) |

1008 | |

1009 | typeMap = dict([(i, j.identifier) for i,j in zip(supportedIdentifiers, |

1010 | _attrClasses)]) |

1011 | |

1012 | def __init__(self, functionName, argumentType=None, **kw): |

1013 | if kw.get('functionId') is None: |

1014 | kw['functionId'] = functionName |

1015 | |

1016 | if kw.get('paramType') is None: |

1017 | kw['paramType'] = EqualFunction._getArgumentType(functionName) |

1018 | |

1019 | super(EqualFunction, self).__init__(functionName, **kw) |

1020 | |

1021 | def evaluate(self, inputs, evaluationCtx): |

1022 | function = EqualFunction.lut.get(self.functionName) |

1023 | if function is None: |

1024 | if self.functionName in supportedIdentifiers: |

1025 | raise NotImplementedError("No implementation is available for " |

1026 | "%s" % self.functionName) |

1027 | else: |

1028 | raise AttributeError('function name "%s" not recognised ' |

1029 | 'for %s' % (self.functionName, |

1030 | self.__class__.__name__)) |

1031 | |

1032 | return getattr(self, function)(inputs, evaluationCtx) |

1033 | |

1034 | def stringEqual(self, inputs, evaluationCtx): |

1035 | result = self.evalArgs(inputs, context, argValues) |

1036 | if result is not None: |

1037 | return result |

1038 | |

1039 | return EvaluationResult(argValues[0] == argValues[1]) |

1040 | |

1041 | @classmethod |

1042 | def _getArgumentType(cls, functionName): |

1043 | argumentType = cls.typeMap.get(functionName) |

1044 | if argumentType is None: |

1045 | if functionName in cls.supportedIdentifiers: |

1046 | raise NotImplementedError('No implementation is currently ' |

1047 | 'available for "%s"' % functionName) |

1048 | else: |

1049 | raise TypeError("Not a standard function: %s" % functionName) |

1050 | |

1051 | return argumentType |

1052 | |

1053 | class AddFunction(FunctionBase): |

1054 | |

1055 | def __init__(self, *arg, **kw): |

1056 | raise NotImplementedError() |

1057 | |

1058 | class SubtractFunction(FunctionBase): |

1059 | |

1060 | def __init__(self, *arg, **kw): |

1061 | raise NotImplementedError() |

1062 | |

1063 | class MultiplyFunction(FunctionBase): |

1064 | |

1065 | def __init__(self, *arg, **kw): |

1066 | raise NotImplementedError() |

1067 | |

1068 | class DivideFunction(FunctionBase): |

1069 | |

1070 | def __init__(self, *arg, **kw): |

1071 | raise NotImplementedError() |

1072 | |

1073 | class ModFunction(FunctionBase): |

1074 | |

1075 | def __init__(self, *arg, **kw): |

1076 | raise NotImplementedError() |

1077 | |

1078 | class AbsFunction(FunctionBase): |

1079 | |

1080 | def __init__(self, *arg, **kw): |

1081 | raise NotImplementedError() |

1082 | |

1083 | class RoundFunction(FunctionBase): |

1084 | |

1085 | def __init__(self, *arg, **kw): |

1086 | raise NotImplementedError() |

1087 | |

1088 | class FloorFunction(FunctionBase): |

1089 | |

1090 | def __init__(self, *arg, **kw): |

1091 | raise NotImplementedError() |

1092 | |

1093 | class DateMathFunction(FunctionBase): |

1094 | |

1095 | def __init__(self, *arg, **kw): |

1096 | raise NotImplementedError() |

1097 | |

1098 | class GeneralBagFunction(BagFunction): |

1099 | |

1100 | def __init__(self, *arg, **kw): |

1101 | raise NotImplementedError() |

1102 | |

1103 | class NumericConvertFunction(FunctionBase): |

1104 | |

1105 | def __init__(self, *arg, **kw): |

1106 | raise NotImplementedError() |

1107 | |

1108 | class StringNormalizeFunction(FunctionBase): |

1109 | |

1110 | def __init__(self, *arg, **kw): |

1111 | raise NotImplementedError() |

1112 | |

1113 | class MapFunction(Function): |

1114 | supportedIdentifiers = () |

1115 | NAME_MAP = FunctionBase.FUNCTION_NS + "map" |

1116 | |

1117 | def __init__(self, *arg, **kw): |

1118 | raise NotImplementedError() |

1119 | |

1120 | @classmethod |

1121 | def getInstance(cls, root): |

1122 | raise NotImplementedError() |

1123 | |

1124 | class FunctionProxy(): |

1125 | |

1126 | def getInstance(self, root): |

1127 | raise NotImplementedError() |

1128 | |

1129 | class MapFunctionProxy(FunctionProxy): |

1130 | |

1131 | def getInstance(self, root): |

1132 | return MapFunction.getInstance(root) |

**Note:**See TracBrowser for help on using the repository browser.