#
source:
TI12-security/trunk/python/ndg.security.common/ndg/security/common/authz/xacml/cond/__init__.py
@
5397

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI12-security/trunk/python/ndg.security.common/ndg/security/common/authz/xacml/cond/__init__.py@5397
Revision 5397, 44.2 KB checked in by pjkersha, 11 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 import getLocalName |

49 | from ndg.security.common.authz.xacml.exceptions import \ |

50 | UnknownIdentifierException, ParsingException |

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

52 | EvaluationResult |

53 | from ndg.security.common.authz.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 | |

92 | @classmethod |

93 | def getConditionInstance(cls, root): |

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

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

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

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

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

99 | a boolean value. |

100 | |

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

102 | ''' |

103 | from ndg.security.common.authz.xacml.cond.factory import \ |

104 | FunctionFactory |

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

106 | |

107 | |

108 | @classmethod |

109 | def getInstance(cls, root): |

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

111 | |

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

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

114 | ''' |

115 | from ndg.security.common.authz.xacml.cond.factory import \ |

116 | FunctionFactory |

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

118 | |

119 | |

120 | @classmethod |

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

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

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

124 | kind of function.''' |

125 | |

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

127 | bagFunction = None |

128 | evals = [] |

129 | |

130 | attrFactory = AttributeFactory.getInstance() |

131 | |

132 | for elem in root: |

133 | name = getLocalName(elem) |

134 | |

135 | if name == "Apply": |

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

137 | |

138 | elif name == "AttributeValue": |

139 | try: |

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

141 | |

142 | except UnknownIdentifierException, e: |

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

144 | |

145 | elif name == "SubjectAttributeDesignator": |

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

147 | AttributeDesignator.SUBJECT_TARGET)) |

148 | |

149 | elif name =="ResourceAttributeDesignator": |

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

151 | AttributeDesignator.RESOURCE_TARGET)) |

152 | |

153 | elif name == "ActionAttributeDesignator": |

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

155 | AttributeDesignator.ACTION_TARGET)) |

156 | |

157 | elif name == "EnvironmentAttributeDesignator": |

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

159 | AttributeDesignator.ENVIRONMENT_TARGET)) |

160 | |

161 | elif name == "AttributeSelector": |

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

163 | |

164 | elif name == "Function": |

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

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

167 | if bagFunction != None: |

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

169 | |

170 | from ndg.security.common.authz.xacml.cond.factory import \ |

171 | FunctionFactory |

172 | bagFunction = cls.__getFunction(elem, |

173 | FunctionFactory.getGeneralInstance()) |

174 | |

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

176 | |

177 | |

178 | @classmethod |

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

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

181 | |

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

183 | try: |

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

185 | return factory.createFunction(functionName) |

186 | |

187 | except UnknownIdentifierException, e: |

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

189 | |

190 | except FunctionTypeException, e: |

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

192 | try: |

193 | from ndg.security.common.authz.xacml.cond.factory import \ |

194 | FunctionFactory |

195 | functionFactory = FunctionFactory.getGeneralInstance() |

196 | return functionFactory.createAbstractFunction(functionName, |

197 | root) |

198 | except Exception, e: |

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

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

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

202 | |

203 | def getFunction(self): |

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

205 | |

206 | @return the Function''' |

207 | return self._function |

208 | |

209 | def getChildren(self): |

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

211 | The List contains Evaluatables. The list is |

212 | unmodifiable, and may be empty. |

213 | |

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

215 | return self._evals |

216 | |

217 | def getHigherOrderFunction(self): |

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

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

220 | |

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

222 | return self.bagFunction |

223 | |

224 | def isCondition(self): |

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

226 | |

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

228 | return isCondition |

229 | |

230 | def evaluate(self, context): |

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

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

233 | other Apply objects. |

234 | |

235 | @param context the representation of the request |

236 | |

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

238 | parameters = self.evals |

239 | |

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

241 | if bagFunction != None: |

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

243 | # with the function |

244 | parameters = [bagFunction] |

245 | |

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

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

248 | # all the AttributeValues that get returned |

249 | for eval in self.evals: |

250 | result = eval.evaluate(context) |

251 | |

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

253 | # we stop right away |

254 | if result.indeterminate(): |

255 | return result |

256 | |

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

258 | |

259 | # now we can call the base function |

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

261 | |

262 | def getType(self): |

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

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

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

266 | by this object. |

267 | |

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

269 | return self.function.getReturnType() |

270 | |

271 | def evaluatesToBag(self): |

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

273 | of values on evaluation. |

274 | |

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

276 | ''' |

277 | return self.function.returnsBag() |

278 | |

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

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

281 | writes this encoding to the given OutputStream with |

282 | indentation. |

283 | |

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

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

286 | raise NotImplementedError() |

287 | |

288 | class Function(object): |

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

290 | |

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

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

293 | The List contains Evaluatables which are all |

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

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

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

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

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

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

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

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

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

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

304 | <p> |

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

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

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

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

309 | AttributeValue objects (no INDETERMINATE values are |

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

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

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

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

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

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

316 | checkInputs on its sub-function when |

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

318 | |

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

320 | @param context the representation of the request |

321 | |

322 | @return a result containing the AttributeValue computed |

323 | when evaluating the function, or Status |

324 | specifying some error condition''' |

325 | raise NotImplementedError() |

326 | |

327 | |

328 | def getIdentifier(self): |

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

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

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

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

333 | |

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

335 | raise NotImplementedError() |

336 | |

337 | def getReturnType(self): |

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

339 | returns from evaluate in a successful evaluation. |

340 | |

341 | @return the type returned by this function |

342 | ''' |

343 | raise NotImplementedError() |

344 | |

345 | def returnsBag(self): |

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

347 | single value. |

348 | |

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

350 | raise NotImplementedError() |

351 | |

352 | def checkInputs(self, inputs): |

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

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

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

356 | IllegalArgumentException is thrown. |

357 | |

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

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

360 | |

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

362 | evaluation |

363 | ''' |

364 | raise NotImplementedError() |

365 | |

366 | def checkInputsNoBag(self, inputs): |

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

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

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

370 | IllegalArgumentException is thrown. Unlike the other |

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

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

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

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

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

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

377 | generates. |

378 | |

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

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

381 | |

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

383 | evaluation''' |

384 | raise NotImplementedError() |

385 | |

386 | |

387 | class FunctionBase(Function): |

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

389 | supportedIdentifiers = () |

390 | |

391 | def __init__(self, |

392 | functionName, |

393 | functionId=None, |

394 | paramType=None, |

395 | paramIsBag=False, |

396 | numParams=-1, |

397 | minParams=0, |

398 | returnType='', |

399 | returnsBag=False): |

400 | ''' |

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

402 | and any XACML policies |

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

404 | code for convenience |

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

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

407 | documents |

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

409 | of values |

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

411 | or -1 if any number are allowed |

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

413 | numParams is -1 |

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

415 | the factory and any XACML documents |

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

417 | ''' |

418 | self.functionName = functionName |

419 | self.functionId = functionId |

420 | self.returnType = returnType |

421 | self.returnsBag = returnsBag |

422 | |

423 | self.paramType = paramType |

424 | |

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

426 | if not self.paramType: |

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

428 | self.singleType = False |

429 | |

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

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

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

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

434 | 'have the same length') |

435 | else: |

436 | self.singleType = True |

437 | |

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

439 | self.numParams = numParams |

440 | self.minParams = minParams |

441 | |

442 | self.paramIsBag = paramIsBag |

443 | |

444 | |

445 | def _setFunctionName(self, functionName): |

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

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

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

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

450 | self._functionName = functionName |

451 | |

452 | def _getFunctionName(self): |

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

454 | |

455 | functionName = property(fset=_setFunctionName, |

456 | fget=_getFunctionName) |

457 | |

458 | def checkInputs(self, inputs): |

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

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

461 | raise NotImplementedError() |

462 | |

463 | def checkInputsNoBag(self, inputs): |

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

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

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

467 | override this method. |

468 | |

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

470 | |

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

472 | ''' |

473 | numInputs = len(inputs) |

474 | |

475 | if self.singleType: |

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

477 | if sum(self.paramIsBag): |

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

479 | |

480 | # now check on the length |

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

482 | if numInputs != self.numParams: |

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

484 | self.functionName) |

485 | else: |

486 | if numInputs < self.minParams: |

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

488 | |

489 | |

490 | # finally check param list |

491 | for eval in inputs: |

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

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

494 | "%s type is %s" % |

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

496 | self.__class__.__name__, |

497 | self.paramType)) |

498 | |

499 | else: |

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

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

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

503 | self.functionName) |

504 | |

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

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

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

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

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

510 | "%s type is %s" % |

511 | (eval.type, |

512 | self.__class__.__name__, |

513 | paramType)) |

514 | |

515 | |

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

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

518 | raise NotImplementedError() |

519 | |

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

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

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

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

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

525 | list can be used. |

526 | |

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

528 | to evaluate |

529 | @param context the representation of the request |

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

531 | contain the AttributeValues generated from evaluating all parameters |

532 | |

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

534 | an EvaluationResult representing the error |

535 | ''' |

536 | index = 0 |

537 | |

538 | for eval in params: |

539 | # get and evaluate the next parameter |

540 | result = eval.evaluate(context) |

541 | |

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

543 | if result.indeterminate(): |

544 | return result |

545 | |

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

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

548 | index += 1 |

549 | |

550 | return None |

551 | |

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

553 | # implement |

554 | class BagFunction(FunctionBase): |

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

556 | raise NotImplementedError() |

557 | |

558 | class SetFunction(FunctionBase): |

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

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

561 | functions.''' |

562 | |

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

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

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

566 | # NAME_BASE_INTERSECTION. |

567 | NAME_BASE_INTERSECTION = "-intersection" |

568 | |

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

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

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

572 | # NAME_BASE_AT_LEAST_ONE_MEMBER_OF. |

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

574 | |

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

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

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

578 | NAME_BASE_UNION = "-union" |

579 | |

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

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

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

583 | NAME_BASE_SUBSET = "-subset" |

584 | |

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

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

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

588 | NAME_BASE_SET_EQUALS = "-set-equals" |

589 | |

590 | |

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

592 | # functions |

593 | baseTypes = ( |

594 | StringAttribute.identifier, |

595 | BooleanAttribute.identifier, |

596 | IntegerAttribute.identifier, |

597 | DoubleAttribute.identifier, |

598 | DateAttribute.identifier, |

599 | DateTimeAttribute.identifier, |

600 | TimeAttribute.identifier, |

601 | AnyURIAttribute.identifier, |

602 | HexBinaryAttribute.identifier, |

603 | Base64BinaryAttribute.identifier, |

604 | DayTimeDurationAttribute.identifier, |

605 | YearMonthDurationAttribute.identifier, |

606 | X500NameAttribute.identifier, |

607 | RFC822NameAttribute.identifier) |

608 | |

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

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

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

612 | simpleTypes = ( |

613 | "string", |

614 | "boolean", |

615 | "integer", |

616 | "double", |

617 | "date", |

618 | "dateTime", |

619 | "time", |

620 | "anyURI", |

621 | "hexBinary", |

622 | "base64Binary", |

623 | "dayTimeDuration", |

624 | "yearMonthDuration", |

625 | "x500Name", |

626 | "rfc822Name") |

627 | |

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

629 | _getSupportedIdentifiers = lambda: \ |

630 | ConditionSetFunction.supportedIdentifiers +\ |

631 | GeneralSetFunction.supportedIdentifiers |

632 | |

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

634 | supportedIdentifiers = property(fget=_getSupportedIdentifiers) |

635 | |

636 | |

637 | def __init__(self, |

638 | functionName, |

639 | functionId, |

640 | argumentType, |

641 | returnType, |

642 | returnsBag): |

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

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

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

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

647 | |

648 | @param functionName the identitifer for the function |

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

650 | @param argumentType the datatype this function accepts |

651 | @param returnType the datatype this function returns |

652 | @param returnsBag whether this function returns bags |

653 | ''' |

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

655 | functionId, |

656 | argumentType, |

657 | True, |

658 | 2, |

659 | returnType, |

660 | returnsBag) |

661 | |

662 | |

663 | @classmethod |

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

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

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

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

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

669 | installed in the factory). |

670 | |

671 | @param functionName the name of the function |

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

673 | |

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

675 | ''' |

676 | return GeneralSetFunction(functionName, argumentType, |

677 | cls.NAME_BASE_INTERSECTION) |

678 | |

679 | @classmethod |

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

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

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

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

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

685 | installed in the factory). |

686 | |

687 | @param functionName the name of the function |

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

689 | |

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

691 | ''' |

692 | return ConditionSetFunction(functionName, argumentType, |

693 | cls.NAME_BASE_AT_LEAST_ONE_MEMBER_OF) |

694 | |

695 | @classmethod |

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

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

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

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

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

701 | installed in the factory). |

702 | |

703 | @param functionName the name of the function |

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

705 | |

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

707 | ''' |

708 | return GeneralSetFunction(functionName, argumentType, |

709 | cls.NAME_BASE_UNION) |

710 | |

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

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

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

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

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

716 | installed in the factory). |

717 | |

718 | @param functionName the name of the function |

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

720 | |

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

722 | ''' |

723 | return ConditionSetFunction(functionName, argumentType, |

724 | cls.NAME_BASE_SUBSET) |

725 | |

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

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

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

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

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

731 | installed in the factory). |

732 | |

733 | @param functionName the name of the function |

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

735 | |

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

737 | ''' |

738 | return ConditionSetFunction(functionName, argumentType, |

739 | cls.NAME_BASE_SET_EQUALS) |

740 | |

741 | |

742 | class ConditionSetFunction(SetFunction): |

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

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

745 | type-set-equals.''' |

746 | |

747 | # Private identifiers for the supported functions |

748 | (ID_BASE_AT_LEAST_ONE_MEMBER_OF, |

749 | ID_BASE_SUBSET, |

750 | ID_BASE_SET_EQUALS) = range(3) |

751 | |

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

753 | idMap = {} |

754 | typeMap = {} |

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

756 | SetFunction.simpleTypes): |

757 | baseName = SetFunction.FUNCTION_NS + simpleType |

758 | |

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

760 | ID_BASE_AT_LEAST_ONE_MEMBER_OF |

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

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

763 | |

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

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

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

767 | |

768 | del baseName |

769 | |

770 | # the actual supported ids |

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

772 | |

773 | idMap.update({ |

774 | SetFunction.NAME_BASE_AT_LEAST_ONE_MEMBER_OF: |

775 | ID_BASE_AT_LEAST_ONE_MEMBER_OF, |

776 | SetFunction.NAME_BASE_SUBSET: ID_BASE_SUBSET, |

777 | SetFunction.NAME_BASE_SET_EQUALS: ID_BASE_SET_EQUALS} |

778 | ) |

779 | |

780 | |

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

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

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

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

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

786 | for details about the supported names. |

787 | |

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

789 | |

790 | @throws IllegalArgumentException if the function is unknown |

791 | ''' |

792 | if dataType is None: |

793 | dataType = ConditionSetFunction.typeMap[functionName] |

794 | |

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

796 | ConditionSetFunction.idMap[functionName], |

797 | dataType, |

798 | BooleanAttribute.identifier, |

799 | False) |

800 | |

801 | |

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

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

804 | |

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

806 | passed to the function |

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

808 | evaluated |

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

810 | ''' |

811 | |

812 | # Evaluate the arguments |

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

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

815 | if evalResult is not None: |

816 | return evalResult |

817 | |

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

819 | bags = argValues[:1] |

820 | |

821 | result = None |

822 | |

823 | if self.functionId == \ |

824 | ConditionSetFunction.ID_BASE_AT_LEAST_ONE_MEMBER_OF: |

825 | |

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

827 | # returns a boolean |

828 | |

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

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

831 | |

832 | result = BooleanAttribute.getFalseInstance() |

833 | for it in bags[0]: |

834 | if it in bags[1]: |

835 | result = BooleanAttribute.getTrueInstance() |

836 | break |

837 | |

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

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

840 | # a boolean |

841 | |

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

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

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

845 | # input bags |

846 | |

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

848 | result = BooleanAttribute.getInstance(subset) |

849 | |

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

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

852 | # a boolean |

853 | |

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

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

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

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

858 | # two inputs subsets of each other) |

859 | |

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

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

862 | result = BooleanAttribute.getInstance(equals) |

863 | |

864 | return EvaluationResult(result) |

865 | |

866 | |

867 | class GeneralSetFunction(SetFunction): |

868 | supportedIdentifiers = () |

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

870 | raise NotImplementedError() |

871 | |

872 | class ConditionBagFunction(BagFunction): |

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

874 | raise NotImplementedError() |

875 | |

876 | class HigherOrderFunction(Function): |

877 | supportedIdentifiers = () |

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

879 | raise NotImplementedError() |

880 | |

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

882 | # implement |

883 | class LogicalFunction(FunctionBase): |

884 | |

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

886 | raise NotImplementedError() |

887 | |

888 | class NOfFunction(FunctionBase): |

889 | |

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

891 | raise NotImplementedError() |

892 | |

893 | class NotFunction(FunctionBase): |

894 | |

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

896 | raise NotImplementedError() |

897 | |

898 | class ComparisonFunction(FunctionBase): |

899 | |

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

901 | raise NotImplementedError() |

902 | |

903 | class MatchFunction(FunctionBase): |

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

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

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

907 | |

908 | supportedIdentifiers = ( |

909 | NAME_REGEXP_STRING_MATCH, |

910 | NAME_RFC822NAME_MATCH, |

911 | NAME_X500NAME_MATCH) |

912 | |

913 | functionIds = range(3) |

914 | ID_REGEXP_STRING_MATCH, ID_X500NAME_MATCH, ID_RFC822NAME_MATCH=functionIds |

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

916 | argParams = ( |

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

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

919 | (StringAttribute.identifier, |

920 | RFC822NameAttribute.identifier) |

921 | ) |

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

923 | |

924 | bagParams = (False, False) |

925 | |

926 | lut = { |

927 | NAME_REGEXP_STRING_MATCH: 'regexpStringMatch', |

928 | NAME_RFC822NAME_MATCH: 'rfc822NameMatch', |

929 | NAME_X500NAME_MATCH: 'x500NameMatch' |

930 | } |

931 | |

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

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

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

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

936 | paramIsBag=MatchFunction.bagParams, |

937 | returnType=BooleanAttribute.identifier, |

938 | returnsBag=False) |

939 | |

940 | |

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

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

943 | |

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

945 | raise NotImplementedError() |

946 | |

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

948 | raise NotImplementedError() |

949 | |

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

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

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

953 | if match: |

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

955 | |

956 | |

957 | class EqualFunction(FunctionBase): |

958 | supportedIdentifiers = ( |

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

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

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

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

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

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

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

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

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

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

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

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

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

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

973 | ) |

974 | |

975 | (NAME_ANYURI_EQUAL, |

976 | NAME_BASE64BINARY_EQUAL, |

977 | NAME_BOOLEAN_EQUAL, |

978 | NAME_DATE_EQUAL, |

979 | NAME_DATETIME_EQUAL, |

980 | NAME_DAYTIME_DURATION_EQUAL, |

981 | NAME_DOUBLE_EQUAL, |

982 | NAME_HEXBINARY_EQUAL, |

983 | NAME_INTEGER_EQUAL, |

984 | NAME_RFC822NAME_EQUAL, |

985 | NAME_STRING_EQUAL, |

986 | NAME_TIME_EQUAL, |

987 | NAME_X500NAME_EQUAL, |

988 | NAME_YEARMONTH_DURATION_EQUAL) = supportedIdentifiers |

989 | |

990 | lut = { |

991 | NAME_STRING_EQUAL: 'stringEqual' |

992 | } |

993 | |

994 | _attrClasses = ( |

995 | AnyURIAttribute, |

996 | Base64BinaryAttribute, |

997 | BooleanAttribute, |

998 | DateAttribute, |

999 | DateTimeAttribute, |

1000 | DayTimeDurationAttribute, |

1001 | DoubleAttribute, |

1002 | HexBinaryAttribute, |

1003 | IntegerAttribute, |

1004 | RFC822NameAttribute, |

1005 | StringAttribute, |

1006 | TimeAttribute, |

1007 | X500NameAttribute, |

1008 | YearMonthDurationAttribute |

1009 | ) |

1010 | |

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

1012 | _attrClasses)]) |

1013 | |

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

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

1016 | kw['functionId'] = functionName |

1017 | |

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

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

1020 | |

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

1022 | |

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

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

1025 | if function is None: |

1026 | if self.functionName in supportedIdentifiers: |

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

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

1029 | else: |

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

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

1032 | self.__class__.__name__)) |

1033 | |

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

1035 | |

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

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

1038 | if result is not None: |

1039 | return result |

1040 | |

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

1042 | |

1043 | @classmethod |

1044 | def _getArgumentType(cls, functionName): |

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

1046 | if argumentType is None: |

1047 | if functionName in cls.supportedIdentifiers: |

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

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

1050 | else: |

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

1052 | |

1053 | return argumentType |

1054 | |

1055 | class AddFunction(FunctionBase): |

1056 | |

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

1058 | raise NotImplementedError() |

1059 | |

1060 | class SubtractFunction(FunctionBase): |

1061 | |

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

1063 | raise NotImplementedError() |

1064 | |

1065 | class MultiplyFunction(FunctionBase): |

1066 | |

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

1068 | raise NotImplementedError() |

1069 | |

1070 | class DivideFunction(FunctionBase): |

1071 | |

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

1073 | raise NotImplementedError() |

1074 | |

1075 | class ModFunction(FunctionBase): |

1076 | |

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

1078 | raise NotImplementedError() |

1079 | |

1080 | class AbsFunction(FunctionBase): |

1081 | |

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

1083 | raise NotImplementedError() |

1084 | |

1085 | class RoundFunction(FunctionBase): |

1086 | |

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

1088 | raise NotImplementedError() |

1089 | |

1090 | class FloorFunction(FunctionBase): |

1091 | |

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

1093 | raise NotImplementedError() |

1094 | |

1095 | class DateMathFunction(FunctionBase): |

1096 | |

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

1098 | raise NotImplementedError() |

1099 | |

1100 | class GeneralBagFunction(BagFunction): |

1101 | |

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

1103 | raise NotImplementedError() |

1104 | |

1105 | class NumericConvertFunction(FunctionBase): |

1106 | |

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

1108 | raise NotImplementedError() |

1109 | |

1110 | class StringNormalizeFunction(FunctionBase): |

1111 | |

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

1113 | raise NotImplementedError() |

1114 | |

1115 | class MapFunction(Function): |

1116 | supportedIdentifiers = () |

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

1118 | |

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

1120 | raise NotImplementedError() |

1121 | |

1122 | @classmethod |

1123 | def getInstance(cls, root): |

1124 | raise NotImplementedError() |

1125 | |

1126 | class FunctionProxy(): |

1127 | |

1128 | def getInstance(self, root): |

1129 | raise NotImplementedError() |

1130 | |

1131 | class MapFunctionProxy(FunctionProxy): |

1132 | |

1133 | def getInstance(self, root): |

1134 | return MapFunction.getInstance(root) |

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